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             sdmain.setAsCurrentOpenGlContext();
467 	    nvg = nvgCreateContext();
468 	    if (nvg is null) assert(0, "cannot initialize NanoVega");
469 	  };
470 
471 	  // this callback will be called when we will need to repaint our window
472 	  sdmain.redrawOpenGlScene = delegate () {
473 	    // fix viewport (we can do this in resize event, or here, it doesn't matter)
474 	    glViewport(0, 0, sdmain.width, sdmain.height);
475 
476 	    // clear window
477 	    glClearColor(0, 0, 0, 0);
478 	    glClear(glNVGClearFlags); // use NanoVega API to get flags for OpenGL call
479 
480 	    {
481 	      nvg.beginFrame(sdmain.width, sdmain.height); // begin rendering
482 	      scope(exit) nvg.endFrame(); // and flush render queue on exit
483 
484 	      nvg.beginPath(); // start new path
485 	      nvg.roundedRect(20.5, 30.5, sdmain.width-40, sdmain.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation)
486 	      // now set filling mode for our rectangle
487 	      // you can create colors using HTML syntax, or with convenient constants
488 	      nvg.fillPaint = nvg.linearGradient(20.5, 30.5, sdmain.width-40, sdmain.height-60, NVGColor("#f70"), NVGColor.green);
489 	      // now fill our rect
490 	      nvg.fill();
491 	      // and draw a nice outline
492 	      nvg.strokeColor = NVGColor.white;
493 	      nvg.strokeWidth = 2;
494 	      nvg.stroke();
495 	      // that's all, folks!
496 	    }
497 	  };
498 
499 	  sdmain.eventLoop(0, // no pulse timer required
500 	    delegate (KeyEvent event) {
501 	      if (event == "*-Q" || event == "Escape") { sdmain.close(); return; } // quit on Q, Ctrl+Q, and so on
502 	    },
503 	  );
504 
505 	  flushGui(); // let OS do it's cleanup
506 	}
507 }
508 
509 private:
510 
511 version(aliced) {
512   import iv.meta;
513   import iv.vfs;
514 } else {
515   private alias usize = size_t;
516   // i fear phobos!
517   private template Unqual(T) {
518          static if (is(T U ==          immutable U)) alias Unqual = U;
519     else static if (is(T U == shared inout const U)) alias Unqual = U;
520     else static if (is(T U == shared inout       U)) alias Unqual = U;
521     else static if (is(T U == shared       const U)) alias Unqual = U;
522     else static if (is(T U == shared             U)) alias Unqual = U;
523     else static if (is(T U ==        inout const U)) alias Unqual = U;
524     else static if (is(T U ==        inout       U)) alias Unqual = U;
525     else static if (is(T U ==              const U)) alias Unqual = U;
526     else alias Unqual = T;
527   }
528   private template isAnyCharType(T, bool unqual=false) {
529     static if (unqual) private alias UT = Unqual!T; else private alias UT = T;
530     enum isAnyCharType = is(UT == char) || is(UT == wchar) || is(UT == dchar);
531   }
532   private template isWideCharType(T, bool unqual=false) {
533     static if (unqual) private alias UT = Unqual!T; else private alias UT = T;
534     enum isWideCharType = is(UT == wchar) || is(UT == dchar);
535   }
536 }
537 version(nanovg_disable_vfs) {
538   enum NanoVegaHasIVVFS = false;
539 } else {
540   static if (is(typeof((){import iv.vfs;}))) {
541     enum NanoVegaHasIVVFS = true;
542     import iv.vfs;
543   } else {
544     enum NanoVegaHasIVVFS = false;
545   }
546 }
547 
548 // ////////////////////////////////////////////////////////////////////////// //
549 // engine
550 // ////////////////////////////////////////////////////////////////////////// //
551 import core.stdc.stdlib : malloc, realloc, free;
552 import core.stdc.string : memset, memcpy, strlen;
553 import std.math : PI;
554 
555 //version = nanovg_force_stb_ttf;
556 
557 version(Posix) {
558   version = nanovg_use_freetype;
559 } else {
560   version = nanovg_disable_fontconfig;
561 }
562 
563 version (bindbc) {
564   version = nanovg_builtin_fontconfig_bindings;
565   version = nanovg_bindbc_opengl_bindings;
566   version = nanovg_bindbc_freetype_bindings;
567   version(BindFT_Dynamic)
568     static assert(0, "AsumFace was too lazy to write the code for the dynamic bindbc freetype bindings");
569   else {
570     version(BindFT_Static) {}
571     else
572       static assert(0, "well, duh. you got to pass the BindFT_Static version identifier to the compiler");
573   }
574 } else version(aliced) {
575   version = nanovg_default_no_font_aa;
576   version = nanovg_builtin_fontconfig_bindings;
577   version = nanovg_builtin_freetype_bindings;
578   version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings
579 } else {
580   version (neverHave_bindbc_opengl)
581     version = nanovg_bindbc_opengl_bindings;
582    else
583     version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings
584   version (neverHave_bindbc_freetype)
585     version = nanovg_bindbc_freetype_bindings;
586    else
587     version = nanovg_builtin_freetype_bindings;
588   version = nanovg_builtin_fontconfig_bindings;
589 }
590 
591 version(nanovg_disable_fontconfig) {
592   public enum NanoVegaHasFontConfig = false;
593 } else {
594   public enum NanoVegaHasFontConfig = true;
595   version(nanovg_builtin_fontconfig_bindings) {} else import iv.fontconfig;
596 }
597 
598 //version = nanovg_bench_flatten;
599 
600 /++
601 	Annotation to indicate the marked function is compatible with [arsd.script].
602 
603 
604 	Any function that takes a [Color] argument will be passed a string instead.
605 
606 	Scriptable Functions
607 	====================
608 
609 	$(UDA_USES)
610 
611 	$(ALWAYS_DOCUMENT)
612 +/
613 private enum scriptable = "arsd_jsvar_compatible";
614 
615 public:
616 alias NVG_PI = PI;
617 
618 enum NanoVegaHasArsdColor = (is(typeof((){ import arsd.color; })));
619 enum NanoVegaHasArsdImage = (is(typeof((){ import arsd.color; import arsd.image; })));
620 
621 static if (NanoVegaHasArsdColor) private import arsd.color;
622 static if (NanoVegaHasArsdImage) {
623   private import arsd.image;
624 } else {
625   void stbi_set_unpremultiply_on_load (int flag_true_if_should_unpremultiply) {}
626   void stbi_convert_iphone_png_to_rgb (int flag_true_if_should_convert) {}
627   ubyte* stbi_load (const(char)* filename, int* x, int* y, int* comp, int req_comp) { return null; }
628   ubyte* stbi_load_from_memory (const(void)* buffer, int len, int* x, int* y, int* comp, int req_comp) { return null; }
629   void stbi_image_free (void* retval_from_stbi_load) {}
630 }
631 
632 version(nanovg_default_no_font_aa) {
633   __gshared bool NVG_INVERT_FONT_AA = false;
634 } else {
635   __gshared bool NVG_INVERT_FONT_AA = true;
636 }
637 
638 
639 /// this is branchless for ints on x86, and even for longs on x86_64
640 public ubyte nvgClampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) {
641   static if (__VERSION__ > 2067) pragma(inline, true);
642   static if (T.sizeof == 2 || T.sizeof == 4) {
643     static if (__traits(isUnsigned, T)) {
644       return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24)));
645     } else {
646       n &= -cast(int)(n >= 0);
647       return cast(ubyte)(n|((255-cast(int)n)>>31));
648     }
649   } else static if (T.sizeof == 1) {
650     static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?");
651     return cast(ubyte)n;
652   } else static if (T.sizeof == 8) {
653     static if (__traits(isUnsigned, T)) {
654       return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56)));
655     } else {
656       n &= -cast(long)(n >= 0);
657       return cast(ubyte)(n|((255-cast(long)n)>>63));
658     }
659   } else {
660     static assert(false, "clampToByte: integer too big");
661   }
662 }
663 
664 
665 /// NanoVega RGBA color
666 /// Group: color_utils
667 public align(1) struct NVGColor {
668 align(1):
669 public:
670   float[4] rgba = 0; /// default color is transparent (a=1 is opaque)
671 
672 public:
673   @property string toString () const @safe { import std.string : format; return "NVGColor(%s,%s,%s,%s)".format(r, g, b, a); }
674 
675 public:
676   enum transparent = NVGColor(0.0f, 0.0f, 0.0f, 0.0f);
677   enum k8orange = NVGColor(1.0f, 0.5f, 0.0f, 1.0f);
678 
679   enum aliceblue = NVGColor(240, 248, 255);
680   enum antiquewhite = NVGColor(250, 235, 215);
681   enum aqua = NVGColor(0, 255, 255);
682   enum aquamarine = NVGColor(127, 255, 212);
683   enum azure = NVGColor(240, 255, 255);
684   enum beige = NVGColor(245, 245, 220);
685   enum bisque = NVGColor(255, 228, 196);
686   enum black = NVGColor(0, 0, 0); // basic color
687   enum blanchedalmond = NVGColor(255, 235, 205);
688   enum blue = NVGColor(0, 0, 255); // basic color
689   enum blueviolet = NVGColor(138, 43, 226);
690   enum brown = NVGColor(165, 42, 42);
691   enum burlywood = NVGColor(222, 184, 135);
692   enum cadetblue = NVGColor(95, 158, 160);
693   enum chartreuse = NVGColor(127, 255, 0);
694   enum chocolate = NVGColor(210, 105, 30);
695   enum coral = NVGColor(255, 127, 80);
696   enum cornflowerblue = NVGColor(100, 149, 237);
697   enum cornsilk = NVGColor(255, 248, 220);
698   enum crimson = NVGColor(220, 20, 60);
699   enum cyan = NVGColor(0, 255, 255); // basic color
700   enum darkblue = NVGColor(0, 0, 139);
701   enum darkcyan = NVGColor(0, 139, 139);
702   enum darkgoldenrod = NVGColor(184, 134, 11);
703   enum darkgray = NVGColor(169, 169, 169);
704   enum darkgreen = NVGColor(0, 100, 0);
705   enum darkgrey = NVGColor(169, 169, 169);
706   enum darkkhaki = NVGColor(189, 183, 107);
707   enum darkmagenta = NVGColor(139, 0, 139);
708   enum darkolivegreen = NVGColor(85, 107, 47);
709   enum darkorange = NVGColor(255, 140, 0);
710   enum darkorchid = NVGColor(153, 50, 204);
711   enum darkred = NVGColor(139, 0, 0);
712   enum darksalmon = NVGColor(233, 150, 122);
713   enum darkseagreen = NVGColor(143, 188, 143);
714   enum darkslateblue = NVGColor(72, 61, 139);
715   enum darkslategray = NVGColor(47, 79, 79);
716   enum darkslategrey = NVGColor(47, 79, 79);
717   enum darkturquoise = NVGColor(0, 206, 209);
718   enum darkviolet = NVGColor(148, 0, 211);
719   enum deeppink = NVGColor(255, 20, 147);
720   enum deepskyblue = NVGColor(0, 191, 255);
721   enum dimgray = NVGColor(105, 105, 105);
722   enum dimgrey = NVGColor(105, 105, 105);
723   enum dodgerblue = NVGColor(30, 144, 255);
724   enum firebrick = NVGColor(178, 34, 34);
725   enum floralwhite = NVGColor(255, 250, 240);
726   enum forestgreen = NVGColor(34, 139, 34);
727   enum fuchsia = NVGColor(255, 0, 255);
728   enum gainsboro = NVGColor(220, 220, 220);
729   enum ghostwhite = NVGColor(248, 248, 255);
730   enum gold = NVGColor(255, 215, 0);
731   enum goldenrod = NVGColor(218, 165, 32);
732   enum gray = NVGColor(128, 128, 128); // basic color
733   enum green = NVGColor(0, 128, 0); // basic color
734   enum greenyellow = NVGColor(173, 255, 47);
735   enum grey = NVGColor(128, 128, 128); // basic color
736   enum honeydew = NVGColor(240, 255, 240);
737   enum hotpink = NVGColor(255, 105, 180);
738   enum indianred = NVGColor(205, 92, 92);
739   enum indigo = NVGColor(75, 0, 130);
740   enum ivory = NVGColor(255, 255, 240);
741   enum khaki = NVGColor(240, 230, 140);
742   enum lavender = NVGColor(230, 230, 250);
743   enum lavenderblush = NVGColor(255, 240, 245);
744   enum lawngreen = NVGColor(124, 252, 0);
745   enum lemonchiffon = NVGColor(255, 250, 205);
746   enum lightblue = NVGColor(173, 216, 230);
747   enum lightcoral = NVGColor(240, 128, 128);
748   enum lightcyan = NVGColor(224, 255, 255);
749   enum lightgoldenrodyellow = NVGColor(250, 250, 210);
750   enum lightgray = NVGColor(211, 211, 211);
751   enum lightgreen = NVGColor(144, 238, 144);
752   enum lightgrey = NVGColor(211, 211, 211);
753   enum lightpink = NVGColor(255, 182, 193);
754   enum lightsalmon = NVGColor(255, 160, 122);
755   enum lightseagreen = NVGColor(32, 178, 170);
756   enum lightskyblue = NVGColor(135, 206, 250);
757   enum lightslategray = NVGColor(119, 136, 153);
758   enum lightslategrey = NVGColor(119, 136, 153);
759   enum lightsteelblue = NVGColor(176, 196, 222);
760   enum lightyellow = NVGColor(255, 255, 224);
761   enum lime = NVGColor(0, 255, 0);
762   enum limegreen = NVGColor(50, 205, 50);
763   enum linen = NVGColor(250, 240, 230);
764   enum magenta = NVGColor(255, 0, 255); // basic color
765   enum maroon = NVGColor(128, 0, 0);
766   enum mediumaquamarine = NVGColor(102, 205, 170);
767   enum mediumblue = NVGColor(0, 0, 205);
768   enum mediumorchid = NVGColor(186, 85, 211);
769   enum mediumpurple = NVGColor(147, 112, 219);
770   enum mediumseagreen = NVGColor(60, 179, 113);
771   enum mediumslateblue = NVGColor(123, 104, 238);
772   enum mediumspringgreen = NVGColor(0, 250, 154);
773   enum mediumturquoise = NVGColor(72, 209, 204);
774   enum mediumvioletred = NVGColor(199, 21, 133);
775   enum midnightblue = NVGColor(25, 25, 112);
776   enum mintcream = NVGColor(245, 255, 250);
777   enum mistyrose = NVGColor(255, 228, 225);
778   enum moccasin = NVGColor(255, 228, 181);
779   enum navajowhite = NVGColor(255, 222, 173);
780   enum navy = NVGColor(0, 0, 128);
781   enum oldlace = NVGColor(253, 245, 230);
782   enum olive = NVGColor(128, 128, 0);
783   enum olivedrab = NVGColor(107, 142, 35);
784   enum orange = NVGColor(255, 165, 0);
785   enum orangered = NVGColor(255, 69, 0);
786   enum orchid = NVGColor(218, 112, 214);
787   enum palegoldenrod = NVGColor(238, 232, 170);
788   enum palegreen = NVGColor(152, 251, 152);
789   enum paleturquoise = NVGColor(175, 238, 238);
790   enum palevioletred = NVGColor(219, 112, 147);
791   enum papayawhip = NVGColor(255, 239, 213);
792   enum peachpuff = NVGColor(255, 218, 185);
793   enum peru = NVGColor(205, 133, 63);
794   enum pink = NVGColor(255, 192, 203);
795   enum plum = NVGColor(221, 160, 221);
796   enum powderblue = NVGColor(176, 224, 230);
797   enum purple = NVGColor(128, 0, 128);
798   enum red = NVGColor(255, 0, 0); // basic color
799   enum rosybrown = NVGColor(188, 143, 143);
800   enum royalblue = NVGColor(65, 105, 225);
801   enum saddlebrown = NVGColor(139, 69, 19);
802   enum salmon = NVGColor(250, 128, 114);
803   enum sandybrown = NVGColor(244, 164, 96);
804   enum seagreen = NVGColor(46, 139, 87);
805   enum seashell = NVGColor(255, 245, 238);
806   enum sienna = NVGColor(160, 82, 45);
807   enum silver = NVGColor(192, 192, 192);
808   enum skyblue = NVGColor(135, 206, 235);
809   enum slateblue = NVGColor(106, 90, 205);
810   enum slategray = NVGColor(112, 128, 144);
811   enum slategrey = NVGColor(112, 128, 144);
812   enum snow = NVGColor(255, 250, 250);
813   enum springgreen = NVGColor(0, 255, 127);
814   enum steelblue = NVGColor(70, 130, 180);
815   enum tan = NVGColor(210, 180, 140);
816   enum teal = NVGColor(0, 128, 128);
817   enum thistle = NVGColor(216, 191, 216);
818   enum tomato = NVGColor(255, 99, 71);
819   enum turquoise = NVGColor(64, 224, 208);
820   enum violet = NVGColor(238, 130, 238);
821   enum wheat = NVGColor(245, 222, 179);
822   enum white = NVGColor(255, 255, 255); // basic color
823   enum whitesmoke = NVGColor(245, 245, 245);
824   enum yellow = NVGColor(255, 255, 0); // basic color
825   enum yellowgreen = NVGColor(154, 205, 50);
826 
827 nothrow @safe @nogc:
828 public:
829   ///
830   this (ubyte ar, ubyte ag, ubyte ab, ubyte aa=255) pure {
831     pragma(inline, true);
832     r = ar/255.0f;
833     g = ag/255.0f;
834     b = ab/255.0f;
835     a = aa/255.0f;
836   }
837 
838   ///
839   this (float ar, float ag, float ab, float aa=1.0f) pure {
840     pragma(inline, true);
841     r = ar;
842     g = ag;
843     b = ab;
844     a = aa;
845   }
846 
847   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
848   this (uint c) pure {
849     pragma(inline, true);
850     r = (c&0xff)/255.0f;
851     g = ((c>>8)&0xff)/255.0f;
852     b = ((c>>16)&0xff)/255.0f;
853     a = ((c>>24)&0xff)/255.0f;
854   }
855 
856   /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb"
857   this (const(char)[] srgb) {
858     static int c2d (char ch) pure nothrow @safe @nogc {
859       pragma(inline, true);
860       return
861         ch >= '0' && ch <= '9' ? ch-'0' :
862         ch >= 'A' && ch <= 'F' ? ch-'A'+10 :
863         ch >= 'a' && ch <= 'f' ? ch-'a'+10 :
864         -1;
865     }
866     int[8] digs;
867     int dc = -1;
868     foreach (immutable char ch; srgb) {
869       if (ch <= ' ') continue;
870       if (ch == '#') {
871         if (dc != -1) { dc = -1; break; }
872         dc = 0;
873       } else {
874         if (dc >= digs.length) { dc = -1; break; }
875         if ((digs[dc++] = c2d(ch)) < 0) { dc = -1; break; }
876       }
877     }
878     switch (dc) {
879       case 3: // rgb
880         a = 1.0f;
881         r = digs[0]/15.0f;
882         g = digs[1]/15.0f;
883         b = digs[2]/15.0f;
884         break;
885       case 4: // argb
886         a = digs[0]/15.0f;
887         r = digs[1]/15.0f;
888         g = digs[2]/15.0f;
889         b = digs[3]/15.0f;
890         break;
891       case 6: // rrggbb
892         a = 1.0f;
893         r = (digs[0]*16+digs[1])/255.0f;
894         g = (digs[2]*16+digs[3])/255.0f;
895         b = (digs[4]*16+digs[5])/255.0f;
896         break;
897       case 8: // aarrggbb
898         a = (digs[0]*16+digs[1])/255.0f;
899         r = (digs[2]*16+digs[3])/255.0f;
900         g = (digs[4]*16+digs[5])/255.0f;
901         b = (digs[6]*16+digs[7])/255.0f;
902         break;
903       default:
904         break;
905     }
906   }
907 
908   /// Is this color completely opaque?
909   @property bool isOpaque () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] >= 1.0f); }
910   /// Is this color completely transparent?
911   @property bool isTransparent () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] <= 0.0f); }
912 
913   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
914   @property uint asUint () const pure {
915     pragma(inline, true);
916     return
917       cast(uint)(r*255)|
918       (cast(uint)(g*255)<<8)|
919       (cast(uint)(b*255)<<16)|
920       (cast(uint)(a*255)<<24);
921   }
922 
923   alias asUintABGR = asUint; /// Ditto.
924 
925   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
926   static NVGColor fromUint (uint c) pure { pragma(inline, true); return NVGColor(c); }
927 
928   alias fromUintABGR = fromUint; /// Ditto.
929 
930   /// AARRGGBB
931   @property uint asUintARGB () const pure {
932     pragma(inline, true);
933     return
934       cast(uint)(b*255)|
935       (cast(uint)(g*255)<<8)|
936       (cast(uint)(r*255)<<16)|
937       (cast(uint)(a*255)<<24);
938   }
939 
940   /// AARRGGBB
941   static NVGColor fromUintARGB (uint c) pure { pragma(inline, true); return NVGColor((c>>16)&0xff, (c>>8)&0xff, c&0xff, (c>>24)&0xff); }
942 
943   @property ref inout(float) r () inout pure @trusted { pragma(inline, true); return rgba.ptr[0]; } ///
944   @property ref inout(float) g () inout pure @trusted { pragma(inline, true); return rgba.ptr[1]; } ///
945   @property ref inout(float) b () inout pure @trusted { pragma(inline, true); return rgba.ptr[2]; } ///
946   @property ref inout(float) a () inout pure @trusted { pragma(inline, true); return rgba.ptr[3]; } ///
947 
948   ref NVGColor applyTint() (const scope auto ref NVGColor tint) nothrow @trusted @nogc {
949     if (tint.a == 0) return this;
950     foreach (immutable idx, ref float v; rgba[0..4]) {
951       v = nvg__clamp(v*tint.rgba.ptr[idx], 0.0f, 1.0f);
952     }
953     return this;
954   }
955 
956   NVGHSL asHSL() (bool useWeightedLightness=false) const { pragma(inline, true); return NVGHSL.fromColor(this, useWeightedLightness); } ///
957   static fromHSL() (const scope auto ref NVGHSL hsl) { pragma(inline, true); return hsl.asColor; } ///
958 
959   static if (NanoVegaHasArsdColor) {
960     Color toArsd () const { pragma(inline, true); return Color(cast(int)(r*255), cast(int)(g*255), cast(int)(b*255), cast(int)(a*255)); } ///
961     static NVGColor fromArsd (in Color c) { pragma(inline, true); return NVGColor(c.r, c.g, c.b, c.a); } ///
962     ///
963     this (in Color c) {
964       version(aliced) pragma(inline, true);
965       r = c.r/255.0f;
966       g = c.g/255.0f;
967       b = c.b/255.0f;
968       a = c.a/255.0f;
969     }
970   }
971 }
972 
973 
974 /// NanoVega A-HSL color
975 /// Group: color_utils
976 public align(1) struct NVGHSL {
977 align(1):
978   float h=0, s=0, l=1, a=1; ///
979 
980   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)); }
981 
982 nothrow @safe @nogc:
983 public:
984   ///
985   this (float ah, float as, float al, float aa=1) pure { pragma(inline, true); h = ah; s = as; l = al; a = aa; }
986 
987   NVGColor asColor () const { pragma(inline, true); return nvgHSLA(h, s, l, a); } ///
988 
989   // taken from Adam's arsd.color
990   /** Converts an RGB color into an HSL triplet.
991    * [useWeightedLightness] will try to get a better value for luminosity for the human eye,
992    * which is more sensitive to green than red and more to red than blue.
993    * If it is false, it just does average of the rgb. */
994   static NVGHSL fromColor() (const scope auto ref NVGColor c, bool useWeightedLightness=false) pure {
995     NVGHSL res;
996     res.a = c.a;
997     float r1 = c.r;
998     float g1 = c.g;
999     float b1 = c.b;
1000 
1001     float maxColor = r1;
1002     if (g1 > maxColor) maxColor = g1;
1003     if (b1 > maxColor) maxColor = b1;
1004     float minColor = r1;
1005     if (g1 < minColor) minColor = g1;
1006     if (b1 < minColor) minColor = b1;
1007 
1008     res.l = (maxColor+minColor)/2;
1009     if (useWeightedLightness) {
1010       // the colors don't affect the eye equally
1011       // this is a little more accurate than plain HSL numbers
1012       res.l = 0.2126*r1+0.7152*g1+0.0722*b1;
1013     }
1014     if (maxColor != minColor) {
1015       if (res.l < 0.5) {
1016         res.s = (maxColor-minColor)/(maxColor+minColor);
1017       } else {
1018         res.s = (maxColor-minColor)/(2.0-maxColor-minColor);
1019       }
1020       if (r1 == maxColor) {
1021         res.h = (g1-b1)/(maxColor-minColor);
1022       } else if(g1 == maxColor) {
1023         res.h = 2.0+(b1-r1)/(maxColor-minColor);
1024       } else {
1025         res.h = 4.0+(r1-g1)/(maxColor-minColor);
1026       }
1027     }
1028 
1029     res.h = res.h*60;
1030     if (res.h < 0) res.h += 360;
1031     res.h /= 360;
1032 
1033     return res;
1034   }
1035 }
1036 
1037 
1038 //version = nanovega_debug_image_manager;
1039 //version = nanovega_debug_image_manager_rc;
1040 
1041 /** NanoVega image handle.
1042  *
1043  * This is refcounted struct, so you don't need to do anything special to free it once it is allocated.
1044  *
1045  * Group: images
1046  */
1047 struct NVGImage {
1048 	enum isOpaqueStruct = true;
1049 private:
1050   NVGContext ctx;
1051   int id; // backend image id
1052 
1053 public:
1054   ///
1055   this() (const scope auto ref NVGImage src) nothrow @trusted @nogc {
1056     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); }
1057     if (src.id > 0 && src.ctx !is null) {
1058       ctx = cast(NVGContext)src.ctx;
1059       id = src.id;
1060       ctx.nvg__imageIncRef(id);
1061     }
1062   }
1063 
1064   ///
1065   ~this () nothrow @trusted @nogc { version(aliced) pragma(inline, true); clear(); }
1066 
1067   ///
1068   this (this) nothrow @trusted @nogc {
1069     version(aliced) pragma(inline, true);
1070     if (id > 0 && ctx !is null) {
1071       version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p postblit (imgid=%d)\n", &this, id); }
1072       ctx.nvg__imageIncRef(id);
1073     }
1074   }
1075 
1076   ///
1077   void opAssign() (const scope auto ref NVGImage src) nothrow @trusted @nogc {
1078     if (src.id <= 0 || src.ctx is null) {
1079       clear();
1080     } else {
1081       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); }
1082       if (src.id > 0 && src.ctx !is null) (cast(NVGContext)src.ctx).nvg__imageIncRef(src.id);
1083       if (id > 0 && ctx !is null) ctx.nvg__imageDecRef(id);
1084       ctx = cast(NVGContext)src.ctx;
1085       id = src.id;
1086     }
1087   }
1088 
1089   /// Free this image.
1090   void clear () nothrow @trusted @nogc {
1091     if (id > 0 && ctx !is null) {
1092       version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p cleared (imgid=%d)\n", &this, id); }
1093       ctx.nvg__imageDecRef(id);
1094     }
1095     id = 0;
1096     ctx = null;
1097   }
1098 
1099   /// Is this image valid?
1100   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (id > 0 && ctx.valid); }
1101 
1102   /// Is the given image valid and comes from the same context?
1103   @property bool isSameContext (const(NVGContext) actx) const pure nothrow @safe @nogc { pragma(inline, true); return (actx !is null && ctx is actx); }
1104 
1105   /// Returns image width, or zero for invalid image.
1106   int width () const nothrow @trusted @nogc {
1107     int w = 0;
1108     if (valid) {
1109       int h = void;
1110       ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h);
1111     }
1112     return w;
1113   }
1114 
1115   /// Returns image height, or zero for invalid image.
1116   int height () const nothrow @trusted @nogc {
1117     int h = 0;
1118     if (valid) {
1119       int w = void;
1120       ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h);
1121     }
1122     return h;
1123   }
1124 }
1125 
1126 
1127 /// Paint parameters for various fills. Don't change anything here!
1128 /// Group: render_styles
1129 public struct NVGPaint {
1130   enum isOpaqueStruct = true;
1131 
1132   NVGMatrix xform;
1133   float[2] extent = 0.0f;
1134   float radius = 0.0f;
1135   float feather = 0.0f;
1136   NVGColor innerColor; /// this can be used to modulate images (fill/font)
1137   NVGColor middleColor;
1138   NVGColor outerColor;
1139   float midp = -1; // middle stop for 3-color gradient
1140   NVGImage image;
1141   bool simpleColor; /// if `true`, only innerColor is used, and this is solid-color paint
1142 
1143   this() (const scope auto ref NVGPaint p) nothrow @trusted @nogc {
1144     xform = p.xform;
1145     extent[] = p.extent[];
1146     radius = p.radius;
1147     feather = p.feather;
1148     innerColor = p.innerColor;
1149     middleColor = p.middleColor;
1150     midp = p.midp;
1151     outerColor = p.outerColor;
1152     image = p.image;
1153     simpleColor = p.simpleColor;
1154   }
1155 
1156   void opAssign() (const scope auto ref NVGPaint p) nothrow @trusted @nogc {
1157     xform = p.xform;
1158     extent[] = p.extent[];
1159     radius = p.radius;
1160     feather = p.feather;
1161     innerColor = p.innerColor;
1162     middleColor = p.middleColor;
1163     midp = p.midp;
1164     outerColor = p.outerColor;
1165     image = p.image;
1166     simpleColor = p.simpleColor;
1167   }
1168 
1169   void clear () nothrow @trusted @nogc {
1170     version(aliced) pragma(inline, true);
1171     import core.stdc.string : memset;
1172     image.clear();
1173     memset(&this, 0, this.sizeof);
1174     simpleColor = true;
1175   }
1176 }
1177 
1178 /// Path winding.
1179 /// Group: paths
1180 public enum NVGWinding {
1181   CCW = 1, /// Winding for solid shapes
1182   CW = 2,  /// Winding for holes
1183 }
1184 
1185 /// Path solidity.
1186 /// Group: paths
1187 public enum NVGSolidity {
1188   Solid = 1, /// Solid shape (CCW winding).
1189   Hole = 2, /// Hole (CW winding).
1190 }
1191 
1192 /// Line cap style.
1193 /// Group: render_styles
1194 public enum NVGLineCap {
1195   Butt, ///
1196   Round, ///
1197   Square, ///
1198   Bevel, ///
1199   Miter, ///
1200 }
1201 
1202 /// Text align.
1203 /// Group: text_api
1204 public align(1) struct NVGTextAlign {
1205 align(1):
1206   /// Horizontal align.
1207   enum H : ubyte {
1208     Left   = 0, /// Default, align text horizontally to left.
1209     Center = 1, /// Align text horizontally to center.
1210     Right  = 2, /// Align text horizontally to right.
1211   }
1212 
1213   /// Vertical align.
1214   enum V : ubyte {
1215     Baseline = 0, /// Default, align text vertically to baseline.
1216     Top      = 1, /// Align text vertically to top.
1217     Middle   = 2, /// Align text vertically to middle.
1218     Bottom   = 3, /// Align text vertically to bottom.
1219   }
1220 
1221 pure nothrow @safe @nogc:
1222 public:
1223   this (H h) { pragma(inline, true); value = h; } ///
1224   this (V v) { pragma(inline, true); value = cast(ubyte)(v<<4); } ///
1225   this (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1226   this (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1227   void reset () { pragma(inline, true); value = 0; } ///
1228   void reset (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1229   void reset (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1230 @property:
1231   bool left () const { pragma(inline, true); return ((value&0x0f) == H.Left); } ///
1232   void left (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Left : 0)); } ///
1233   bool center () const { pragma(inline, true); return ((value&0x0f) == H.Center); } ///
1234   void center (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Center : 0)); } ///
1235   bool right () const { pragma(inline, true); return ((value&0x0f) == H.Right); } ///
1236   void right (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Right : 0)); } ///
1237   //
1238   bool baseline () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Baseline); } ///
1239   void baseline (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Baseline<<4 : 0)); } ///
1240   bool top () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Top); } ///
1241   void top (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Top<<4 : 0)); } ///
1242   bool middle () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Middle); } ///
1243   void middle (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Middle<<4 : 0)); } ///
1244   bool bottom () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Bottom); } ///
1245   void bottom (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Bottom<<4 : 0)); } ///
1246   //
1247   H horizontal () const { pragma(inline, true); return cast(H)(value&0x0f); } ///
1248   void horizontal (H v) { pragma(inline, true); value = (value&0xf0)|v; } ///
1249   //
1250   V vertical () const { pragma(inline, true); return cast(V)((value>>4)&0x0f); } ///
1251   void vertical (V v) { pragma(inline, true); value = (value&0x0f)|cast(ubyte)(v<<4); } ///
1252   //
1253 private:
1254   ubyte value = 0; // low nibble: horizontal; high nibble: vertical
1255 }
1256 
1257 /// Blending type.
1258 /// Group: composite_operation
1259 public enum NVGBlendFactor {
1260   Zero = 1<<0, ///
1261   One = 1<<1, ///
1262   SrcColor = 1<<2, ///
1263   OneMinusSrcColor = 1<<3, ///
1264   DstColor = 1<<4, ///
1265   OneMinusDstColor = 1<<5, ///
1266   SrcAlpha = 1<<6, ///
1267   OneMinusSrcAlpha = 1<<7, ///
1268   DstAlpha = 1<<8, ///
1269   OneMinusDstAlpha = 1<<9, ///
1270   SrcAlphaSaturate = 1<<10, ///
1271 }
1272 
1273 /// Composite operation (HTML5-alike).
1274 /// Group: composite_operation
1275 public enum NVGCompositeOperation {
1276   SourceOver, ///
1277   SourceIn, ///
1278   SourceOut, ///
1279   SourceAtop, ///
1280   DestinationOver, ///
1281   DestinationIn, ///
1282   DestinationOut, ///
1283   DestinationAtop, ///
1284   Lighter, ///
1285   Copy, ///
1286   Xor, ///
1287 }
1288 
1289 /// Composite operation state.
1290 /// Group: composite_operation
1291 public struct NVGCompositeOperationState {
1292   bool simple; /// `true`: use `glBlendFunc()` instead of `glBlendFuncSeparate()`
1293   NVGBlendFactor srcRGB; ///
1294   NVGBlendFactor dstRGB; ///
1295   NVGBlendFactor srcAlpha; ///
1296   NVGBlendFactor dstAlpha; ///
1297 }
1298 
1299 /// Mask combining more
1300 /// Group: clipping
1301 public enum NVGClipMode {
1302   None, /// normal rendering (i.e. render path instead of modifying clip region)
1303   Union, /// old mask will be masked with the current one; this is the default mode for [clip]
1304   Or, /// new mask will be added to the current one (logical `OR` operation);
1305   Xor, /// new mask will be logically `XOR`ed with the current one
1306   Sub, /// "subtract" current path from mask
1307   Replace, /// replace current mask
1308   Add = Or, /// Synonym
1309 }
1310 
1311 /// Glyph position info.
1312 /// Group: text_api
1313 public struct NVGGlyphPosition {
1314   usize strpos;     /// Position of the glyph in the input string.
1315   float x;          /// The x-coordinate of the logical glyph position.
1316   float minx, maxx; /// The bounds of the glyph shape.
1317 }
1318 
1319 /// Text row storage.
1320 /// Group: text_api
1321 public struct NVGTextRow(CT) if (isAnyCharType!CT) {
1322   alias CharType = CT;
1323   const(CT)[] s;
1324   int start;        /// Index in the input text where the row starts.
1325   int end;          /// Index in the input text where the row ends (one past the last character).
1326   float width;      /// Logical width of the row.
1327   float minx, maxx; /// Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending.
1328   /// Get rest of the string.
1329   @property const(CT)[] rest () const pure nothrow @trusted @nogc { pragma(inline, true); return (end <= s.length ? s[end..$] : null); }
1330   /// Get current row.
1331   @property const(CT)[] row () const pure nothrow @trusted @nogc { pragma(inline, true); return s[start..end]; }
1332   @property const(CT)[] string () const pure nothrow @trusted @nogc { pragma(inline, true); return s; }
1333   @property void string(CT) (const(CT)[] v) pure nothrow @trusted @nogc { pragma(inline, true); s = v; }
1334 }
1335 
1336 /// Image creation flags.
1337 /// Group: images
1338 public enum NVGImageFlag : uint {
1339   None            =    0, /// Nothing special.
1340   GenerateMipmaps = 1<<0, /// Generate mipmaps during creation of the image.
1341   RepeatX         = 1<<1, /// Repeat image in X direction.
1342   RepeatY         = 1<<2, /// Repeat image in Y direction.
1343   FlipY           = 1<<3, /// Flips (inverses) image in Y direction when rendered.
1344   Premultiplied   = 1<<4, /// Image data has premultiplied alpha.
1345   ClampToBorderX  = 1<<5, /// Clamp image to border (instead of clamping to edge by default)
1346   ClampToBorderY  = 1<<6, /// Clamp image to border (instead of clamping to edge by default)
1347   NoFiltering     = 1<<8, /// use GL_NEAREST instead of GL_LINEAR. Only affects upscaling if GenerateMipmaps is active.
1348   Nearest = NoFiltering,  /// compatibility with original NanoVG
1349   NoDelete        = 1<<16,/// Do not delete GL texture handle.
1350 }
1351 
1352 alias NVGImageFlags = NVGImageFlag; /// Backwards compatibility for [NVGImageFlag].
1353 
1354 
1355 // ////////////////////////////////////////////////////////////////////////// //
1356 private:
1357 
1358 static T* xdup(T) (const(T)* ptr, int count) nothrow @trusted @nogc {
1359   import core.stdc.stdlib : malloc;
1360   import core.stdc.string : memcpy;
1361   if (count == 0) return null;
1362   T* res = cast(T*)malloc(T.sizeof*count);
1363   if (res is null) assert(0, "NanoVega: out of memory");
1364   memcpy(res, ptr, T.sizeof*count);
1365   return res;
1366 }
1367 
1368 // Internal Render API
1369 enum NVGtexture {
1370   Alpha = 0x01,
1371   RGBA  = 0x02,
1372 }
1373 
1374 struct NVGscissor {
1375   NVGMatrix xform;
1376   float[2] extent = -1.0f;
1377 }
1378 
1379 /// General NanoVega vertex struct. Contains geometry coordinates, and (sometimes unused) texture coordinates.
1380 public struct NVGVertex {
1381   float x, y, u, v;
1382 }
1383 
1384 struct NVGpath {
1385   int first;
1386   int count;
1387   bool closed;
1388   int nbevel;
1389   NVGVertex* fill;
1390   int nfill;
1391   NVGVertex* stroke;
1392   int nstroke;
1393   NVGWinding mWinding;
1394   bool convex;
1395   bool cloned;
1396 
1397   @disable this (this); // no copies
1398   void opAssign() (const scope auto ref NVGpath a) { static assert(0, "no copies!"); }
1399 
1400   void clear () nothrow @trusted @nogc {
1401     import core.stdc.stdlib : free;
1402     import core.stdc.string : memset;
1403     if (cloned) {
1404       if (stroke !is null && stroke !is fill) free(stroke);
1405       if (fill !is null) free(fill);
1406     }
1407     memset(&this, 0, this.sizeof);
1408   }
1409 
1410   // won't clear current path
1411   void copyFrom (const NVGpath* src) nothrow @trusted @nogc {
1412     import core.stdc.string : memcpy;
1413     assert(src !is null);
1414     memcpy(&this, src, NVGpath.sizeof);
1415     this.fill = xdup(src.fill, src.nfill);
1416     if (src.stroke is src.fill) {
1417       this.stroke = this.fill;
1418     } else {
1419       this.stroke = xdup(src.stroke, src.nstroke);
1420     }
1421     this.cloned = true;
1422   }
1423 
1424   public @property const(NVGVertex)[] fillVertices () const pure nothrow @trusted @nogc {
1425     pragma(inline, true);
1426     return (nfill > 0 ? fill[0..nfill] : null);
1427   }
1428 
1429   public @property const(NVGVertex)[] strokeVertices () const pure nothrow @trusted @nogc {
1430     pragma(inline, true);
1431     return (nstroke > 0 ? stroke[0..nstroke] : null);
1432   }
1433 
1434   public @property NVGWinding winding () const pure nothrow @trusted @nogc { pragma(inline, true); return mWinding; }
1435   public @property bool complex () const pure nothrow @trusted @nogc { pragma(inline, true); return !convex; }
1436 }
1437 
1438 
1439 struct NVGparams {
1440   void* userPtr;
1441   bool edgeAntiAlias;
1442   bool fontAA;
1443   bool function (void* uptr) nothrow @trusted @nogc renderCreate;
1444   int function (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc renderCreateTexture;
1445   bool function (void* uptr, int image) nothrow @trusted @nogc renderTextureIncRef;
1446   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
1447   bool function (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc renderUpdateTexture;
1448   bool function (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc renderGetTextureSize;
1449   void function (void* uptr, int width, int height) nothrow @trusted @nogc renderViewport; // called in [beginFrame]
1450   void function (void* uptr) nothrow @trusted @nogc renderCancel;
1451   void function (void* uptr) nothrow @trusted @nogc renderFlush;
1452   void function (void* uptr) nothrow @trusted @nogc renderPushClip; // backend should support stack of at least [NVG_MAX_STATES] elements
1453   void function (void* uptr) nothrow @trusted @nogc renderPopClip; // backend should support stack of at least [NVG_MAX_STATES] elements
1454   void function (void* uptr) nothrow @trusted @nogc renderResetClip; // reset current clip region to `non-clipped`
1455   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;
1456   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;
1457   void function (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc renderTriangles;
1458   void function (void* uptr, const scope ref NVGMatrix mat) nothrow @trusted @nogc renderSetAffine;
1459   void function (void* uptr) nothrow @trusted @nogc renderDelete;
1460 }
1461 
1462 // ////////////////////////////////////////////////////////////////////////// //
1463 private:
1464 
1465 enum NVG_INIT_FONTIMAGE_SIZE = 512;
1466 enum NVG_MAX_FONTIMAGE_SIZE  = 2048;
1467 enum NVG_MAX_FONTIMAGES      = 4;
1468 
1469 enum NVG_INIT_COMMANDS_SIZE = 256;
1470 enum NVG_INIT_POINTS_SIZE   = 128;
1471 enum NVG_INIT_PATHS_SIZE    = 16;
1472 enum NVG_INIT_VERTS_SIZE    = 256;
1473 enum NVG_MAX_STATES         = 32;
1474 
1475 public enum NVG_KAPPA90 = 0.5522847493f; /// Length proportional to radius of a cubic bezier handle for 90deg arcs.
1476 enum NVG_MIN_FEATHER = 0.001f; // it should be greater than zero, 'cause it is used in shader for divisions
1477 
1478 enum Command {
1479   MoveTo = 0,
1480   LineTo = 1,
1481   BezierTo = 2,
1482   Close = 3,
1483   Winding = 4,
1484 }
1485 
1486 enum PointFlag : int {
1487   Corner = 0x01,
1488   Left = 0x02,
1489   Bevel = 0x04,
1490   InnerBevelPR = 0x08,
1491 }
1492 
1493 struct NVGstate {
1494   NVGCompositeOperationState compositeOperation;
1495   bool shapeAntiAlias = true;
1496   NVGPaint fill;
1497   NVGPaint stroke;
1498   float strokeWidth = 1.0f;
1499   float miterLimit = 10.0f;
1500   NVGLineCap lineJoin = NVGLineCap.Miter;
1501   NVGLineCap lineCap = NVGLineCap.Butt;
1502   float alpha = 1.0f;
1503   NVGMatrix xform;
1504   NVGscissor scissor;
1505   float fontSize = 16.0f;
1506   float letterSpacing = 0.0f;
1507   float lineHeight = 1.0f;
1508   float fontBlur = 0.0f;
1509   NVGTextAlign textAlign;
1510   int fontId = 0;
1511   bool evenOddMode = false; // use even-odd filling rule (required for some svgs); otherwise use non-zero fill
1512   // dashing
1513   enum MaxDashes = 32; // max 16 dashes
1514   float[MaxDashes] dashes;
1515   uint dashCount = 0;
1516   uint lastFlattenDashCount = 0;
1517   float dashStart = 0;
1518   float totalDashLen;
1519   bool firstDashIsGap = false;
1520   // dasher state for flattener
1521   bool dasherActive = false;
1522 
1523   void clearPaint () nothrow @trusted @nogc {
1524     fill.clear();
1525     stroke.clear();
1526   }
1527 }
1528 
1529 struct NVGpoint {
1530   float x, y;
1531   float dx, dy;
1532   float len;
1533   float dmx, dmy;
1534   ubyte flags;
1535 }
1536 
1537 struct NVGpathCache {
1538   NVGpoint* points;
1539   int npoints;
1540   int cpoints;
1541   NVGpath* paths;
1542   int npaths;
1543   int cpaths;
1544   NVGVertex* verts;
1545   int nverts;
1546   int cverts;
1547   float[4] bounds;
1548   // this is required for saved paths
1549   bool strokeReady;
1550   bool fillReady;
1551   float strokeAlphaMul;
1552   float strokeWidth;
1553   float fringeWidth;
1554   bool evenOddMode;
1555   NVGClipMode clipmode;
1556   // non-saved path will not have this
1557   float* commands;
1558   int ncommands;
1559 
1560   @disable this (this); // no copies
1561   void opAssign() (const scope auto ref NVGpathCache a) { static assert(0, "no copies!"); }
1562 
1563   // won't clear current path
1564   void copyFrom (const NVGpathCache* src) nothrow @trusted @nogc {
1565     import core.stdc.stdlib : malloc;
1566     import core.stdc.string : memcpy, memset;
1567     assert(src !is null);
1568     memcpy(&this, src, NVGpathCache.sizeof);
1569     this.points = xdup(src.points, src.npoints);
1570     this.cpoints = src.npoints;
1571     this.verts = xdup(src.verts, src.nverts);
1572     this.cverts = src.nverts;
1573     this.commands = xdup(src.commands, src.ncommands);
1574     if (src.npaths > 0) {
1575       this.paths = cast(NVGpath*)malloc(src.npaths*NVGpath.sizeof);
1576       memset(this.paths, 0, npaths*NVGpath.sizeof);
1577       foreach (immutable pidx; 0..npaths) this.paths[pidx].copyFrom(&src.paths[pidx]);
1578       this.cpaths = src.npaths;
1579     } else {
1580       this.npaths = this.cpaths = 0;
1581     }
1582   }
1583 
1584   void clear () nothrow @trusted @nogc {
1585     import core.stdc.stdlib : free;
1586     import core.stdc.string : memset;
1587     if (paths !is null) {
1588       foreach (ref p; paths[0..npaths]) p.clear();
1589       free(paths);
1590     }
1591     if (points !is null) free(points);
1592     if (verts !is null) free(verts);
1593     if (commands !is null) free(commands);
1594     memset(&this, 0, this.sizeof);
1595   }
1596 }
1597 
1598 /// Pointer to opaque NanoVega context structure.
1599 /// Group: context_management
1600 public alias NVGContext = NVGcontextinternal*;
1601 
1602 /// FontStash context
1603 /// Group: font_stash
1604 public alias FONSContext = FONScontextInternal*;
1605 
1606 /// Returns FontStash context of the given NanoVega context.
1607 /// Group: font_stash
1608 public FONSContext fonsContext (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.fs : null); }
1609 
1610 /// Returns scale that should be applied to FontStash parameters due to matrix transformations on the context (or 1)
1611 /// Group: font_stash
1612 public float fonsScale (NVGContext ctx) /*pure*/ nothrow @trusted @nogc {
1613   pragma(inline, true);
1614   return (ctx !is null && ctx.contextAlive && ctx.nstates > 0 ? nvg__getFontScale(&ctx.states.ptr[ctx.nstates-1])*ctx.devicePxRatio : 1);
1615 }
1616 
1617 /// Setup FontStash from the given NanoVega context font parameters. Note that this will apply transformation scale too.
1618 /// Returns `false` if FontStash or NanoVega context is not active.
1619 /// Group: font_stash
1620 public bool setupFonsFrom (FONSContext stash, NVGContext ctx) nothrow @trusted @nogc {
1621   if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false;
1622   NVGstate* state = nvg__getState(ctx);
1623   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
1624   stash.size = state.fontSize*scale;
1625   stash.spacing = state.letterSpacing*scale;
1626   stash.blur = state.fontBlur*scale;
1627   stash.textAlign = state.textAlign;
1628   stash.fontId = state.fontId;
1629   return true;
1630 }
1631 
1632 /// Setup NanoVega context font parameters from the given FontStash. Note that NanoVega can apply transformation scale later.
1633 /// Returns `false` if FontStash or NanoVega context is not active.
1634 /// Group: font_stash
1635 public bool setupCtxFrom (NVGContext ctx, FONSContext stash) nothrow @trusted @nogc {
1636   if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false;
1637   NVGstate* state = nvg__getState(ctx);
1638   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
1639   state.fontSize = stash.size;
1640   state.letterSpacing = stash.spacing;
1641   state.fontBlur = stash.blur;
1642   state.textAlign = stash.textAlign;
1643   state.fontId = stash.fontId;
1644   return true;
1645 }
1646 
1647 /** Bezier curve rasterizer.
1648  *
1649  * De Casteljau Bezier rasterizer is faster, but currently rasterizing curves with cusps sligtly wrong.
1650  * It doesn't really matter in practice.
1651  *
1652  * AFD tesselator is somewhat slower, but does cusps better.
1653  *
1654  * McSeem rasterizer should have the best quality, bit it is the slowest method. Basically, you will
1655  * never notice any visial difference (and this code is not really debugged), so you probably should
1656  * not use it. It is there for further experiments.
1657  */
1658 public enum NVGTesselation {
1659   DeCasteljau, /// default: standard well-known tesselation algorithm
1660   AFD, /// adaptive forward differencing
1661   DeCasteljauMcSeem, /// standard well-known tesselation algorithm, with improvements from Maxim Shemanarev; slowest one, but should give best results
1662 }
1663 
1664 /// Default tesselator for Bezier curves.
1665 public __gshared NVGTesselation NVG_DEFAULT_TESSELATOR = NVGTesselation.DeCasteljau;
1666 
1667 
1668 // some public info
1669 
1670 /// valid only inside [beginFrame]/[endFrame]
1671 /// Group: context_management
1672 public int width (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mWidth : 0); }
1673 
1674 /// valid only inside [beginFrame]/[endFrame]
1675 /// Group: context_management
1676 public int height (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mHeight : 0); }
1677 
1678 /// valid only inside [beginFrame]/[endFrame]
1679 /// Group: context_management
1680 public float devicePixelRatio (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.devicePxRatio : float.nan); }
1681 
1682 /// Returns `true` if [beginFrame] was called, and neither [endFrame], nor [cancelFrame] were.
1683 /// Group: context_management
1684 public bool inFrame (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.nstates > 0 : false); }
1685 
1686 // path autoregistration
1687 
1688 /// [pickid] to stop autoregistration.
1689 /// Group: context_management
1690 public enum NVGNoPick = -1;
1691 
1692 /// >=0: this pickid will be assigned to all filled/stroked paths
1693 /// Group: context_management
1694 public int pickid (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickId : NVGNoPick); }
1695 
1696 /// >=0: this pickid will be assigned to all filled/stroked paths
1697 /// Group: context_management
1698 public void pickid (NVGContext ctx, int v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.pathPickId = v; }
1699 
1700 /// pick autoregistration mode; see [NVGPickKind]
1701 /// Group: context_management
1702 public uint pickmode (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickRegistered&NVGPickKind.All : 0); }
1703 
1704 /// pick autoregistration mode; see [NVGPickKind]
1705 /// Group: context_management
1706 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); }
1707 
1708 // tesselator options
1709 
1710 /// Get current Bezier tesselation mode. See [NVGTesselation].
1711 /// Group: context_management
1712 public NVGTesselation tesselation (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.tesselatortype : NVGTesselation.DeCasteljau); }
1713 
1714 /// Set current Bezier tesselation mode. See [NVGTesselation].
1715 /// Group: context_management
1716 public void tesselation (NVGContext ctx, NVGTesselation v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.tesselatortype = v; }
1717 
1718 
1719 private struct NVGcontextinternal {
1720 private:
1721   NVGparams params;
1722   float* commands;
1723   int ccommands;
1724   int ncommands;
1725   float commandx, commandy;
1726   NVGstate[NVG_MAX_STATES] states;
1727   int nstates;
1728   NVGpathCache* cache;
1729   public float tessTol;
1730   public float angleTol; // 0.0f -- angle tolerance for McSeem Bezier rasterizer
1731   public float cuspLimit; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps)
1732   float distTol;
1733   public float fringeWidth;
1734   float devicePxRatio;
1735   FONSContext fs;
1736   NVGImage[NVG_MAX_FONTIMAGES] fontImages;
1737   int fontImageIdx;
1738   int drawCallCount;
1739   int fillTriCount;
1740   int strokeTriCount;
1741   int textTriCount;
1742   NVGTesselation tesselatortype;
1743   // picking API
1744   NVGpickScene* pickScene;
1745   int pathPickId; // >=0: register all paths for picking using this id
1746   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
1747   // path recording
1748   NVGPathSet recset;
1749   int recstart; // used to cancel recording
1750   bool recblockdraw;
1751   // internals
1752   NVGMatrix gpuAffine;
1753   int mWidth, mHeight;
1754   // image manager
1755   shared int imageCount; // number of alive images in this context
1756   bool contextAlive; // context can be dead, but still contain some images
1757 
1758   @disable this (this); // no copies
1759   void opAssign() (const scope auto ref NVGcontextinternal a) { static assert(0, "no copies!"); }
1760 
1761   // debug feature
1762   public @property int getImageCount () nothrow @trusted @nogc {
1763     import core.atomic;
1764     return atomicLoad(imageCount);
1765   }
1766 }
1767 
1768 /** Returns number of tesselated pathes in context.
1769  *
1770  * Tesselated pathes are either triangle strips (for strokes), or
1771  * triangle fans (for fills). Note that NanoVega can generate some
1772  * surprising pathes (like fringe stroke for antialiasing, for example).
1773  *
1774  * One render path can contain vertices both for fill, and for stroke triangles.
1775  */
1776 public int renderPathCount (NVGContext ctx) pure nothrow @trusted @nogc {
1777   pragma(inline, true);
1778   return (ctx !is null && ctx.contextAlive ? ctx.cache.npaths : 0);
1779 }
1780 
1781 /** Get vertices of "fill" triangle fan for the given render path. Can return empty slice.
1782  *
1783  * $(WARNING Returned slice can be invalidated by any other NanoVega API call
1784  *           (except the calls to render path accessors), and using it in such
1785  *           case is UB. So copy vertices to freshly allocated array if you want
1786  *           to keep them for further processing.)
1787  */
1788 public const(NVGVertex)[] renderPathFillVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1789   pragma(inline, true);
1790   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].fillVertices : null);
1791 }
1792 
1793 /** Get vertices of "stroke" triangle strip for the given render path. Can return empty slice.
1794  *
1795  * $(WARNING Returned slice can be invalidated by any other NanoVega API call
1796  *           (except the calls to render path accessors), and using it in such
1797  *           case is UB. So copy vertices to freshly allocated array if you want
1798  *           to keep them for further processing.)
1799  */
1800 public const(NVGVertex)[] renderPathStrokeVertices (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].strokeVertices : null);
1803 
1804 }
1805 
1806 /// Returns winding for the given render path.
1807 public NVGWinding renderPathWinding (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].winding : NVGWinding.CCW);
1810 
1811 }
1812 
1813 /// Returns "complex path" flag for the given render path.
1814 public bool renderPathComplex (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1815   pragma(inline, true);
1816   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].complex : false);
1817 
1818 }
1819 
1820 void nvg__imageIncRef (NVGContext ctx, int imgid, bool increfInGL=true) nothrow @trusted @nogc {
1821   if (ctx !is null && imgid > 0) {
1822     import core.atomic : atomicOp;
1823     atomicOp!"+="(ctx.imageCount, 1);
1824     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[++]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); }
1825     if (ctx.contextAlive && increfInGL) ctx.params.renderTextureIncRef(ctx.params.userPtr, imgid);
1826   }
1827 }
1828 
1829 void nvg__imageDecRef (NVGContext ctx, int imgid) nothrow @trusted @nogc {
1830   if (ctx !is null && imgid > 0) {
1831     import core.atomic : atomicOp;
1832     int icnt = atomicOp!"-="(ctx.imageCount, 1);
1833     if (icnt < 0) assert(0, "NanoVega: internal image refcounting error");
1834     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[--]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); }
1835     if (ctx.contextAlive) ctx.params.renderDeleteTexture(ctx.params.userPtr, imgid);
1836     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); }
1837     if (!ctx.contextAlive && icnt == 0) {
1838       // it is finally safe to free context memory
1839       import core.stdc.stdlib : free;
1840       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("killed zombie context %p\n", ctx); }
1841       free(ctx);
1842     }
1843   }
1844 }
1845 
1846 
1847 public import core.stdc.math :
1848   nvg__sqrtf = sqrtf,
1849   nvg__modf = fmodf,
1850   nvg__sinf = sinf,
1851   nvg__cosf = cosf,
1852   nvg__tanf = tanf,
1853   nvg__atan2f = atan2f,
1854   nvg__acosf = acosf,
1855   nvg__ceilf = ceilf;
1856 
1857 version(Windows) {
1858   public int nvg__lrintf (float f) nothrow @trusted @nogc { pragma(inline, true); return cast(int)(f+0.5); }
1859 } else {
1860   public import core.stdc.math : nvg__lrintf = lrintf;
1861 }
1862 
1863 public auto nvg__min(T) (T a, T b) { pragma(inline, true); return (a < b ? a : b); }
1864 public auto nvg__max(T) (T a, T b) { pragma(inline, true); return (a > b ? a : b); }
1865 public auto nvg__clamp(T) (T a, T mn, T mx) { pragma(inline, true); return (a < mn ? mn : (a > mx ? mx : a)); }
1866 //float nvg__absf() (float a) { pragma(inline, true); return (a >= 0.0f ? a : -a); }
1867 public auto nvg__sign(T) (T a) { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); }
1868 public float nvg__cross() (float dx0, float dy0, float dx1, float dy1) { pragma(inline, true); return (dx1*dy0-dx0*dy1); }
1869 
1870 //public import core.stdc.math : nvg__absf = fabsf;
1871 public import core.math : nvg__absf = fabs;
1872 
1873 
1874 float nvg__normalize (float* x, float* y) nothrow @safe @nogc {
1875   float d = nvg__sqrtf((*x)*(*x)+(*y)*(*y));
1876   if (d > 1e-6f) {
1877     immutable float id = 1.0f/d;
1878     *x *= id;
1879     *y *= id;
1880   }
1881   return d;
1882 }
1883 
1884 void nvg__deletePathCache (ref NVGpathCache* c) nothrow @trusted @nogc {
1885   if (c !is null) {
1886     c.clear();
1887     free(c);
1888   }
1889 }
1890 
1891 NVGpathCache* nvg__allocPathCache () nothrow @trusted @nogc {
1892   NVGpathCache* c = cast(NVGpathCache*)malloc(NVGpathCache.sizeof);
1893   if (c is null) goto error;
1894   memset(c, 0, NVGpathCache.sizeof);
1895 
1896   c.points = cast(NVGpoint*)malloc(NVGpoint.sizeof*NVG_INIT_POINTS_SIZE);
1897   if (c.points is null) goto error;
1898   assert(c.npoints == 0);
1899   c.cpoints = NVG_INIT_POINTS_SIZE;
1900 
1901   c.paths = cast(NVGpath*)malloc(NVGpath.sizeof*NVG_INIT_PATHS_SIZE);
1902   if (c.paths is null) goto error;
1903   assert(c.npaths == 0);
1904   c.cpaths = NVG_INIT_PATHS_SIZE;
1905 
1906   c.verts = cast(NVGVertex*)malloc(NVGVertex.sizeof*NVG_INIT_VERTS_SIZE);
1907   if (c.verts is null) goto error;
1908   assert(c.nverts == 0);
1909   c.cverts = NVG_INIT_VERTS_SIZE;
1910 
1911   return c;
1912 
1913 error:
1914   nvg__deletePathCache(c);
1915   return null;
1916 }
1917 
1918 void nvg__setDevicePixelRatio (NVGContext ctx, float ratio) pure nothrow @safe @nogc {
1919   ctx.tessTol = 0.25f/ratio;
1920   ctx.distTol = 0.01f/ratio;
1921   ctx.fringeWidth = 1.0f/ratio;
1922   ctx.devicePxRatio = ratio;
1923 }
1924 
1925 NVGCompositeOperationState nvg__compositeOperationState (NVGCompositeOperation op) pure nothrow @safe @nogc {
1926   NVGCompositeOperationState state;
1927   NVGBlendFactor sfactor, dfactor;
1928 
1929        if (op == NVGCompositeOperation.SourceOver) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha;}
1930   else if (op == NVGCompositeOperation.SourceIn) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.Zero; }
1931   else if (op == NVGCompositeOperation.SourceOut) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.Zero; }
1932   else if (op == NVGCompositeOperation.SourceAtop) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.OneMinusSrcAlpha; }
1933   else if (op == NVGCompositeOperation.DestinationOver) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.One; }
1934   else if (op == NVGCompositeOperation.DestinationIn) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.SrcAlpha; }
1935   else if (op == NVGCompositeOperation.DestinationOut) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.OneMinusSrcAlpha; }
1936   else if (op == NVGCompositeOperation.DestinationAtop) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.SrcAlpha; }
1937   else if (op == NVGCompositeOperation.Lighter) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.One; }
1938   else if (op == NVGCompositeOperation.Copy) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.Zero;  }
1939   else if (op == NVGCompositeOperation.Xor) {
1940     state.simple = false;
1941     state.srcRGB = NVGBlendFactor.OneMinusDstColor;
1942     state.srcAlpha = NVGBlendFactor.OneMinusDstAlpha;
1943     state.dstRGB = NVGBlendFactor.OneMinusSrcColor;
1944     state.dstAlpha = NVGBlendFactor.OneMinusSrcAlpha;
1945     return state;
1946   }
1947   else { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } // default value for invalid op: SourceOver
1948 
1949   state.simple = true;
1950   state.srcAlpha = sfactor;
1951   state.dstAlpha = dfactor;
1952   return state;
1953 }
1954 
1955 NVGstate* nvg__getState (NVGContext ctx) pure nothrow @trusted @nogc {
1956   pragma(inline, true);
1957   if (ctx is null || !ctx.contextAlive || ctx.nstates == 0) assert(0, "NanoVega: cannot perform commands on inactive context");
1958   return &ctx.states.ptr[ctx.nstates-1];
1959 }
1960 
1961 // Constructor called by the render back-end.
1962 NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc {
1963   FONSParams fontParams;
1964   NVGContext ctx = cast(NVGContext)malloc(NVGcontextinternal.sizeof);
1965   if (ctx is null) goto error;
1966   memset(ctx, 0, NVGcontextinternal.sizeof);
1967 
1968   ctx.angleTol = 0; // angle tolerance for McSeem Bezier rasterizer
1969   ctx.cuspLimit = 0; // cusp limit for McSeem Bezier rasterizer (0: real cusps)
1970 
1971   ctx.contextAlive = true;
1972 
1973   ctx.params = *params;
1974   //ctx.fontImages[0..NVG_MAX_FONTIMAGES] = 0;
1975 
1976   ctx.commands = cast(float*)malloc(float.sizeof*NVG_INIT_COMMANDS_SIZE);
1977   if (ctx.commands is null) goto error;
1978   ctx.ncommands = 0;
1979   ctx.ccommands = NVG_INIT_COMMANDS_SIZE;
1980 
1981   ctx.cache = nvg__allocPathCache();
1982   if (ctx.cache is null) goto error;
1983 
1984   ctx.save();
1985   ctx.reset();
1986 
1987   nvg__setDevicePixelRatio(ctx, 1.0f);
1988   ctx.mWidth = ctx.mHeight = 0;
1989 
1990   if (!ctx.params.renderCreate(ctx.params.userPtr)) goto error;
1991 
1992   // init font rendering
1993   memset(&fontParams, 0, fontParams.sizeof);
1994   fontParams.width = NVG_INIT_FONTIMAGE_SIZE;
1995   fontParams.height = NVG_INIT_FONTIMAGE_SIZE;
1996   fontParams.flags = FONSParams.Flag.ZeroTopLeft;
1997   fontParams.renderCreate = null;
1998   fontParams.renderUpdate = null;
1999   fontParams.renderDelete = null;
2000   fontParams.userPtr = null;
2001   ctx.fs = FONSContext.create(fontParams);
2002   if (ctx.fs is null) goto error;
2003 
2004   // create font texture
2005   ctx.fontImages[0].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, fontParams.width, fontParams.height, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null);
2006   if (ctx.fontImages[0].id == 0) goto error;
2007   ctx.fontImages[0].ctx = ctx;
2008   ctx.nvg__imageIncRef(ctx.fontImages[0].id, false); // don't increment driver refcount
2009   ctx.fontImageIdx = 0;
2010 
2011   ctx.pathPickId = -1;
2012   ctx.tesselatortype = NVG_DEFAULT_TESSELATOR;
2013 
2014   return ctx;
2015 
2016 error:
2017   ctx.deleteInternal();
2018   return null;
2019 }
2020 
2021 // Called by render backend.
2022 NVGparams* internalParams (NVGContext ctx) nothrow @trusted @nogc {
2023   return &ctx.params;
2024 }
2025 
2026 // Destructor called by the render back-end.
2027 void deleteInternal (ref NVGContext ctx) nothrow @trusted @nogc {
2028   if (ctx is null) return;
2029   if (ctx.contextAlive) {
2030     if (ctx.commands !is null) free(ctx.commands);
2031     nvg__deletePathCache(ctx.cache);
2032 
2033     if (ctx.fs) ctx.fs.kill();
2034 
2035     foreach (uint i; 0..NVG_MAX_FONTIMAGES) ctx.fontImages[i].clear();
2036 
2037     if (ctx.params.renderDelete !is null) ctx.params.renderDelete(ctx.params.userPtr);
2038 
2039     if (ctx.pickScene !is null) nvg__deletePickScene(ctx.pickScene);
2040 
2041     ctx.contextAlive = false;
2042 
2043     import core.atomic : atomicLoad;
2044     if (atomicLoad(ctx.imageCount) == 0) {
2045       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("destroyed context %p\n", ctx); }
2046       free(ctx);
2047     } else {
2048       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("context %p is zombie now (%d image refs)\n", ctx, ctx.imageCount); }
2049     }
2050   }
2051 }
2052 
2053 /// Delete NanoVega context.
2054 /// Group: context_management
2055 public void kill (ref NVGContext ctx) nothrow @trusted @nogc {
2056   if (ctx !is null) {
2057     ctx.deleteInternal();
2058     ctx = null;
2059   }
2060 }
2061 
2062 /// Returns `true` if the given context is not `null` and can be used for painting.
2063 /// Group: context_management
2064 public bool valid (in NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive); }
2065 
2066 
2067 // ////////////////////////////////////////////////////////////////////////// //
2068 // Frame Management
2069 
2070 /** Begin drawing a new frame.
2071  *
2072  * Calls to NanoVega drawing API should be wrapped in [beginFrame] and [endFrame]
2073  *
2074  * [beginFrame] defines the size of the window to render to in relation currently
2075  * set viewport (i.e. glViewport on GL backends). Device pixel ration allows to
2076  * control the rendering on Hi-DPI devices.
2077  *
2078  * For example, GLFW returns two dimension for an opened window: window size and
2079  * frame buffer size. In that case you would set windowWidth/windowHeight to the window size,
2080  * devicePixelRatio to: `windowWidth/windowHeight`.
2081  *
2082  * Default ratio is `1`.
2083  *
2084  * Note that fractional ratio can (and will) distort your fonts and images.
2085  *
2086  * This call also resets pick marks (see picking API for non-rasterized paths),
2087  * path recording, and GPU affine transformatin matrix.
2088  *
2089  * see also [glNVGClearFlags], which returns necessary flags for [glClear].
2090  *
2091  * Group: frame_management
2092  */
2093 public void beginFrame (NVGContext ctx, int windowWidth, int windowHeight, float devicePixelRatio=1.0f) nothrow @trusted @nogc {
2094   import std.math : isNaN;
2095   /*
2096   printf("Tris: draws:%d  fill:%d  stroke:%d  text:%d  TOT:%d\n",
2097          ctx.drawCallCount, ctx.fillTriCount, ctx.strokeTriCount, ctx.textTriCount,
2098          ctx.fillTriCount+ctx.strokeTriCount+ctx.textTriCount);
2099   */
2100   if (ctx.nstates > 0) ctx.cancelFrame();
2101 
2102   if (windowWidth < 1) windowWidth = 1;
2103   if (windowHeight < 1) windowHeight = 1;
2104 
2105   if (isNaN(devicePixelRatio)) devicePixelRatio = (windowHeight > 0 ? cast(float)windowWidth/cast(float)windowHeight : 1024.0/768.0);
2106 
2107   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2108   ctx.nstates = 0;
2109   ctx.save();
2110   ctx.reset();
2111 
2112   nvg__setDevicePixelRatio(ctx, devicePixelRatio);
2113 
2114   ctx.params.renderViewport(ctx.params.userPtr, windowWidth, windowHeight);
2115   ctx.mWidth = windowWidth;
2116   ctx.mHeight = windowHeight;
2117 
2118   ctx.recset = null;
2119   ctx.recstart = -1;
2120 
2121   ctx.pathPickId = NVGNoPick;
2122   ctx.pathPickRegistered = 0;
2123 
2124   ctx.drawCallCount = 0;
2125   ctx.fillTriCount = 0;
2126   ctx.strokeTriCount = 0;
2127   ctx.textTriCount = 0;
2128 
2129   ctx.ncommands = 0;
2130   ctx.pathPickRegistered = 0;
2131   nvg__clearPathCache(ctx);
2132 
2133   ctx.gpuAffine = NVGMatrix.Identity;
2134 
2135   nvg__pickBeginFrame(ctx, windowWidth, windowHeight);
2136 }
2137 
2138 /// Cancels drawing the current frame. Cancels path recording.
2139 /// Group: frame_management
2140 public void cancelFrame (NVGContext ctx) nothrow @trusted @nogc {
2141   ctx.cancelRecording();
2142   //ctx.mWidth = 0;
2143   //ctx.mHeight = 0;
2144   // cancel render queue
2145   ctx.params.renderCancel(ctx.params.userPtr);
2146   // clear saved states (this may free some textures)
2147   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2148   ctx.nstates = 0;
2149 }
2150 
2151 /// Ends drawing the current frame (flushing remaining render state). Commits recorded paths.
2152 /// Group: frame_management
2153 public void endFrame (NVGContext ctx) nothrow @trusted @nogc {
2154   if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx);
2155   ctx.stopRecording();
2156   //ctx.mWidth = 0;
2157   //ctx.mHeight = 0;
2158   // flush render queue
2159   NVGstate* state = nvg__getState(ctx);
2160   ctx.params.renderFlush(ctx.params.userPtr);
2161   if (ctx.fontImageIdx != 0) {
2162     auto fontImage = ctx.fontImages[ctx.fontImageIdx];
2163     int j = 0, iw, ih;
2164     // delete images that smaller than current one
2165     if (!fontImage.valid) return;
2166     ctx.imageSize(fontImage, iw, ih);
2167     foreach (int i; 0..ctx.fontImageIdx) {
2168       if (ctx.fontImages[i].valid) {
2169         int nw, nh;
2170         ctx.imageSize(ctx.fontImages[i], nw, nh);
2171         if (nw < iw || nh < ih) {
2172           ctx.deleteImage(ctx.fontImages[i]);
2173         } else {
2174           ctx.fontImages[j++] = ctx.fontImages[i];
2175         }
2176       }
2177     }
2178     // make current font image to first
2179     ctx.fontImages[j++] = ctx.fontImages[0];
2180     ctx.fontImages[0] = fontImage;
2181     ctx.fontImageIdx = 0;
2182     // clear all images after j
2183     ctx.fontImages[j..NVG_MAX_FONTIMAGES] = NVGImage.init;
2184   }
2185   // clear saved states (this may free some textures)
2186   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2187   ctx.nstates = 0;
2188 }
2189 
2190 
2191 // ////////////////////////////////////////////////////////////////////////// //
2192 // Recording and Replaying Pathes
2193 
2194 // Saved path set.
2195 // Group: path_recording
2196 public alias NVGPathSet = NVGPathSetS*;
2197 
2198 
2199 //TODO: save scissor info?
2200 struct NVGPathSetS {
2201 private:
2202   // either path cache, or text item
2203   static struct Node {
2204     NVGPaint paint;
2205     NVGpathCache* path;
2206   }
2207 
2208 private:
2209   Node* nodes;
2210   int nnodes, cnodes;
2211   NVGpickScene* pickscene;
2212   //int npickscenes, cpickscenes;
2213   NVGContext svctx; // used to do some sanity checks, and to free resources
2214 
2215 private:
2216   Node* allocNode () nothrow @trusted @nogc {
2217     import core.stdc.string : memset;
2218     // grow buffer if necessary
2219     if (nnodes+1 > cnodes) {
2220       import core.stdc.stdlib : realloc;
2221       int newsz = (cnodes == 0 ? 8 : cnodes <= 1024 ? cnodes*2 : cnodes+1024);
2222       nodes = cast(Node*)realloc(nodes, newsz*Node.sizeof);
2223       if (nodes is null) assert(0, "NanoVega: out of memory");
2224       //memset(svp.caches+svp.ccaches, 0, (newsz-svp.ccaches)*NVGpathCache.sizeof);
2225       cnodes = newsz;
2226     }
2227     assert(nnodes < cnodes);
2228     memset(nodes+nnodes, 0, Node.sizeof);
2229     return &nodes[nnodes++];
2230   }
2231 
2232   Node* allocPathNode () nothrow @trusted @nogc {
2233     import core.stdc.stdlib : malloc;
2234     import core.stdc.string : memset;
2235     auto node = allocNode();
2236     // allocate path cache
2237     auto pc = cast(NVGpathCache*)malloc(NVGpathCache.sizeof);
2238     if (pc is null) assert(0, "NanoVega: out of memory");
2239     node.path = pc;
2240     return node;
2241   }
2242 
2243   void clearNode (int idx) nothrow @trusted @nogc {
2244     if (idx < 0 || idx >= nnodes) return;
2245     Node* node = &nodes[idx];
2246     if (svctx !is null && node.paint.image.valid) node.paint.image.clear();
2247     if (node.path !is null) node.path.clear();
2248   }
2249 
2250 private:
2251   void takeCurrentPickScene (NVGContext ctx) nothrow @trusted @nogc {
2252     NVGpickScene* ps = ctx.pickScene;
2253     if (ps is null) return; // nothing to do
2254     if (ps.npaths == 0) return; // pick scene is empty
2255     ctx.pickScene = null;
2256     pickscene = ps;
2257   }
2258 
2259   void replay (NVGContext ctx, const scope ref NVGColor fillTint, const scope ref NVGColor strokeTint) nothrow @trusted @nogc {
2260     NVGstate* state = nvg__getState(ctx);
2261     foreach (ref node; nodes[0..nnodes]) {
2262       if (auto cc = node.path) {
2263         if (cc.npaths <= 0) continue;
2264 
2265         if (cc.fillReady) {
2266           NVGPaint fillPaint = node.paint;
2267 
2268           // apply global alpha
2269           fillPaint.innerColor.a *= state.alpha;
2270           fillPaint.middleColor.a *= state.alpha;
2271           fillPaint.outerColor.a *= state.alpha;
2272 
2273           fillPaint.innerColor.applyTint(fillTint);
2274           fillPaint.middleColor.applyTint(fillTint);
2275           fillPaint.outerColor.applyTint(fillTint);
2276 
2277           ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &fillPaint, &state.scissor, cc.fringeWidth, cc.bounds.ptr, cc.paths, cc.npaths, cc.evenOddMode);
2278 
2279           // count triangles
2280           foreach (int i; 0..cc.npaths) {
2281             NVGpath* path = &cc.paths[i];
2282             ctx.fillTriCount += path.nfill-2;
2283             ctx.fillTriCount += path.nstroke-2;
2284             ctx.drawCallCount += 2;
2285           }
2286         }
2287 
2288         if (cc.strokeReady) {
2289           NVGPaint strokePaint = node.paint;
2290 
2291           strokePaint.innerColor.a *= cc.strokeAlphaMul;
2292           strokePaint.middleColor.a *= cc.strokeAlphaMul;
2293           strokePaint.outerColor.a *= cc.strokeAlphaMul;
2294 
2295           // apply global alpha
2296           strokePaint.innerColor.a *= state.alpha;
2297           strokePaint.middleColor.a *= state.alpha;
2298           strokePaint.outerColor.a *= state.alpha;
2299 
2300           strokePaint.innerColor.applyTint(strokeTint);
2301           strokePaint.middleColor.applyTint(strokeTint);
2302           strokePaint.outerColor.applyTint(strokeTint);
2303 
2304           ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &strokePaint, &state.scissor, cc.fringeWidth, cc.strokeWidth, cc.paths, cc.npaths);
2305 
2306           // count triangles
2307           foreach (int i; 0..cc.npaths) {
2308             NVGpath* path = &cc.paths[i];
2309             ctx.strokeTriCount += path.nstroke-2;
2310             ++ctx.drawCallCount;
2311           }
2312         }
2313       }
2314     }
2315   }
2316 
2317 public:
2318   @disable this (this); // no copies
2319   void opAssign() (const scope auto ref NVGPathSetS a) { static assert(0, "no copies!"); }
2320 
2321   // pick test
2322   // Call delegate [dg] for each path under the specified position (in no particular order).
2323   // Returns the id of the path for which delegate [dg] returned true or -1.
2324   // dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending).
2325   int hitTestDG(bool bestOrder=false, DG) (in float x, in float y, NVGPickKind kind, scope DG dg) if (IsGoodHitTestDG!DG || IsGoodHitTestInternalDG!DG) {
2326     if (pickscene is null) return -1;
2327 
2328     NVGpickScene* ps = pickscene;
2329     int levelwidth = 1<<(ps.nlevels-1);
2330     int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth);
2331     int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth);
2332     int npicked = 0;
2333 
2334     for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) {
2335       NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx];
2336       while (pp !is null) {
2337         if (nvg__pickPathTestBounds(svctx, ps, pp, x, y)) {
2338           int hit = 0;
2339           if ((kind&NVGPickKind.Stroke) && (pp.flags&NVGPathFlags.Stroke)) hit = nvg__pickPathStroke(ps, pp, x, y);
2340           if (!hit && (kind&NVGPickKind.Fill) && (pp.flags&NVGPathFlags.Fill)) hit = nvg__pickPath(ps, pp, x, y);
2341           if (hit) {
2342             static if (IsGoodHitTestDG!DG) {
2343               static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) {
2344                 if (dg(pp.id, cast(int)pp.order)) return pp.id;
2345               } else {
2346                 dg(pp.id, cast(int)pp.order);
2347               }
2348             } else {
2349               static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) {
2350                 if (dg(pp)) return pp.id;
2351               } else {
2352                 dg(pp);
2353               }
2354             }
2355           }
2356         }
2357         pp = pp.next;
2358       }
2359       cellx >>= 1;
2360       celly >>= 1;
2361       levelwidth >>= 1;
2362     }
2363 
2364     return -1;
2365   }
2366 
2367   // Fills ids with a list of the top most hit ids under the specified position.
2368   // Returns the slice of [ids].
2369   int[] hitTestAll (in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc {
2370     if (pickscene is null || ids.length == 0) return ids[0..0];
2371 
2372     int npicked = 0;
2373     NVGpickScene* ps = pickscene;
2374 
2375     hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
2376       if (npicked == ps.cpicked) {
2377         int cpicked = ps.cpicked+ps.cpicked;
2378         NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked);
2379         if (picked is null) return true; // abort
2380         ps.cpicked = cpicked;
2381         ps.picked = picked;
2382       }
2383       ps.picked[npicked] = pp;
2384       ++npicked;
2385       return false; // go on
2386     });
2387 
2388     qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths);
2389 
2390     assert(npicked >= 0);
2391     if (npicked > ids.length) npicked = cast(int)ids.length;
2392     foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id;
2393 
2394     return ids[0..npicked];
2395   }
2396 
2397   // Returns the id of the pickable shape containing x,y or -1 if no shape was found.
2398   int hitTest (in float x, in float y, NVGPickKind kind) nothrow @trusted @nogc {
2399     if (pickscene is null) return -1;
2400 
2401     int bestOrder = -1;
2402     int bestID = -1;
2403 
2404     hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
2405       if (pp.order > bestOrder) {
2406         bestOrder = pp.order;
2407         bestID = pp.id;
2408       }
2409     });
2410 
2411     return bestID;
2412   }
2413 }
2414 
2415 // Append current path to existing path set. Is is safe to call this with `null` [svp].
2416 void appendCurrentPathToCache (NVGContext ctx, NVGPathSet svp, const scope ref NVGPaint paint) nothrow @trusted @nogc {
2417   if (ctx is null || svp is null) return;
2418   if (ctx !is svp.svctx) assert(0, "NanoVega: cannot save paths from different contexts");
2419   if (ctx.ncommands == 0) {
2420     assert(ctx.cache.npaths == 0);
2421     return;
2422   }
2423   if (!ctx.cache.fillReady && !ctx.cache.strokeReady) return;
2424 
2425   // tesselate current path
2426   //if (!ctx.cache.fillReady) nvg__prepareFill(ctx);
2427   //if (!ctx.cache.strokeReady) nvg__prepareStroke(ctx);
2428 
2429   auto node = svp.allocPathNode();
2430   NVGpathCache* cc = node.path;
2431   cc.copyFrom(ctx.cache);
2432   node.paint = paint;
2433   // copy path commands (we may need 'em for picking)
2434   version(all) {
2435     cc.ncommands = ctx.ncommands;
2436     if (cc.ncommands) {
2437       import core.stdc.stdlib : malloc;
2438       import core.stdc.string : memcpy;
2439       cc.commands = cast(float*)malloc(ctx.ncommands*float.sizeof);
2440       if (cc.commands is null) assert(0, "NanoVega: out of memory");
2441       memcpy(cc.commands, ctx.commands, ctx.ncommands*float.sizeof);
2442     } else {
2443       cc.commands = null;
2444     }
2445   }
2446 }
2447 
2448 // Create new empty path set.
2449 // Group: path_recording
2450 public NVGPathSet newPathSet (NVGContext ctx) nothrow @trusted @nogc {
2451   import core.stdc.stdlib : malloc;
2452   import core.stdc.string : memset;
2453   if (ctx is null) return null;
2454   NVGPathSet res = cast(NVGPathSet)malloc(NVGPathSetS.sizeof);
2455   if (res is null) assert(0, "NanoVega: out of memory");
2456   memset(res, 0, NVGPathSetS.sizeof);
2457   res.svctx = ctx;
2458   return res;
2459 }
2460 
2461 // Is the given path set empty? Empty path set can be `null`.
2462 // Group: path_recording
2463 public bool empty (NVGPathSet svp) pure nothrow @safe @nogc { pragma(inline, true); return (svp is null || svp.nnodes == 0); }
2464 
2465 // Clear path set contents. Will release $(B some) allocated memory (this function is meant to clear something that will be reused).
2466 // Group: path_recording
2467 public void clear (NVGPathSet svp) nothrow @trusted @nogc {
2468   if (svp !is null) {
2469     import core.stdc.stdlib : free;
2470     foreach (immutable idx; 0.. svp.nnodes) svp.clearNode(idx);
2471     svp.nnodes = 0;
2472   }
2473 }
2474 
2475 // Destroy path set (frees all allocated memory).
2476 // Group: path_recording
2477 public void kill (ref NVGPathSet svp) nothrow @trusted @nogc {
2478   if (svp !is null) {
2479     import core.stdc.stdlib : free;
2480     svp.clear();
2481     if (svp.nodes !is null) free(svp.nodes);
2482     free(svp);
2483     if (svp.pickscene !is null) nvg__deletePickScene(svp.pickscene);
2484     svp = null;
2485   }
2486 }
2487 
2488 // Start path recording. [svp] should be alive until recording is cancelled or stopped.
2489 // Group: path_recording
2490 public void startRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc {
2491   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2492   ctx.stopRecording();
2493   ctx.recset = svp;
2494   ctx.recstart = (svp !is null ? svp.nnodes : -1);
2495   ctx.recblockdraw = false;
2496 }
2497 
2498 /* Start path recording. [svp] should be alive until recording is cancelled or stopped.
2499  *
2500  * This will block all rendering, so you can call your rendering functions to record paths without actual drawing.
2501  * Commiting or cancelling will re-enable rendering.
2502  * You can call this with `null` svp to block rendering without recording any paths.
2503  *
2504  * Group: path_recording
2505  */
2506 public void startBlockingRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc {
2507   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2508   ctx.stopRecording();
2509   ctx.recset = svp;
2510   ctx.recstart = (svp !is null ? svp.nnodes : -1);
2511   ctx.recblockdraw = true;
2512 }
2513 
2514 // Commit recorded paths. It is safe to call this when recording is not started.
2515 // Group: path_recording
2516 public void stopRecording (NVGContext ctx) nothrow @trusted @nogc {
2517   if (ctx.recset !is null && ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2518   if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx);
2519   ctx.recset = null;
2520   ctx.recstart = -1;
2521   ctx.recblockdraw = false;
2522 }
2523 
2524 // Cancel path recording.
2525 // Group: path_recording
2526 public void cancelRecording (NVGContext ctx) nothrow @trusted @nogc {
2527   if (ctx.recset !is null) {
2528     if (ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2529     assert(ctx.recstart >= 0 && ctx.recstart <= ctx.recset.nnodes);
2530     foreach (immutable idx; ctx.recstart..ctx.recset.nnodes) ctx.recset.clearNode(idx);
2531     ctx.recset.nnodes = ctx.recstart;
2532     ctx.recset = null;
2533     ctx.recstart = -1;
2534   }
2535   ctx.recblockdraw = false;
2536 }
2537 
2538 /* Replay saved path set.
2539  *
2540  * Replaying record while you're recording another one is undefined behavior.
2541  *
2542  * Group: path_recording
2543  */
2544 public void replayRecording() (NVGContext ctx, NVGPathSet svp, const scope auto ref NVGColor fillTint, const scope auto ref NVGColor strokeTint) nothrow @trusted @nogc {
2545   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2546   svp.replay(ctx, fillTint, strokeTint);
2547 }
2548 
2549 /// Ditto.
2550 public void replayRecording() (NVGContext ctx, NVGPathSet svp, const scope auto ref NVGColor fillTint) nothrow @trusted @nogc { ctx.replayRecording(svp, fillTint, NVGColor.transparent); }
2551 
2552 /// Ditto.
2553 public void replayRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { ctx.replayRecording(svp, NVGColor.transparent, NVGColor.transparent); }
2554 
2555 
2556 // ////////////////////////////////////////////////////////////////////////// //
2557 // Composite operation
2558 
2559 /// Sets the composite operation.
2560 /// Group: composite_operation
2561 public void globalCompositeOperation (NVGContext ctx, NVGCompositeOperation op) nothrow @trusted @nogc {
2562   NVGstate* state = nvg__getState(ctx);
2563   state.compositeOperation = nvg__compositeOperationState(op);
2564 }
2565 
2566 /// Sets the composite operation with custom pixel arithmetic.
2567 /// Group: composite_operation
2568 public void globalCompositeBlendFunc (NVGContext ctx, NVGBlendFactor sfactor, NVGBlendFactor dfactor) nothrow @trusted @nogc {
2569   ctx.globalCompositeBlendFuncSeparate(sfactor, dfactor, sfactor, dfactor);
2570 }
2571 
2572 /// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately.
2573 /// Group: composite_operation
2574 public void globalCompositeBlendFuncSeparate (NVGContext ctx, NVGBlendFactor srcRGB, NVGBlendFactor dstRGB, NVGBlendFactor srcAlpha, NVGBlendFactor dstAlpha) nothrow @trusted @nogc {
2575   NVGCompositeOperationState op;
2576   op.simple = false;
2577   op.srcRGB = srcRGB;
2578   op.dstRGB = dstRGB;
2579   op.srcAlpha = srcAlpha;
2580   op.dstAlpha = dstAlpha;
2581   NVGstate* state = nvg__getState(ctx);
2582   state.compositeOperation = op;
2583 }
2584 
2585 
2586 // ////////////////////////////////////////////////////////////////////////// //
2587 // Color utils
2588 
2589 /// Returns a color value from string form.
2590 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb"
2591 /// Group: color_utils
2592 public NVGColor nvgRGB (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); }
2593 
2594 /// Ditto.
2595 public NVGColor nvgRGBA (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); }
2596 
2597 /// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f).
2598 /// Group: color_utils
2599 public NVGColor nvgRGB (int r, int g, int b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(nvgClampToByte(r), nvgClampToByte(g), nvgClampToByte(b), 255); }
2600 
2601 /// Returns a color value from red, green, blue values. Alpha will be set to 1.0f.
2602 /// Group: color_utils
2603 public NVGColor nvgRGBf (float r, float g, float b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(r, g, b, 1.0f); }
2604 
2605 /// Returns a color value from red, green, blue and alpha values.
2606 /// Group: color_utils
2607 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)); }
2608 
2609 /// Returns a color value from red, green, blue and alpha values.
2610 /// Group: color_utils
2611 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); }
2612 
2613 /// Returns new color with transparency (alpha) set to [a].
2614 /// Group: color_utils
2615 public NVGColor nvgTransRGBA (NVGColor c, ubyte a) nothrow @trusted @nogc {
2616   pragma(inline, true);
2617   c.a = a/255.0f;
2618   return c;
2619 }
2620 
2621 /// Ditto.
2622 public NVGColor nvgTransRGBAf (NVGColor c, float a) nothrow @trusted @nogc {
2623   pragma(inline, true);
2624   c.a = a;
2625   return c;
2626 }
2627 
2628 /// Linearly interpolates from color c0 to c1, and returns resulting color value.
2629 /// Group: color_utils
2630 public NVGColor nvgLerpRGBA() (const scope auto ref NVGColor c0, const scope auto ref NVGColor c1, float u) nothrow @trusted @nogc {
2631   NVGColor cint = void;
2632   u = nvg__clamp(u, 0.0f, 1.0f);
2633   float oneminu = 1.0f-u;
2634   foreach (uint i; 0..4) cint.rgba.ptr[i] = c0.rgba.ptr[i]*oneminu+c1.rgba.ptr[i]*u;
2635   return cint;
2636 }
2637 
2638 /* see below
2639 public NVGColor nvgHSL() (float h, float s, float l) {
2640   //pragma(inline, true); // alas
2641   return nvgHSLA(h, s, l, 255);
2642 }
2643 */
2644 
2645 float nvg__hue (float h, float m1, float m2) pure nothrow @safe @nogc {
2646   if (h < 0) h += 1;
2647   if (h > 1) h -= 1;
2648   if (h < 1.0f/6.0f) return m1+(m2-m1)*h*6.0f;
2649   if (h < 3.0f/6.0f) return m2;
2650   if (h < 4.0f/6.0f) return m1+(m2-m1)*(2.0f/3.0f-h)*6.0f;
2651   return m1;
2652 }
2653 
2654 /// Returns color value specified by hue, saturation and lightness.
2655 /// HSL values are all in range [0..1], alpha will be set to 255.
2656 /// Group: color_utils
2657 public alias nvgHSL = nvgHSLA; // trick to allow inlining
2658 
2659 /// Returns color value specified by hue, saturation and lightness and alpha.
2660 /// HSL values are all in range [0..1], alpha in range [0..255].
2661 /// Group: color_utils
2662 public NVGColor nvgHSLA (float h, float s, float l, ubyte a=255) nothrow @trusted @nogc {
2663   pragma(inline, true);
2664   NVGColor col = void;
2665   h = nvg__modf(h, 1.0f);
2666   if (h < 0.0f) h += 1.0f;
2667   s = nvg__clamp(s, 0.0f, 1.0f);
2668   l = nvg__clamp(l, 0.0f, 1.0f);
2669   immutable float m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s);
2670   immutable float m1 = 2*l-m2;
2671   col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2672   col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f);
2673   col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2674   col.a = a/255.0f;
2675   return col;
2676 }
2677 
2678 /// Returns color value specified by hue, saturation and lightness and alpha.
2679 /// HSL values and alpha are all in range [0..1].
2680 /// Group: color_utils
2681 public NVGColor nvgHSLA (float h, float s, float l, float a) nothrow @trusted @nogc {
2682   // sorry for copypasta, it is for inliner
2683   static if (__VERSION__ >= 2072) pragma(inline, true);
2684   NVGColor col = void;
2685   h = nvg__modf(h, 1.0f);
2686   if (h < 0.0f) h += 1.0f;
2687   s = nvg__clamp(s, 0.0f, 1.0f);
2688   l = nvg__clamp(l, 0.0f, 1.0f);
2689   immutable m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s);
2690   immutable m1 = 2*l-m2;
2691   col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2692   col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f);
2693   col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2694   col.a = a;
2695   return col;
2696 }
2697 
2698 
2699 // ////////////////////////////////////////////////////////////////////////// //
2700 // Matrices and Transformations
2701 
2702 /** Matrix class.
2703  *
2704  * Group: matrices
2705  */
2706 public align(1) struct NVGMatrix {
2707 align(1):
2708 private:
2709   static immutable float[6] IdentityMat = [
2710     1.0f, 0.0f,
2711     0.0f, 1.0f,
2712     0.0f, 0.0f,
2713   ];
2714 
2715 public:
2716   /// Matrix values. Initial value is identity matrix.
2717   float[6] mat = [
2718     1.0f, 0.0f,
2719     0.0f, 1.0f,
2720     0.0f, 0.0f,
2721   ];
2722 
2723 public nothrow @trusted @nogc:
2724   /// Create Matrix with the given values.
2725   this (const(float)[] amat...) {
2726     pragma(inline, true);
2727     if (amat.length >= 6) {
2728       mat.ptr[0..6] = amat.ptr[0..6];
2729     } else {
2730       mat.ptr[0..6] = 0;
2731       mat.ptr[0..amat.length] = amat[];
2732     }
2733   }
2734 
2735   /// Can be used to check validity of [inverted] result
2736   @property bool valid () const { import core.stdc.math : isfinite; return (isfinite(mat.ptr[0]) != 0); }
2737 
2738   /// Returns `true` if this matrix is identity matrix.
2739   @property bool isIdentity () const { version(aliced) pragma(inline, true); return (mat[] == IdentityMat[]); }
2740 
2741   /// Returns new inverse matrix.
2742   /// If inverted matrix cannot be calculated, `res.valid` fill be `false`.
2743   NVGMatrix inverted () const {
2744     NVGMatrix res = this;
2745     res.invert;
2746     return res;
2747   }
2748 
2749   /// Inverts this matrix.
2750   /// If inverted matrix cannot be calculated, `this.valid` fill be `false`.
2751   ref NVGMatrix invert () return {
2752     float[6] inv = void;
2753     immutable double det = cast(double)mat.ptr[0]*mat.ptr[3]-cast(double)mat.ptr[2]*mat.ptr[1];
2754     if (det > -1e-6 && det < 1e-6) {
2755       inv[] = float.nan;
2756     } else {
2757       immutable double invdet = 1.0/det;
2758       inv.ptr[0] = cast(float)(mat.ptr[3]*invdet);
2759       inv.ptr[2] = cast(float)(-mat.ptr[2]*invdet);
2760       inv.ptr[4] = cast(float)((cast(double)mat.ptr[2]*mat.ptr[5]-cast(double)mat.ptr[3]*mat.ptr[4])*invdet);
2761       inv.ptr[1] = cast(float)(-mat.ptr[1]*invdet);
2762       inv.ptr[3] = cast(float)(mat.ptr[0]*invdet);
2763       inv.ptr[5] = cast(float)((cast(double)mat.ptr[1]*mat.ptr[4]-cast(double)mat.ptr[0]*mat.ptr[5])*invdet);
2764     }
2765     mat.ptr[0..6] = inv.ptr[0..6];
2766     return this;
2767   }
2768 
2769   /// Sets this matrix to identity matrix.
2770   ref NVGMatrix identity () return { version(aliced) pragma(inline, true); mat[] = IdentityMat[]; return this; }
2771 
2772   /// Translate this matrix.
2773   ref NVGMatrix translate (in float tx, in float ty) return {
2774     version(aliced) pragma(inline, true);
2775     return this.mul(Translated(tx, ty));
2776   }
2777 
2778   /// Scale this matrix.
2779   ref NVGMatrix scale (in float sx, in float sy) return {
2780     version(aliced) pragma(inline, true);
2781     return this.mul(Scaled(sx, sy));
2782   }
2783 
2784   /// Rotate this matrix.
2785   ref NVGMatrix rotate (in float a) return {
2786     version(aliced) pragma(inline, true);
2787     return this.mul(Rotated(a));
2788   }
2789 
2790   /// Skew this matrix by X axis.
2791   ref NVGMatrix skewX (in float a) return {
2792     version(aliced) pragma(inline, true);
2793     return this.mul(SkewedX(a));
2794   }
2795 
2796   /// Skew this matrix by Y axis.
2797   ref NVGMatrix skewY (in float a) return {
2798     version(aliced) pragma(inline, true);
2799     return this.mul(SkewedY(a));
2800   }
2801 
2802   /// Skew this matrix by both axes.
2803   ref NVGMatrix skewY (in float ax, in float ay) return {
2804     version(aliced) pragma(inline, true);
2805     return this.mul(SkewedXY(ax, ay));
2806   }
2807 
2808   /// Transform point with this matrix. `null` destinations are allowed.
2809   /// [sx] and [sy] is the source point. [dx] and [dy] may point to the same variables.
2810   void point (float* dx, float* dy, float sx, float sy) nothrow @trusted @nogc {
2811     version(aliced) pragma(inline, true);
2812     if (dx !is null) *dx = sx*mat.ptr[0]+sy*mat.ptr[2]+mat.ptr[4];
2813     if (dy !is null) *dy = sx*mat.ptr[1]+sy*mat.ptr[3]+mat.ptr[5];
2814   }
2815 
2816   /// Transform point with this matrix.
2817   void point (ref float x, ref float y) nothrow @trusted @nogc {
2818     version(aliced) pragma(inline, true);
2819     immutable float nx = x*mat.ptr[0]+y*mat.ptr[2]+mat.ptr[4];
2820     immutable float ny = x*mat.ptr[1]+y*mat.ptr[3]+mat.ptr[5];
2821     x = nx;
2822     y = ny;
2823   }
2824 
2825   /// Sets this matrix to the result of multiplication of `this` and [s] (this * S).
2826   ref NVGMatrix mul() (const scope auto ref NVGMatrix s) {
2827     immutable float t0 = mat.ptr[0]*s.mat.ptr[0]+mat.ptr[1]*s.mat.ptr[2];
2828     immutable float t2 = mat.ptr[2]*s.mat.ptr[0]+mat.ptr[3]*s.mat.ptr[2];
2829     immutable float t4 = mat.ptr[4]*s.mat.ptr[0]+mat.ptr[5]*s.mat.ptr[2]+s.mat.ptr[4];
2830     mat.ptr[1] = mat.ptr[0]*s.mat.ptr[1]+mat.ptr[1]*s.mat.ptr[3];
2831     mat.ptr[3] = mat.ptr[2]*s.mat.ptr[1]+mat.ptr[3]*s.mat.ptr[3];
2832     mat.ptr[5] = mat.ptr[4]*s.mat.ptr[1]+mat.ptr[5]*s.mat.ptr[3]+s.mat.ptr[5];
2833     mat.ptr[0] = t0;
2834     mat.ptr[2] = t2;
2835     mat.ptr[4] = t4;
2836     return this;
2837   }
2838 
2839   /// Sets this matrix to the result of multiplication of [s] and `this` (S * this).
2840   /// Sets the transform to the result of multiplication of two transforms, of A = B*A.
2841   /// Group: matrices
2842   ref NVGMatrix premul() (const scope auto ref NVGMatrix s) {
2843     NVGMatrix s2 = s;
2844     s2.mul(this);
2845     mat[] = s2.mat[];
2846     return this;
2847   }
2848 
2849   /// Multiply this matrix by [s], return result as new matrix.
2850   /// Performs operations in this left-to-right order.
2851   NVGMatrix opBinary(string op="*") (const scope auto ref NVGMatrix s) const {
2852     version(aliced) pragma(inline, true);
2853     NVGMatrix res = this;
2854     res.mul(s);
2855     return res;
2856   }
2857 
2858   /// Multiply this matrix by [s].
2859   /// Performs operations in this left-to-right order.
2860   ref NVGMatrix opOpAssign(string op="*") (const scope auto ref NVGMatrix s) {
2861     version(aliced) pragma(inline, true);
2862     return this.mul(s);
2863   }
2864 
2865   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.
2866   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.
2867   float rotation () const { pragma(inline, true); return nvg__atan2f(mat.ptr[1], mat.ptr[0]); } /// Returns rotation of this matrix.
2868   float tx () const { pragma(inline, true); return mat.ptr[4]; } /// Returns x translation of this matrix.
2869   float ty () const { pragma(inline, true); return mat.ptr[5]; } /// Returns y translation of this matrix.
2870 
2871   ref NVGMatrix scaleX (in float v) return { pragma(inline, true); return scaleRotateTransform(v, scaleY, rotation, tx, ty); } /// Sets x scaling of this matrix.
2872   ref NVGMatrix scaleY (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, v, rotation, tx, ty); } /// Sets y scaling of this matrix.
2873   ref NVGMatrix rotation (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, scaleY, v, tx, ty); } /// Sets rotation of this matrix.
2874   ref NVGMatrix tx (in float v) return { pragma(inline, true); mat.ptr[4] = v; return this; } /// Sets x translation of this matrix.
2875   ref NVGMatrix ty (in float v) return { pragma(inline, true); mat.ptr[5] = v; return this; } /// Sets y translation of this matrix.
2876 
2877   /// Utility function to be used in `setXXX()`.
2878   /// This is the same as doing: `mat.identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster
2879   ref NVGMatrix scaleRotateTransform (in float xscale, in float yscale, in float a, in float tx, in float ty) return {
2880     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2881     mat.ptr[0] = xscale*cs; mat.ptr[1] = yscale*sn;
2882     mat.ptr[2] = xscale*-sn; mat.ptr[3] = yscale*cs;
2883     mat.ptr[4] = tx; mat.ptr[5] = ty;
2884     return this;
2885   }
2886 
2887   /// This is the same as doing: `mat.identity.rotate(a).translate(tx, ty)`, only faster
2888   ref NVGMatrix rotateTransform (in float a, in float tx, in float ty) return {
2889     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2890     mat.ptr[0] = cs; mat.ptr[1] = sn;
2891     mat.ptr[2] = -sn; mat.ptr[3] = cs;
2892     mat.ptr[4] = tx; mat.ptr[5] = ty;
2893     return this;
2894   }
2895 
2896   /// Returns new identity matrix.
2897   static NVGMatrix Identity () { pragma(inline, true); return NVGMatrix.init; }
2898 
2899   /// Returns new translation matrix.
2900   static NVGMatrix Translated (in float tx, in float ty) {
2901     version(aliced) pragma(inline, true);
2902     NVGMatrix res = void;
2903     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f;
2904     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f;
2905     res.mat.ptr[4] = tx; res.mat.ptr[5] = ty;
2906     return res;
2907   }
2908 
2909   /// Returns new scaling matrix.
2910   static NVGMatrix Scaled (in float sx, in float sy) {
2911     version(aliced) pragma(inline, true);
2912     NVGMatrix res = void;
2913     res.mat.ptr[0] = sx; res.mat.ptr[1] = 0.0f;
2914     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = sy;
2915     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2916     return res;
2917   }
2918 
2919   /// Returns new rotation matrix. Angle is specified in radians.
2920   static NVGMatrix Rotated (in float a) {
2921     version(aliced) pragma(inline, true);
2922     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2923     NVGMatrix res = void;
2924     res.mat.ptr[0] = cs; res.mat.ptr[1] = sn;
2925     res.mat.ptr[2] = -sn; res.mat.ptr[3] = cs;
2926     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2927     return res;
2928   }
2929 
2930   /// Returns new x-skewing matrix. Angle is specified in radians.
2931   static NVGMatrix SkewedX (in float a) {
2932     version(aliced) pragma(inline, true);
2933     NVGMatrix res = void;
2934     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f;
2935     res.mat.ptr[2] = nvg__tanf(a); res.mat.ptr[3] = 1.0f;
2936     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2937     return res;
2938   }
2939 
2940   /// Returns new y-skewing matrix. Angle is specified in radians.
2941   static NVGMatrix SkewedY (in float a) {
2942     version(aliced) pragma(inline, true);
2943     NVGMatrix res = void;
2944     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(a);
2945     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f;
2946     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2947     return res;
2948   }
2949 
2950   /// Returns new xy-skewing matrix. Angles are specified in radians.
2951   static NVGMatrix SkewedXY (in float ax, in float ay) {
2952     version(aliced) pragma(inline, true);
2953     NVGMatrix res = void;
2954     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(ay);
2955     res.mat.ptr[2] = nvg__tanf(ax); res.mat.ptr[3] = 1.0f;
2956     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2957     return res;
2958   }
2959 
2960   /// Utility function to be used in `setXXX()`.
2961   /// This is the same as doing: `NVGMatrix.Identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster
2962   static NVGMatrix ScaledRotatedTransformed (in float xscale, in float yscale, in float a, in float tx, in float ty) {
2963     NVGMatrix res = void;
2964     res.scaleRotateTransform(xscale, yscale, a, tx, ty);
2965     return res;
2966   }
2967 
2968   /// This is the same as doing: `NVGMatrix.Identity.rotate(a).translate(tx, ty)`, only faster
2969   static NVGMatrix RotatedTransformed (in float a, in float tx, in float ty) {
2970     NVGMatrix res = void;
2971     res.rotateTransform(a, tx, ty);
2972     return res;
2973   }
2974 }
2975 
2976 
2977 /// Converts degrees to radians.
2978 /// Group: matrices
2979 public float nvgDegToRad() (in float deg) pure nothrow @safe @nogc { pragma(inline, true); return deg/180.0f*NVG_PI; }
2980 
2981 /// Converts radians to degrees.
2982 /// Group: matrices
2983 public float nvgRadToDeg() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad/NVG_PI*180.0f; }
2984 
2985 public alias nvgDegrees = nvgDegToRad; /// Use this like `42.nvgDegrees`
2986 public float nvgRadians() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad; } /// Use this like `0.1.nvgRadians`
2987 
2988 
2989 // ////////////////////////////////////////////////////////////////////////// //
2990 void nvg__setPaintColor() (ref NVGPaint p, const scope auto ref NVGColor color) nothrow @trusted @nogc {
2991   p.clear();
2992   p.xform.identity;
2993   p.radius = 0.0f;
2994   p.feather = 1.0f;
2995   p.innerColor = p.middleColor = p.outerColor = color;
2996   p.midp = -1;
2997   p.simpleColor = true;
2998 }
2999 
3000 
3001 // ////////////////////////////////////////////////////////////////////////// //
3002 // State handling
3003 
3004 version(nanovega_debug_clipping) {
3005   public void nvgClipDumpOn (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, true); }
3006   public void nvgClipDumpOff (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, false); }
3007 }
3008 
3009 /** Pushes and saves the current render state into a state stack.
3010  * A matching [restore] must be used to restore the state.
3011  * Returns `false` if state stack overflowed.
3012  *
3013  * Group: state_handling
3014  */
3015 public bool save (NVGContext ctx) nothrow @trusted @nogc {
3016   if (ctx.nstates >= NVG_MAX_STATES) return false;
3017   if (ctx.nstates > 0) {
3018     //memcpy(&ctx.states[ctx.nstates], &ctx.states[ctx.nstates-1], NVGstate.sizeof);
3019     ctx.states[ctx.nstates] = ctx.states[ctx.nstates-1];
3020     ctx.params.renderPushClip(ctx.params.userPtr);
3021   }
3022   ++ctx.nstates;
3023   return true;
3024 }
3025 
3026 /// Pops and restores current render state.
3027 /// Group: state_handling
3028 public bool restore (NVGContext ctx) nothrow @trusted @nogc {
3029   if (ctx.nstates <= 1) return false;
3030   ctx.states[ctx.nstates-1].clearPaint();
3031   ctx.params.renderPopClip(ctx.params.userPtr);
3032   --ctx.nstates;
3033   return true;
3034 }
3035 
3036 /// Resets current render state to default values. Does not affect the render state stack.
3037 /// Group: state_handling
3038 public void reset (NVGContext ctx) nothrow @trusted @nogc {
3039   NVGstate* state = nvg__getState(ctx);
3040   state.clearPaint();
3041 
3042   nvg__setPaintColor(state.fill, nvgRGBA(255, 255, 255, 255));
3043   nvg__setPaintColor(state.stroke, nvgRGBA(0, 0, 0, 255));
3044   state.compositeOperation = nvg__compositeOperationState(NVGCompositeOperation.SourceOver);
3045   state.shapeAntiAlias = true;
3046   state.strokeWidth = 1.0f;
3047   state.miterLimit = 10.0f;
3048   state.lineCap = NVGLineCap.Butt;
3049   state.lineJoin = NVGLineCap.Miter;
3050   state.alpha = 1.0f;
3051   state.xform.identity;
3052 
3053   state.scissor.extent[] = -1.0f;
3054 
3055   state.fontSize = 16.0f;
3056   state.letterSpacing = 0.0f;
3057   state.lineHeight = 1.0f;
3058   state.fontBlur = 0.0f;
3059   state.textAlign.reset;
3060   state.fontId = 0;
3061   state.evenOddMode = false;
3062   state.dashCount = 0;
3063   state.lastFlattenDashCount = 0;
3064   state.dashStart = 0;
3065   state.firstDashIsGap = false;
3066   state.dasherActive = false;
3067 
3068   ctx.params.renderResetClip(ctx.params.userPtr);
3069 }
3070 
3071 /** Returns `true` if we have any room in state stack.
3072  * It is guaranteed to have at least 32 stack slots.
3073  *
3074  * Group: state_handling
3075  */
3076 public bool canSave (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates < NVG_MAX_STATES); }
3077 
3078 /** Returns `true` if we have any saved state.
3079  *
3080  * Group: state_handling
3081  */
3082 public bool canRestore (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates > 1); }
3083 
3084 /// Returns `true` if rendering is currently blocked.
3085 /// Group: state_handling
3086 public bool renderBlocked (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.recblockdraw : false); }
3087 
3088 /// Blocks/unblocks rendering
3089 /// Group: state_handling
3090 public void renderBlocked (NVGContext ctx, bool v) pure nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null && ctx.contextAlive) ctx.recblockdraw = v; }
3091 
3092 /// Blocks/unblocks rendering; returns previous state.
3093 /// Group: state_handling
3094 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; }
3095 
3096 
3097 // ////////////////////////////////////////////////////////////////////////// //
3098 // Render styles
3099 
3100 /// Sets filling mode to "even-odd".
3101 /// Group: render_styles
3102 public void evenOddFill (NVGContext ctx) nothrow @trusted @nogc {
3103   NVGstate* state = nvg__getState(ctx);
3104   state.evenOddMode = true;
3105 }
3106 
3107 /// Sets filling mode to "non-zero" (this is default mode).
3108 /// Group: render_styles
3109 public void nonZeroFill (NVGContext ctx) nothrow @trusted @nogc {
3110   NVGstate* state = nvg__getState(ctx);
3111   state.evenOddMode = false;
3112 }
3113 
3114 /// Sets whether to draw antialias for [stroke] and [fill]. It's enabled by default.
3115 /// Group: render_styles
3116 public void shapeAntiAlias (NVGContext ctx, bool enabled) {
3117   NVGstate* state = nvg__getState(ctx);
3118   state.shapeAntiAlias = enabled;
3119 }
3120 
3121 /// Sets the stroke width of the stroke style.
3122 /// Group: render_styles
3123 @scriptable
3124 public void strokeWidth (NVGContext ctx, float width) nothrow @trusted @nogc {
3125   NVGstate* state = nvg__getState(ctx);
3126   state.strokeWidth = width;
3127 }
3128 
3129 /// Sets the miter limit of the stroke style. Miter limit controls when a sharp corner is beveled.
3130 /// Group: render_styles
3131 public void miterLimit (NVGContext ctx, float limit) nothrow @trusted @nogc {
3132   NVGstate* state = nvg__getState(ctx);
3133   state.miterLimit = limit;
3134 }
3135 
3136 /// Sets how the end of the line (cap) is drawn,
3137 /// Can be one of: NVGLineCap.Butt (default), NVGLineCap.Round, NVGLineCap.Square.
3138 /// Group: render_styles
3139 public void lineCap (NVGContext ctx, NVGLineCap cap) nothrow @trusted @nogc {
3140   NVGstate* state = nvg__getState(ctx);
3141   state.lineCap = cap;
3142 }
3143 
3144 /// Sets how sharp path corners are drawn.
3145 /// Can be one of NVGLineCap.Miter (default), NVGLineCap.Round, NVGLineCap.Bevel.
3146 /// Group: render_styles
3147 public void lineJoin (NVGContext ctx, NVGLineCap join) nothrow @trusted @nogc {
3148   NVGstate* state = nvg__getState(ctx);
3149   state.lineJoin = join;
3150 }
3151 
3152 /// Sets stroke dashing, using (dash_length, gap_length) pairs.
3153 /// Current limit is 16 pairs.
3154 /// Resets dash start to zero.
3155 /// Group: render_styles
3156 public void setLineDash (NVGContext ctx, const(float)[] dashdata) nothrow @trusted @nogc {
3157   NVGstate* state = nvg__getState(ctx);
3158   state.dashCount = 0;
3159   state.dashStart = 0;
3160   state.firstDashIsGap = false;
3161   if (dashdata.length >= 2) {
3162     bool curFIsGap = true; // trick
3163     foreach (immutable idx, float f; dashdata) {
3164       curFIsGap = !curFIsGap;
3165       if (f < 0.01f) continue; // skip it
3166       if (idx == 0) {
3167         // register first dash
3168         state.firstDashIsGap = curFIsGap;
3169         state.dashes.ptr[state.dashCount++] = f;
3170       } else {
3171         if ((idx&1) != ((state.dashCount&1)^cast(uint)state.firstDashIsGap)) {
3172           // oops, continuation
3173           state.dashes[state.dashCount-1] += f;
3174         } else {
3175           if (state.dashCount == state.dashes.length) break;
3176           state.dashes[state.dashCount++] = f;
3177         }
3178       }
3179     }
3180     if (state.dashCount&1) {
3181       if (state.dashCount == 1) {
3182         state.dashCount = 0;
3183       } else {
3184         assert(state.dashCount < state.dashes.length);
3185         state.dashes[state.dashCount++] = 0;
3186       }
3187     }
3188     // calculate total dash path length
3189     state.totalDashLen = 0;
3190     foreach (float f; state.dashes.ptr[0..state.dashCount]) state.totalDashLen += f;
3191     if (state.totalDashLen < 0.01f) {
3192       state.dashCount = 0; // nothing to do
3193     } else {
3194       if (state.lastFlattenDashCount != 0) state.lastFlattenDashCount = uint.max; // force re-flattening
3195     }
3196   }
3197 }
3198 
3199 public alias lineDash = setLineDash; /// Ditto.
3200 
3201 /// Sets stroke dashing, using (dash_length, gap_length) pairs.
3202 /// Current limit is 16 pairs.
3203 /// Group: render_styles
3204 public void setLineDashStart (NVGContext ctx, in float dashStart) nothrow @trusted @nogc {
3205   NVGstate* state = nvg__getState(ctx);
3206   if (state.lastFlattenDashCount != 0 && state.dashStart != dashStart) {
3207     state.lastFlattenDashCount = uint.max; // force re-flattening
3208   }
3209   state.dashStart = dashStart;
3210 }
3211 
3212 public alias lineDashStart = setLineDashStart; /// Ditto.
3213 
3214 /// Sets the transparency applied to all rendered shapes.
3215 /// Already transparent paths will get proportionally more transparent as well.
3216 /// Group: render_styles
3217 public void globalAlpha (NVGContext ctx, float alpha) nothrow @trusted @nogc {
3218   NVGstate* state = nvg__getState(ctx);
3219   state.alpha = alpha;
3220 }
3221 
3222 private void strokeColor() {}
3223 
3224 static if (NanoVegaHasArsdColor) {
3225 /// Sets current stroke style to a solid color.
3226 /// Group: render_styles
3227 @scriptable
3228 public void strokeColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
3229   NVGstate* state = nvg__getState(ctx);
3230   nvg__setPaintColor(state.stroke, NVGColor(color));
3231 }
3232 }
3233 
3234 /// Sets current stroke style to a solid color.
3235 /// Group: render_styles
3236 public void strokeColor() (NVGContext ctx, const scope auto ref NVGColor color) nothrow @trusted @nogc {
3237   NVGstate* state = nvg__getState(ctx);
3238   nvg__setPaintColor(state.stroke, color);
3239 }
3240 
3241 @scriptable
3242 public void strokePaint(NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc {
3243 	strokePaint(ctx, *paint);
3244 }
3245 
3246 /// Sets current stroke style to a paint, which can be a one of the gradients or a pattern.
3247 /// Group: render_styles
3248 @scriptable
3249 public void strokePaint() (NVGContext ctx, const scope auto ref NVGPaint paint) nothrow @trusted @nogc {
3250   NVGstate* state = nvg__getState(ctx);
3251   state.stroke = paint;
3252   //nvgTransformMultiply(state.stroke.xform[], state.xform[]);
3253   state.stroke.xform.mul(state.xform);
3254 }
3255 
3256 // this is a hack to work around https://issues.dlang.org/show_bug.cgi?id=16206
3257 // for scriptable reflection. it just needs to be declared first among the overloads
3258 private void fillColor (NVGContext ctx) nothrow @trusted @nogc { }
3259 
3260 static if (NanoVegaHasArsdColor) {
3261 /// Sets current fill style to a solid color.
3262 /// Group: render_styles
3263 @scriptable
3264 public void fillColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
3265   NVGstate* state = nvg__getState(ctx);
3266   nvg__setPaintColor(state.fill, NVGColor(color));
3267 }
3268 }
3269 
3270 /// Sets current fill style to a solid color.
3271 /// Group: render_styles
3272 public void fillColor() (NVGContext ctx, const scope auto ref NVGColor color) nothrow @trusted @nogc {
3273   NVGstate* state = nvg__getState(ctx);
3274   nvg__setPaintColor(state.fill, color);
3275 
3276 }
3277 
3278 @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)
3279 public void fillPaint (NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc {
3280 	fillPaint(ctx, *paint);
3281 }
3282 
3283 /// Sets current fill style to a paint, which can be a one of the gradients or a pattern.
3284 /// Group: render_styles
3285 @scriptable
3286 public void fillPaint() (NVGContext ctx, const scope auto ref NVGPaint paint) nothrow @trusted @nogc {
3287   NVGstate* state = nvg__getState(ctx);
3288   state.fill = paint;
3289   //nvgTransformMultiply(state.fill.xform[], state.xform[]);
3290   state.fill.xform.mul(state.xform);
3291 }
3292 
3293 /// Sets current fill style to a multistop linear gradient.
3294 /// Group: render_styles
3295 public void fillPaint() (NVGContext ctx, const scope auto ref NVGLGS lgs) nothrow @trusted @nogc {
3296   if (!lgs.valid) {
3297     NVGPaint p = void;
3298     memset(&p, 0, p.sizeof);
3299     nvg__setPaintColor(p, NVGColor.red);
3300     ctx.fillPaint = p;
3301   } else if (lgs.midp >= -1) {
3302     //{ import core.stdc.stdio; printf("SIMPLE! midp=%f\n", cast(double)lgs.midp); }
3303     ctx.fillPaint = ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
3304   } else {
3305     ctx.fillPaint = ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
3306   }
3307 }
3308 
3309 /// Returns current transformation matrix.
3310 /// Group: render_transformations
3311 public NVGMatrix currTransform (NVGContext ctx) pure nothrow @trusted @nogc {
3312   NVGstate* state = nvg__getState(ctx);
3313   return state.xform;
3314 }
3315 
3316 /// Sets current transformation matrix.
3317 /// Group: render_transformations
3318 public void currTransform() (NVGContext ctx, const scope auto ref NVGMatrix m) nothrow @trusted @nogc {
3319   NVGstate* state = nvg__getState(ctx);
3320   state.xform = m;
3321 }
3322 
3323 /// Resets current transform to an identity matrix.
3324 /// Group: render_transformations
3325 @scriptable
3326 public void resetTransform (NVGContext ctx) nothrow @trusted @nogc {
3327   NVGstate* state = nvg__getState(ctx);
3328   state.xform.identity;
3329 }
3330 
3331 /// Premultiplies current coordinate system by specified matrix.
3332 /// Group: render_transformations
3333 public void transform() (NVGContext ctx, const scope auto ref NVGMatrix mt) nothrow @trusted @nogc {
3334   NVGstate* state = nvg__getState(ctx);
3335   //nvgTransformPremultiply(state.xform[], t[]);
3336   state.xform *= mt;
3337 }
3338 
3339 /// Translates current coordinate system.
3340 /// Group: render_transformations
3341 @scriptable
3342 public void translate (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
3343   NVGstate* state = nvg__getState(ctx);
3344   //NVGMatrix t = void;
3345   //nvgTransformTranslate(t[], x, y);
3346   //nvgTransformPremultiply(state.xform[], t[]);
3347   state.xform.premul(NVGMatrix.Translated(x, y));
3348 }
3349 
3350 /// Rotates current coordinate system. Angle is specified in radians.
3351 /// Group: render_transformations
3352 @scriptable
3353 public void rotate (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3354   NVGstate* state = nvg__getState(ctx);
3355   //NVGMatrix t = void;
3356   //nvgTransformRotate(t[], angle);
3357   //nvgTransformPremultiply(state.xform[], t[]);
3358   state.xform.premul(NVGMatrix.Rotated(angle));
3359 }
3360 
3361 /// Skews the current coordinate system along X axis. Angle is specified in radians.
3362 /// Group: render_transformations
3363 @scriptable
3364 public void skewX (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3365   NVGstate* state = nvg__getState(ctx);
3366   //NVGMatrix t = void;
3367   //nvgTransformSkewX(t[], angle);
3368   //nvgTransformPremultiply(state.xform[], t[]);
3369   state.xform.premul(NVGMatrix.SkewedX(angle));
3370 }
3371 
3372 /// Skews the current coordinate system along Y axis. Angle is specified in radians.
3373 /// Group: render_transformations
3374 @scriptable
3375 public void skewY (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3376   NVGstate* state = nvg__getState(ctx);
3377   //NVGMatrix t = void;
3378   //nvgTransformSkewY(t[], angle);
3379   //nvgTransformPremultiply(state.xform[], t[]);
3380   state.xform.premul(NVGMatrix.SkewedY(angle));
3381 }
3382 
3383 /// Scales the current coordinate system.
3384 /// Group: render_transformations
3385 @scriptable
3386 public void scale (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
3387   NVGstate* state = nvg__getState(ctx);
3388   //NVGMatrix t = void;
3389   //nvgTransformScale(t[], x, y);
3390   //nvgTransformPremultiply(state.xform[], t[]);
3391   state.xform.premul(NVGMatrix.Scaled(x, y));
3392 }
3393 
3394 
3395 // ////////////////////////////////////////////////////////////////////////// //
3396 // Images
3397 
3398 /// Creates image by loading it from the disk from specified file name.
3399 /// Returns handle to the image or 0 on error.
3400 /// Group: images
3401 public NVGImage createImage() (NVGContext ctx, const(char)[] filename, const(NVGImageFlag)[] imageFlagsList...) {
3402   static if (NanoVegaHasArsdImage) {
3403     import arsd.image;
3404     // do we have new arsd API to load images?
3405     static if (!is(typeof(MemoryImage.fromImageFile)) || !is(typeof(MemoryImage.clearInternal))) {
3406       static assert(0, "Sorry, your ARSD is too old. Please, update it.");
3407     }
3408     try {
3409       auto oimg = MemoryImage.fromImageFile(filename);
3410       if (auto img = cast(TrueColorImage)oimg) {
3411         scope(exit) oimg.clearInternal();
3412         return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList);
3413       } else {
3414         TrueColorImage img = oimg.getAsTrueColorImage;
3415         scope(exit) img.clearInternal();
3416         oimg.clearInternal(); // drop original image, as `getAsTrueColorImage()` MUST create a new one here
3417         oimg = null;
3418         return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList);
3419       }
3420     } catch (Exception) {}
3421     return NVGImage.init;
3422   } else {
3423     import std.internal.cstring;
3424     ubyte* img;
3425     int w, h, n;
3426     stbi_set_unpremultiply_on_load(1);
3427     stbi_convert_iphone_png_to_rgb(1);
3428     img = stbi_load(filename.tempCString, &w, &h, &n, 4);
3429     if (img is null) {
3430       //printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
3431       return NVGImage.init;
3432     }
3433     auto image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList);
3434     stbi_image_free(img);
3435     return image;
3436   }
3437 }
3438 
3439 static if (NanoVegaHasArsdImage) {
3440   /// Creates image by loading it from the specified memory image.
3441   /// Returns handle to the image or 0 on error.
3442   /// Group: images
3443   public NVGImage createImageFromMemoryImage() (NVGContext ctx, MemoryImage img, const(NVGImageFlag)[] imageFlagsList...) {
3444     if (img is null) return NVGImage.init;
3445     if (auto tc = cast(TrueColorImage)img) {
3446       return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList);
3447     } else {
3448       auto tc = img.getAsTrueColorImage;
3449       scope(exit) tc.clearInternal(); // here, it is guaranteed that `tc` is newly allocated image, so it is safe to kill it
3450       return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList);
3451     }
3452   }
3453 } else {
3454   /// Creates image by loading it from the specified chunk of memory.
3455   /// Returns handle to the image or 0 on error.
3456   /// Group: images
3457   public NVGImage createImageMem() (NVGContext ctx, const(ubyte)* data, int ndata, const(NVGImageFlag)[] imageFlagsList...) {
3458     int w, h, n, image;
3459     ubyte* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4);
3460     if (img is null) {
3461       //printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
3462       return NVGImage.init;
3463     }
3464     image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList);
3465     stbi_image_free(img);
3466     return image;
3467   }
3468 }
3469 
3470 /// Creates image from specified image data.
3471 /// Returns handle to the image or 0 on error.
3472 /// Group: images
3473 public NVGImage createImageRGBA (NVGContext ctx, int w, int h, const(void)[] data, const(NVGImageFlag)[] imageFlagsList...) nothrow @trusted @nogc {
3474   if (w < 1 || h < 1 || data.length < w*h*4) return NVGImage.init;
3475   uint imageFlags = 0;
3476   foreach (immutable uint flag; imageFlagsList) imageFlags |= flag;
3477   NVGImage res;
3478   res.id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.RGBA, w, h, imageFlags, cast(const(ubyte)*)data.ptr);
3479   if (res.id > 0) {
3480     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); }
3481     res.ctx = ctx;
3482     ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount
3483   }
3484   return res;
3485 }
3486 
3487 /// Updates image data specified by image handle.
3488 /// Group: images
3489 public void updateImage() (NVGContext ctx, auto ref NVGImage image, const(void)[] data) nothrow @trusted @nogc {
3490   if (image.valid) {
3491     int w, h;
3492     if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3493     ctx.params.renderGetTextureSize(ctx.params.userPtr, image.id, &w, &h);
3494     ctx.params.renderUpdateTexture(ctx.params.userPtr, image.id, 0, 0, w, h, cast(const(ubyte)*)data.ptr);
3495   }
3496 }
3497 
3498 /// Returns the dimensions of a created image.
3499 /// Group: images
3500 public void imageSize() (NVGContext ctx, const scope auto ref NVGImage image, out int w, out int h) nothrow @trusted @nogc {
3501   if (image.valid) {
3502     if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3503     ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, image.id, &w, &h);
3504   }
3505 }
3506 
3507 /// Deletes created image.
3508 /// Group: images
3509 public void deleteImage() (NVGContext ctx, ref NVGImage image) nothrow @trusted @nogc {
3510   if (ctx is null || !image.valid) return;
3511   if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3512   image.clear();
3513 }
3514 
3515 
3516 // ////////////////////////////////////////////////////////////////////////// //
3517 // Paints
3518 
3519 private void linearGradient() {} // hack for dmd bug
3520 
3521 static if (NanoVegaHasArsdColor) {
3522 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates
3523  * of the linear gradient, icol specifies the start color and ocol the end color.
3524  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3525  *
3526  * Group: paints
3527  */
3528 @scriptable
3529 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 {
3530   return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), NVGColor(ocol));
3531 }
3532 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
3533  * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
3534  * range `(0..1)`, and ocol the end color.
3535  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3536  *
3537  * Group: paints
3538  */
3539 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 {
3540   return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), midp, NVGColor(mcol), NVGColor(ocol));
3541 }
3542 }
3543 
3544 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates
3545  * of the linear gradient, icol specifies the start color and ocol the end color.
3546  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3547  *
3548  * Group: paints
3549  */
3550 public NVGPaint linearGradient() (NVGContext ctx, float sx, float sy, float ex, float ey, const scope auto ref NVGColor icol, const scope auto ref NVGColor ocol) nothrow @trusted @nogc {
3551   enum large = 1e5f;
3552 
3553   NVGPaint p = void;
3554   memset(&p, 0, p.sizeof);
3555   p.simpleColor = false;
3556 
3557   // Calculate transform aligned to the line
3558   float dx = ex-sx;
3559   float dy = ey-sy;
3560   immutable float d = nvg__sqrtf(dx*dx+dy*dy);
3561   if (d > 0.0001f) {
3562     dx /= d;
3563     dy /= d;
3564   } else {
3565     dx = 0;
3566     dy = 1;
3567   }
3568 
3569   p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx;
3570   p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy;
3571   p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large;
3572 
3573   p.extent.ptr[0] = large;
3574   p.extent.ptr[1] = large+d*0.5f;
3575 
3576   p.radius = 0.0f;
3577 
3578   p.feather = nvg__max(NVG_MIN_FEATHER, d);
3579 
3580   p.innerColor = p.middleColor = icol;
3581   p.outerColor = ocol;
3582   p.midp = -1;
3583 
3584   return p;
3585 }
3586 
3587 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
3588  * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
3589  * range `(0..1)`, and ocol the end color.
3590  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3591  *
3592  * Group: paints
3593  */
3594 public NVGPaint linearGradient() (NVGContext ctx, float sx, float sy, float ex, float ey, const scope auto ref NVGColor icol, in float midp, const scope auto ref NVGColor mcol, const scope auto ref NVGColor ocol) nothrow @trusted @nogc {
3595   enum large = 1e5f;
3596 
3597   NVGPaint p = void;
3598   memset(&p, 0, p.sizeof);
3599   p.simpleColor = false;
3600 
3601   // Calculate transform aligned to the line
3602   float dx = ex-sx;
3603   float dy = ey-sy;
3604   immutable float d = nvg__sqrtf(dx*dx+dy*dy);
3605   if (d > 0.0001f) {
3606     dx /= d;
3607     dy /= d;
3608   } else {
3609     dx = 0;
3610     dy = 1;
3611   }
3612 
3613   p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx;
3614   p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy;
3615   p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large;
3616 
3617   p.extent.ptr[0] = large;
3618   p.extent.ptr[1] = large+d*0.5f;
3619 
3620   p.radius = 0.0f;
3621 
3622   p.feather = nvg__max(NVG_MIN_FEATHER, d);
3623 
3624   if (midp <= 0) {
3625     p.innerColor = p.middleColor = mcol;
3626     p.midp = -1;
3627   } else if (midp > 1) {
3628     p.innerColor = p.middleColor = icol;
3629     p.midp = -1;
3630   } else {
3631     p.innerColor = icol;
3632     p.middleColor = mcol;
3633     p.midp = midp;
3634   }
3635   p.outerColor = ocol;
3636 
3637   return p;
3638 }
3639 
3640 static if (NanoVegaHasArsdColor) {
3641 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify
3642  * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
3643  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3644  *
3645  * Group: paints
3646  */
3647 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 {
3648   return ctx.radialGradient(cx, cy, inr, outr, NVGColor(icol), NVGColor(ocol));
3649 }
3650 }
3651 
3652 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify
3653  * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
3654  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3655  *
3656  * Group: paints
3657  */
3658 public NVGPaint radialGradient() (NVGContext ctx, float cx, float cy, float inr, float outr, const scope auto ref NVGColor icol, const scope auto ref NVGColor ocol) nothrow @trusted @nogc {
3659   immutable float r = (inr+outr)*0.5f;
3660   immutable float f = (outr-inr);
3661 
3662   NVGPaint p = void;
3663   memset(&p, 0, p.sizeof);
3664   p.simpleColor = false;
3665 
3666   p.xform.identity;
3667   p.xform.mat.ptr[4] = cx;
3668   p.xform.mat.ptr[5] = cy;
3669 
3670   p.extent.ptr[0] = r;
3671   p.extent.ptr[1] = r;
3672 
3673   p.radius = r;
3674 
3675   p.feather = nvg__max(NVG_MIN_FEATHER, f);
3676 
3677   p.innerColor = p.middleColor = icol;
3678   p.outerColor = ocol;
3679   p.midp = -1;
3680 
3681   return p;
3682 }
3683 
3684 static if (NanoVegaHasArsdColor) {
3685 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
3686  * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle,
3687  * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
3688  * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
3689  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3690  *
3691  * Group: paints
3692  */
3693 public NVGPaint 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 {
3694   return ctx.boxGradient(x, y, w, h, r, f, NVGColor(icol), NVGColor(ocol));
3695 }
3696 }
3697 
3698 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
3699  * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle,
3700  * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
3701  * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
3702  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3703  *
3704  * Group: paints
3705  */
3706 public NVGPaint boxGradient() (NVGContext ctx, float x, float y, float w, float h, float r, float f, const scope auto ref NVGColor icol, const scope auto ref NVGColor ocol) nothrow @trusted @nogc {
3707   NVGPaint p = void;
3708   memset(&p, 0, p.sizeof);
3709   p.simpleColor = false;
3710 
3711   p.xform.identity;
3712   p.xform.mat.ptr[4] = x+w*0.5f;
3713   p.xform.mat.ptr[5] = y+h*0.5f;
3714 
3715   p.extent.ptr[0] = w*0.5f;
3716   p.extent.ptr[1] = h*0.5f;
3717 
3718   p.radius = r;
3719 
3720   p.feather = nvg__max(NVG_MIN_FEATHER, f);
3721 
3722   p.innerColor = p.middleColor = icol;
3723   p.outerColor = ocol;
3724   p.midp = -1;
3725 
3726   return p;
3727 }
3728 
3729 /** Creates and returns an image pattern. Parameters `(cx, cy)` specify the left-top location of the image pattern,
3730  * `(w, h)` the size of one image, [angle] rotation around the top-left corner, [image] is handle to the image to render.
3731  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3732  *
3733  * Group: paints
3734  */
3735 public NVGPaint imagePattern() (NVGContext ctx, float cx, float cy, float w, float h, float angle, const scope auto ref NVGImage image, float alpha=1) nothrow @trusted @nogc {
3736   NVGPaint p = void;
3737   memset(&p, 0, p.sizeof);
3738   p.simpleColor = false;
3739 
3740   p.xform.identity.rotate(angle);
3741   p.xform.mat.ptr[4] = cx;
3742   p.xform.mat.ptr[5] = cy;
3743 
3744   p.extent.ptr[0] = w;
3745   p.extent.ptr[1] = h;
3746 
3747   p.image = image;
3748 
3749   p.innerColor = p.middleColor = p.outerColor = nvgRGBAf(1, 1, 1, alpha);
3750   p.midp = -1;
3751 
3752   return p;
3753 }
3754 
3755 /// Linear gradient with multiple stops.
3756 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3757 /// Group: paints
3758 public struct NVGLGS {
3759 private:
3760   NVGColor ic, mc, oc; // inner, middle, out
3761   float midp;
3762   NVGImage imgid;
3763   // [imagePattern] arguments
3764   float cx, cy, dimx, dimy; // dimx and dimy are ex and ey for simple gradients
3765   public float angle; ///
3766 
3767 public:
3768   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (imgid.valid || midp >= -1); } ///
3769   void clear ()  nothrow @safe @nogc { pragma(inline, true); imgid.clear(); midp = float.nan; } ///
3770 }
3771 
3772 /** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops].
3773  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3774  *
3775  * $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3776  * Group: paints
3777  */
3778 public NVGPaint asPaint() (NVGContext ctx, const scope auto ref NVGLGS lgs) nothrow @trusted @nogc {
3779   if (!lgs.valid) {
3780     NVGPaint p = void;
3781     memset(&p, 0, p.sizeof);
3782     nvg__setPaintColor(p, NVGColor.red);
3783     return p;
3784   } else if (lgs.midp >= -1) {
3785     return ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
3786   } else {
3787     return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
3788   }
3789 }
3790 
3791 /// Gradient Stop Point.
3792 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3793 /// Group: paints
3794 public struct NVGGradientStop {
3795   float offset = 0; /// [0..1]
3796   NVGColor color; ///
3797 
3798   this() (in float aofs, const scope auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } ///
3799   static if (NanoVegaHasArsdColor) {
3800     this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } ///
3801   }
3802 }
3803 
3804 /// Create linear gradient data suitable to use with `linearGradient(res)`.
3805 /// Don't forget to destroy the result when you don't need it anymore with `ctx.kill(res);`.
3806 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3807 /// Group: paints
3808 public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, const(NVGGradientStop)[] stops...) nothrow @trusted @nogc {
3809   // based on the code by Jorge Acereda <jacereda@gmail.com>
3810   enum NVG_GRADIENT_SAMPLES = 1024;
3811   static void gradientSpan (uint* dst, const(NVGGradientStop)* s0, const(NVGGradientStop)* s1) nothrow @trusted @nogc {
3812     immutable float s0o = nvg__clamp(s0.offset, 0.0f, 1.0f);
3813     immutable float s1o = nvg__clamp(s1.offset, 0.0f, 1.0f);
3814     uint s = cast(uint)(s0o*NVG_GRADIENT_SAMPLES);
3815     uint e = cast(uint)(s1o*NVG_GRADIENT_SAMPLES);
3816     uint sc = 0xffffffffU;
3817     uint sh = 24;
3818     uint r = cast(uint)(s0.color.rgba[0]*sc);
3819     uint g = cast(uint)(s0.color.rgba[1]*sc);
3820     uint b = cast(uint)(s0.color.rgba[2]*sc);
3821     uint a = cast(uint)(s0.color.rgba[3]*sc);
3822     uint dr = cast(uint)((s1.color.rgba[0]*sc-r)/(e-s));
3823     uint dg = cast(uint)((s1.color.rgba[1]*sc-g)/(e-s));
3824     uint db = cast(uint)((s1.color.rgba[2]*sc-b)/(e-s));
3825     uint da = cast(uint)((s1.color.rgba[3]*sc-a)/(e-s));
3826     dst += s;
3827     foreach (immutable _; s..e) {
3828       version(BigEndian) {
3829         *dst++ = ((r>>sh)<<24)+((g>>sh)<<16)+((b>>sh)<<8)+((a>>sh)<<0);
3830       } else {
3831         *dst++ = ((a>>sh)<<24)+((b>>sh)<<16)+((g>>sh)<<8)+((r>>sh)<<0);
3832       }
3833       r += dr;
3834       g += dg;
3835       b += db;
3836       a += da;
3837     }
3838   }
3839 
3840   NVGLGS res;
3841   res.cx = sx;
3842   res.cy = sy;
3843 
3844   if (stops.length == 2 && stops.ptr[0].offset <= 0 && stops.ptr[1].offset >= 1) {
3845     // create simple linear gradient
3846     res.ic = res.mc = stops.ptr[0].color;
3847     res.oc = stops.ptr[1].color;
3848     res.midp = -1;
3849     res.dimx = ex;
3850     res.dimy = ey;
3851   } else if (stops.length == 3 && stops.ptr[0].offset <= 0 && stops.ptr[2].offset >= 1) {
3852     // create simple linear gradient with middle stop
3853     res.ic = stops.ptr[0].color;
3854     res.mc = stops.ptr[1].color;
3855     res.oc = stops.ptr[2].color;
3856     res.midp = stops.ptr[1].offset;
3857     res.dimx = ex;
3858     res.dimy = ey;
3859   } else {
3860     // create image gradient
3861     uint[NVG_GRADIENT_SAMPLES] data = void;
3862     immutable float w = ex-sx;
3863     immutable float h = ey-sy;
3864     res.dimx = nvg__sqrtf(w*w+h*h);
3865     res.dimy = 1; //???
3866 
3867     res.angle =
3868       (/*nvg__absf(h) < 0.0001 ? 0 :
3869        nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/
3870        nvg__atan2f(h/*ey-sy*/, w/*ex-sx*/));
3871 
3872     if (stops.length > 0) {
3873       auto s0 = NVGGradientStop(0, nvgRGBAf(0, 0, 0, 1));
3874       auto s1 = NVGGradientStop(1, nvgRGBAf(1, 1, 1, 1));
3875       if (stops.length > 64) stops = stops[0..64];
3876       if (stops.length) {
3877         s0.color = stops[0].color;
3878         s1.color = stops[$-1].color;
3879       }
3880       gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1));
3881       foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1);
3882       gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1);
3883       res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[]/*, NVGImageFlag.RepeatX, NVGImageFlag.RepeatY*/);
3884     }
3885   }
3886   return res;
3887 }
3888 
3889 
3890 // ////////////////////////////////////////////////////////////////////////// //
3891 // Scissoring
3892 
3893 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform.
3894 /// Group: scissoring
3895 public void scissor (NVGContext ctx, in float x, in float y, float w, float h) nothrow @trusted @nogc {
3896   NVGstate* state = nvg__getState(ctx);
3897 
3898   w = nvg__max(0.0f, w);
3899   h = nvg__max(0.0f, h);
3900 
3901   state.scissor.xform.identity;
3902   state.scissor.xform.mat.ptr[4] = x+w*0.5f;
3903   state.scissor.xform.mat.ptr[5] = y+h*0.5f;
3904   //nvgTransformMultiply(state.scissor.xform[], state.xform[]);
3905   state.scissor.xform.mul(state.xform);
3906 
3907   state.scissor.extent.ptr[0] = w*0.5f;
3908   state.scissor.extent.ptr[1] = h*0.5f;
3909 }
3910 
3911 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform.
3912 /// Arguments: [x, y, w, h]*
3913 /// Group: scissoring
3914 public void scissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
3915   enum ArgC = 4;
3916   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [scissor] call");
3917   if (args.length < ArgC) return;
3918   NVGstate* state = nvg__getState(ctx);
3919   const(float)* aptr = args.ptr;
3920   foreach (immutable idx; 0..args.length/ArgC) {
3921     immutable x = *aptr++;
3922     immutable y = *aptr++;
3923     immutable w = nvg__max(0.0f, *aptr++);
3924     immutable h = nvg__max(0.0f, *aptr++);
3925 
3926     state.scissor.xform.identity;
3927     state.scissor.xform.mat.ptr[4] = x+w*0.5f;
3928     state.scissor.xform.mat.ptr[5] = y+h*0.5f;
3929     //nvgTransformMultiply(state.scissor.xform[], state.xform[]);
3930     state.scissor.xform.mul(state.xform);
3931 
3932     state.scissor.extent.ptr[0] = w*0.5f;
3933     state.scissor.extent.ptr[1] = h*0.5f;
3934   }
3935 }
3936 
3937 void nvg__isectRects (float* dst, float ax, float ay, float aw, float ah, float bx, float by, float bw, float bh) nothrow @trusted @nogc {
3938   immutable float minx = nvg__max(ax, bx);
3939   immutable float miny = nvg__max(ay, by);
3940   immutable float maxx = nvg__min(ax+aw, bx+bw);
3941   immutable float maxy = nvg__min(ay+ah, by+bh);
3942   dst[0] = minx;
3943   dst[1] = miny;
3944   dst[2] = nvg__max(0.0f, maxx-minx);
3945   dst[3] = nvg__max(0.0f, maxy-miny);
3946 }
3947 
3948 /** Intersects current scissor rectangle with the specified rectangle.
3949  * The scissor rectangle is transformed by the current transform.
3950  * Note: in case the rotation of previous scissor rect differs from
3951  * the current one, the intersection will be done between the specified
3952  * rectangle and the previous scissor rectangle transformed in the current
3953  * transform space. The resulting shape is always rectangle.
3954  *
3955  * Group: scissoring
3956  */
3957 public void intersectScissor (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc {
3958   NVGstate* state = nvg__getState(ctx);
3959 
3960   // If no previous scissor has been set, set the scissor as current scissor.
3961   if (state.scissor.extent.ptr[0] < 0) {
3962     ctx.scissor(x, y, w, h);
3963     return;
3964   }
3965 
3966   NVGMatrix pxform = void;
3967   NVGMatrix invxorm = void;
3968   float[4] rect = void;
3969 
3970   // Transform the current scissor rect into current transform space.
3971   // If there is difference in rotation, this will be approximation.
3972   //memcpy(pxform.mat.ptr, state.scissor.xform.ptr, float.sizeof*6);
3973   pxform = state.scissor.xform;
3974   immutable float ex = state.scissor.extent.ptr[0];
3975   immutable float ey = state.scissor.extent.ptr[1];
3976   //nvgTransformInverse(invxorm[], state.xform[]);
3977   invxorm = state.xform.inverted;
3978   //nvgTransformMultiply(pxform[], invxorm[]);
3979   pxform.mul(invxorm);
3980   immutable float tex = ex*nvg__absf(pxform.mat.ptr[0])+ey*nvg__absf(pxform.mat.ptr[2]);
3981   immutable float tey = ex*nvg__absf(pxform.mat.ptr[1])+ey*nvg__absf(pxform.mat.ptr[3]);
3982 
3983   // Intersect rects.
3984   nvg__isectRects(rect.ptr, pxform.mat.ptr[4]-tex, pxform.mat.ptr[5]-tey, tex*2, tey*2, x, y, w, h);
3985 
3986   //ctx.scissor(rect.ptr[0], rect.ptr[1], rect.ptr[2], rect.ptr[3]);
3987   ctx.scissor(rect.ptr[0..4]);
3988 }
3989 
3990 /** Intersects current scissor rectangle with the specified rectangle.
3991  * The scissor rectangle is transformed by the current transform.
3992  * Note: in case the rotation of previous scissor rect differs from
3993  * the current one, the intersection will be done between the specified
3994  * rectangle and the previous scissor rectangle transformed in the current
3995  * transform space. The resulting shape is always rectangle.
3996  *
3997  * Arguments: [x, y, w, h]*
3998  *
3999  * Group: scissoring
4000  */
4001 public void intersectScissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
4002   enum ArgC = 4;
4003   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [intersectScissor] call");
4004   if (args.length < ArgC) return;
4005   const(float)* aptr = args.ptr;
4006   foreach (immutable idx; 0..args.length/ArgC) {
4007     immutable x = *aptr++;
4008     immutable y = *aptr++;
4009     immutable w = *aptr++;
4010     immutable h = *aptr++;
4011     ctx.intersectScissor(x, y, w, h);
4012   }
4013 }
4014 
4015 /// Reset and disables scissoring.
4016 /// Group: scissoring
4017 public void resetScissor (NVGContext ctx) nothrow @trusted @nogc {
4018   NVGstate* state = nvg__getState(ctx);
4019   state.scissor.xform.mat[] = 0.0f;
4020   state.scissor.extent[] = -1.0f;
4021 }
4022 
4023 
4024 // ////////////////////////////////////////////////////////////////////////// //
4025 // Render-Time Affine Transformations
4026 
4027 /// Sets GPU affine transformatin matrix. Don't do scaling or skewing here.
4028 /// This matrix won't be saved/restored with context state save/restore operations, as it is not a part of that state.
4029 /// Group: gpu_affine
4030 public void affineGPU() (NVGContext ctx, const scope auto ref NVGMatrix mat) nothrow @trusted @nogc {
4031   ctx.gpuAffine = mat;
4032   ctx.params.renderSetAffine(ctx.params.userPtr, ctx.gpuAffine);
4033 }
4034 
4035 /// Get current GPU affine transformatin matrix.
4036 /// Group: gpu_affine
4037 public NVGMatrix affineGPU (NVGContext ctx) nothrow @safe @nogc {
4038   pragma(inline, true);
4039   return ctx.gpuAffine;
4040 }
4041 
4042 /// "Untransform" point using current GPU affine matrix.
4043 /// Group: gpu_affine
4044 public void gpuUntransformPoint (NVGContext ctx, float *dx, float *dy, in float x, in float y) nothrow @safe @nogc {
4045   if (ctx.gpuAffine.isIdentity) {
4046     if (dx !is null) *dx = x;
4047     if (dy !is null) *dy = y;
4048   } else {
4049     // inverse GPU transformation
4050     NVGMatrix igpu = ctx.gpuAffine.inverted;
4051     igpu.point(dx, dy, x, y);
4052   }
4053 }
4054 
4055 
4056 // ////////////////////////////////////////////////////////////////////////// //
4057 // rasterization (tesselation) code
4058 
4059 int nvg__ptEquals (float x1, float y1, float x2, float y2, float tol) pure nothrow @safe @nogc {
4060   //pragma(inline, true);
4061   immutable float dx = x2-x1;
4062   immutable float dy = y2-y1;
4063   return dx*dx+dy*dy < tol*tol;
4064 }
4065 
4066 float nvg__distPtSeg (float x, float y, float px, float py, float qx, float qy) pure nothrow @safe @nogc {
4067   immutable float pqx = qx-px;
4068   immutable float pqy = qy-py;
4069   float dx = x-px;
4070   float dy = y-py;
4071   immutable float d = pqx*pqx+pqy*pqy;
4072   float t = pqx*dx+pqy*dy;
4073   if (d > 0) t /= d;
4074   if (t < 0) t = 0; else if (t > 1) t = 1;
4075   dx = px+t*pqx-x;
4076   dy = py+t*pqy-y;
4077   return dx*dx+dy*dy;
4078 }
4079 
4080 void nvg__appendCommands(bool useCommand=true) (NVGContext ctx, Command acmd, const(float)[] vals...) nothrow @trusted @nogc {
4081   int nvals = cast(int)vals.length;
4082   static if (useCommand) {
4083     enum addon = 1;
4084   } else {
4085     enum addon = 0;
4086     if (nvals == 0) return; // nothing to do
4087   }
4088 
4089   NVGstate* state = nvg__getState(ctx);
4090 
4091   if (ctx.ncommands+nvals+addon > ctx.ccommands) {
4092     //int ccommands = ctx.ncommands+nvals+ctx.ccommands/2;
4093     int ccommands = ((ctx.ncommands+(nvals+addon))|0xfff)+1;
4094     float* commands = cast(float*)realloc(ctx.commands, float.sizeof*ccommands);
4095     if (commands is null) assert(0, "NanoVega: out of memory");
4096     ctx.commands = commands;
4097     ctx.ccommands = ccommands;
4098     assert(ctx.ncommands+(nvals+addon) <= ctx.ccommands);
4099   }
4100 
4101   static if (!useCommand) acmd = cast(Command)vals.ptr[0];
4102 
4103   if (acmd != Command.Close && acmd != Command.Winding) {
4104     //assert(nvals+addon >= 3);
4105     ctx.commandx = vals.ptr[nvals-2];
4106     ctx.commandy = vals.ptr[nvals-1];
4107   }
4108 
4109   // copy commands
4110   float* vp = ctx.commands+ctx.ncommands;
4111   static if (useCommand) {
4112     vp[0] = cast(float)acmd;
4113     if (nvals > 0) memcpy(vp+1, vals.ptr, nvals*float.sizeof);
4114   } else {
4115     memcpy(vp, vals.ptr, nvals*float.sizeof);
4116   }
4117   ctx.ncommands += nvals+addon;
4118 
4119   // transform commands
4120   int i = nvals+addon;
4121   while (i > 0) {
4122     int nlen = 1;
4123     final switch (cast(Command)(*vp)) {
4124       case Command.MoveTo:
4125       case Command.LineTo:
4126         assert(i >= 3);
4127         state.xform.point(vp+1, vp+2, vp[1], vp[2]);
4128         nlen = 3;
4129         break;
4130       case Command.BezierTo:
4131         assert(i >= 7);
4132         state.xform.point(vp+1, vp+2, vp[1], vp[2]);
4133         state.xform.point(vp+3, vp+4, vp[3], vp[4]);
4134         state.xform.point(vp+5, vp+6, vp[5], vp[6]);
4135         nlen = 7;
4136         break;
4137       case Command.Close:
4138         nlen = 1;
4139         break;
4140       case Command.Winding:
4141         nlen = 2;
4142         break;
4143     }
4144     assert(nlen > 0 && nlen <= i);
4145     i -= nlen;
4146     vp += nlen;
4147   }
4148 }
4149 
4150 void nvg__clearPathCache (NVGContext ctx) nothrow @trusted @nogc {
4151   // no need to clear paths, as data is not copied there
4152   //foreach (ref p; ctx.cache.paths[0..ctx.cache.npaths]) p.clear();
4153   ctx.cache.npoints = 0;
4154   ctx.cache.npaths = 0;
4155   ctx.cache.fillReady = ctx.cache.strokeReady = false;
4156   ctx.cache.clipmode = NVGClipMode.None;
4157 }
4158 
4159 NVGpath* nvg__lastPath (NVGContext ctx) nothrow @trusted @nogc {
4160   return (ctx.cache.npaths > 0 ? &ctx.cache.paths[ctx.cache.npaths-1] : null);
4161 }
4162 
4163 void nvg__addPath (NVGContext ctx) nothrow @trusted @nogc {
4164   import core.stdc.stdlib : realloc;
4165   import core.stdc.string : memset;
4166 
4167   if (ctx.cache.npaths+1 > ctx.cache.cpaths) {
4168     int cpaths = ctx.cache.npaths+1+ctx.cache.cpaths/2;
4169     NVGpath* paths = cast(NVGpath*)realloc(ctx.cache.paths, NVGpath.sizeof*cpaths);
4170     if (paths is null) assert(0, "NanoVega: out of memory");
4171     ctx.cache.paths = paths;
4172     ctx.cache.cpaths = cpaths;
4173   }
4174 
4175   NVGpath* path = &ctx.cache.paths[ctx.cache.npaths++];
4176   memset(path, 0, NVGpath.sizeof);
4177   path.first = ctx.cache.npoints;
4178   path.mWinding = NVGWinding.CCW;
4179 }
4180 
4181 NVGpoint* nvg__lastPoint (NVGContext ctx) nothrow @trusted @nogc {
4182   return (ctx.cache.npoints > 0 ? &ctx.cache.points[ctx.cache.npoints-1] : null);
4183 }
4184 
4185 void nvg__addPoint (NVGContext ctx, float x, float y, int flags) nothrow @trusted @nogc {
4186   NVGpath* path = nvg__lastPath(ctx);
4187   if (path is null) return;
4188 
4189   if (path.count > 0 && ctx.cache.npoints > 0) {
4190     NVGpoint* pt = nvg__lastPoint(ctx);
4191     if (nvg__ptEquals(pt.x, pt.y, x, y, ctx.distTol)) {
4192       pt.flags |= flags;
4193       return;
4194     }
4195   }
4196 
4197   if (ctx.cache.npoints+1 > ctx.cache.cpoints) {
4198     int cpoints = ctx.cache.npoints+1+ctx.cache.cpoints/2;
4199     NVGpoint* points = cast(NVGpoint*)realloc(ctx.cache.points, NVGpoint.sizeof*cpoints);
4200     if (points is null) return;
4201     ctx.cache.points = points;
4202     ctx.cache.cpoints = cpoints;
4203   }
4204 
4205   NVGpoint* pt = &ctx.cache.points[ctx.cache.npoints];
4206   memset(pt, 0, (*pt).sizeof);
4207   pt.x = x;
4208   pt.y = y;
4209   pt.flags = cast(ubyte)flags;
4210 
4211   ++ctx.cache.npoints;
4212   ++path.count;
4213 }
4214 
4215 void nvg__closePath (NVGContext ctx) nothrow @trusted @nogc {
4216   NVGpath* path = nvg__lastPath(ctx);
4217   if (path is null) return;
4218   path.closed = true;
4219 }
4220 
4221 void nvg__pathWinding (NVGContext ctx, NVGWinding winding) nothrow @trusted @nogc {
4222   NVGpath* path = nvg__lastPath(ctx);
4223   if (path is null) return;
4224   path.mWinding = winding;
4225 }
4226 
4227 float nvg__getAverageScale() (const scope auto ref NVGMatrix t) /*pure*/ nothrow @trusted @nogc {
4228   immutable float sx = nvg__sqrtf(t.mat.ptr[0]*t.mat.ptr[0]+t.mat.ptr[2]*t.mat.ptr[2]);
4229   immutable float sy = nvg__sqrtf(t.mat.ptr[1]*t.mat.ptr[1]+t.mat.ptr[3]*t.mat.ptr[3]);
4230   return (sx+sy)*0.5f;
4231 }
4232 
4233 NVGVertex* nvg__allocTempVerts (NVGContext ctx, int nverts) nothrow @trusted @nogc {
4234   if (nverts > ctx.cache.cverts) {
4235     int cverts = (nverts+0xff)&~0xff; // Round up to prevent allocations when things change just slightly.
4236     NVGVertex* verts = cast(NVGVertex*)realloc(ctx.cache.verts, NVGVertex.sizeof*cverts);
4237     if (verts is null) return null;
4238     ctx.cache.verts = verts;
4239     ctx.cache.cverts = cverts;
4240   }
4241 
4242   return ctx.cache.verts;
4243 }
4244 
4245 float nvg__triarea2 (float ax, float ay, float bx, float by, float cx, float cy) pure nothrow @safe @nogc {
4246   immutable float abx = bx-ax;
4247   immutable float aby = by-ay;
4248   immutable float acx = cx-ax;
4249   immutable float acy = cy-ay;
4250   return acx*aby-abx*acy;
4251 }
4252 
4253 float nvg__polyArea (NVGpoint* pts, int npts) nothrow @trusted @nogc {
4254   float area = 0;
4255   foreach (int i; 2..npts) {
4256     NVGpoint* a = &pts[0];
4257     NVGpoint* b = &pts[i-1];
4258     NVGpoint* c = &pts[i];
4259     area += nvg__triarea2(a.x, a.y, b.x, b.y, c.x, c.y);
4260   }
4261   return area*0.5f;
4262 }
4263 
4264 void nvg__polyReverse (NVGpoint* pts, int npts) nothrow @trusted @nogc {
4265   NVGpoint tmp = void;
4266   int i = 0, j = npts-1;
4267   while (i < j) {
4268     tmp = pts[i];
4269     pts[i] = pts[j];
4270     pts[j] = tmp;
4271     ++i;
4272     --j;
4273   }
4274 }
4275 
4276 void nvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc {
4277   vtx.x = x;
4278   vtx.y = y;
4279   vtx.u = u;
4280   vtx.v = v;
4281 }
4282 
4283 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 {
4284   if (level > 10) return;
4285 
4286   // check for collinear points, and use AFD tesselator on such curves (it is WAY faster for this case)
4287   /*
4288   if (level == 0 && ctx.tesselatortype == NVGTesselation.Combined) {
4289     static bool collinear (in float v0x, in float v0y, in float v1x, in float v1y, in float v2x, in float v2y) nothrow @trusted @nogc {
4290       immutable float cz = (v1x-v0x)*(v2y-v0y)-(v2x-v0x)*(v1y-v0y);
4291       return (nvg__absf(cz*cz) <= 0.01f); // arbitrary number, seems to work ok with NanoSVG output
4292     }
4293     if (collinear(x1, y1, x2, y2, x3, y3) && collinear(x2, y2, x3, y3, x3, y4)) {
4294       //{ import core.stdc.stdio; printf("AFD fallback!\n"); }
4295       ctx.nvg__tesselateBezierAFD(x1, y1, x2, y2, x3, y3, x4, y4, type);
4296       return;
4297     }
4298   }
4299   */
4300 
4301   immutable float x12 = (x1+x2)*0.5f;
4302   immutable float y12 = (y1+y2)*0.5f;
4303   immutable float x23 = (x2+x3)*0.5f;
4304   immutable float y23 = (y2+y3)*0.5f;
4305   immutable float x34 = (x3+x4)*0.5f;
4306   immutable float y34 = (y3+y4)*0.5f;
4307   immutable float x123 = (x12+x23)*0.5f;
4308   immutable float y123 = (y12+y23)*0.5f;
4309 
4310   immutable float dx = x4-x1;
4311   immutable float dy = y4-y1;
4312   immutable float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx));
4313   immutable float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx));
4314 
4315   if ((d2+d3)*(d2+d3) < ctx.tessTol*(dx*dx+dy*dy)) {
4316     nvg__addPoint(ctx, x4, y4, type);
4317     return;
4318   }
4319 
4320   immutable float x234 = (x23+x34)*0.5f;
4321   immutable float y234 = (y23+y34)*0.5f;
4322   immutable float x1234 = (x123+x234)*0.5f;
4323   immutable float y1234 = (y123+y234)*0.5f;
4324 
4325   // "taxicab" / "manhattan" check for flat curves
4326   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) {
4327     nvg__addPoint(ctx, x1234, y1234, type);
4328     return;
4329   }
4330 
4331   nvg__tesselateBezier(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
4332   nvg__tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
4333 }
4334 
4335 // based on the ideas and code of Maxim Shemanarev. Rest in Peace, bro!
4336 // see http://www.antigrain.com/research/adaptive_bezier/index.html
4337 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 {
4338   enum CollinearEPS = 0.00000001f; // 0.00001f;
4339   enum AngleTolEPS = 0.01f;
4340 
4341   static float distSquared (in float x1, in float y1, in float x2, in float y2) pure nothrow @safe @nogc {
4342     pragma(inline, true);
4343     immutable float dx = x2-x1;
4344     immutable float dy = y2-y1;
4345     return dx*dx+dy*dy;
4346   }
4347 
4348   if (level == 0) {
4349     nvg__addPoint(ctx, x1, y1, 0);
4350     nvg__tesselateBezierMcSeem(ctx, x1, y1, x2, y2, x3, y3, x4, y4, 1, type);
4351     nvg__addPoint(ctx, x4, y4, type);
4352     return;
4353   }
4354 
4355   if (level >= 32) return; // recurse limit; practically, it should be never reached, but...
4356 
4357   // calculate all the mid-points of the line segments
4358   immutable float x12 = (x1+x2)*0.5f;
4359   immutable float y12 = (y1+y2)*0.5f;
4360   immutable float x23 = (x2+x3)*0.5f;
4361   immutable float y23 = (y2+y3)*0.5f;
4362   immutable float x34 = (x3+x4)*0.5f;
4363   immutable float y34 = (y3+y4)*0.5f;
4364   immutable float x123 = (x12+x23)*0.5f;
4365   immutable float y123 = (y12+y23)*0.5f;
4366   immutable float x234 = (x23+x34)*0.5f;
4367   immutable float y234 = (y23+y34)*0.5f;
4368   immutable float x1234 = (x123+x234)*0.5f;
4369   immutable float y1234 = (y123+y234)*0.5f;
4370 
4371   // try to approximate the full cubic curve by a single straight line
4372   immutable float dx = x4-x1;
4373   immutable float dy = y4-y1;
4374 
4375   float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx));
4376   float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx));
4377   //immutable float da1, da2, k;
4378 
4379   final switch ((cast(int)(d2 > CollinearEPS)<<1)+cast(int)(d3 > CollinearEPS)) {
4380     case 0:
4381       // all collinear or p1 == p4
4382       float k = dx*dx+dy*dy;
4383       if (k == 0) {
4384         d2 = distSquared(x1, y1, x2, y2);
4385         d3 = distSquared(x4, y4, x3, y3);
4386       } else {
4387         k = 1.0f/k;
4388         float da1 = x2-x1;
4389         float da2 = y2-y1;
4390         d2 = k*(da1*dx+da2*dy);
4391         da1 = x3-x1;
4392         da2 = y3-y1;
4393         d3 = k*(da1*dx+da2*dy);
4394         if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) {
4395           // Simple collinear case, 1---2---3---4
4396           // We can leave just two endpoints
4397           return;
4398         }
4399              if (d2 <= 0) d2 = distSquared(x2, y2, x1, y1);
4400         else if (d2 >= 1) d2 = distSquared(x2, y2, x4, y4);
4401         else d2 = distSquared(x2, y2, x1+d2*dx, y1+d2*dy);
4402 
4403              if (d3 <= 0) d3 = distSquared(x3, y3, x1, y1);
4404         else if (d3 >= 1) d3 = distSquared(x3, y3, x4, y4);
4405         else d3 = distSquared(x3, y3, x1+d3*dx, y1+d3*dy);
4406       }
4407       if (d2 > d3) {
4408         if (d2 < ctx.tessTol) {
4409           nvg__addPoint(ctx, x2, y2, type);
4410           return;
4411         }
4412       } if (d3 < ctx.tessTol) {
4413         nvg__addPoint(ctx, x3, y3, type);
4414         return;
4415       }
4416       break;
4417     case 1:
4418       // p1,p2,p4 are collinear, p3 is significant
4419       if (d3*d3 <= ctx.tessTol*(dx*dx+dy*dy)) {
4420         if (ctx.angleTol < AngleTolEPS) {
4421           nvg__addPoint(ctx, x23, y23, type);
4422           return;
4423         } else {
4424           // angle condition
4425           float da1 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-nvg__atan2f(y3-y2, x3-x2));
4426           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4427           if (da1 < ctx.angleTol) {
4428             nvg__addPoint(ctx, x2, y2, type);
4429             nvg__addPoint(ctx, x3, y3, type);
4430             return;
4431           }
4432           if (ctx.cuspLimit != 0.0) {
4433             if (da1 > ctx.cuspLimit) {
4434               nvg__addPoint(ctx, x3, y3, type);
4435               return;
4436             }
4437           }
4438         }
4439       }
4440       break;
4441     case 2:
4442       // p1,p3,p4 are collinear, p2 is significant
4443       if (d2*d2 <= ctx.tessTol*(dx*dx+dy*dy)) {
4444         if (ctx.angleTol < AngleTolEPS) {
4445           nvg__addPoint(ctx, x23, y23, type);
4446           return;
4447         } else {
4448           // angle condition
4449           float da1 = nvg__absf(nvg__atan2f(y3-y2, x3-x2)-nvg__atan2f(y2-y1, x2-x1));
4450           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4451           if (da1 < ctx.angleTol) {
4452             nvg__addPoint(ctx, x2, y2, type);
4453             nvg__addPoint(ctx, x3, y3, type);
4454             return;
4455           }
4456           if (ctx.cuspLimit != 0.0) {
4457             if (da1 > ctx.cuspLimit) {
4458               nvg__addPoint(ctx, x2, y2, type);
4459               return;
4460             }
4461           }
4462         }
4463       }
4464       break;
4465     case 3:
4466       // regular case
4467       if ((d2+d3)*(d2+d3) <= ctx.tessTol*(dx*dx+dy*dy)) {
4468         // if the curvature doesn't exceed the distance tolerance value, we tend to finish subdivisions
4469         if (ctx.angleTol < AngleTolEPS) {
4470           nvg__addPoint(ctx, x23, y23, type);
4471           return;
4472         } else {
4473           // angle and cusp condition
4474           immutable float k = nvg__atan2f(y3-y2, x3-x2);
4475           float da1 = nvg__absf(k-nvg__atan2f(y2-y1, x2-x1));
4476           float da2 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-k);
4477           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4478           if (da2 >= NVG_PI) da2 = 2*NVG_PI-da2;
4479           if (da1+da2 < ctx.angleTol) {
4480             // finally we can stop the recursion
4481             nvg__addPoint(ctx, x23, y23, type);
4482             return;
4483           }
4484           if (ctx.cuspLimit != 0.0) {
4485             if (da1 > ctx.cuspLimit) {
4486               nvg__addPoint(ctx, x2, y2, type);
4487               return;
4488             }
4489             if (da2 > ctx.cuspLimit) {
4490               nvg__addPoint(ctx, x3, y3, type);
4491               return;
4492             }
4493           }
4494         }
4495       }
4496       break;
4497   }
4498 
4499   // continue subdivision
4500   nvg__tesselateBezierMcSeem(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
4501   nvg__tesselateBezierMcSeem(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
4502 }
4503 
4504 
4505 // Adaptive forward differencing for bezier tesselation.
4506 // See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt.
4507 // "Adaptive forward differencing for rendering curves and surfaces."
4508 // ACM SIGGRAPH Computer Graphics. Vol. 21. No. 4. ACM, 1987.
4509 // original code by Taylor Holliday <taylor@audulus.com>
4510 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 {
4511   enum AFD_ONE = (1<<10);
4512 
4513   // power basis
4514   immutable float ax = -x1+3*x2-3*x3+x4;
4515   immutable float ay = -y1+3*y2-3*y3+y4;
4516   immutable float bx = 3*x1-6*x2+3*x3;
4517   immutable float by = 3*y1-6*y2+3*y3;
4518   immutable float cx = -3*x1+3*x2;
4519   immutable float cy = -3*y1+3*y2;
4520 
4521   // Transform to forward difference basis (stepsize 1)
4522   float px = x1;
4523   float py = y1;
4524   float dx = ax+bx+cx;
4525   float dy = ay+by+cy;
4526   float ddx = 6*ax+2*bx;
4527   float ddy = 6*ay+2*by;
4528   float dddx = 6*ax;
4529   float dddy = 6*ay;
4530 
4531   //printf("dx: %f, dy: %f\n", dx, dy);
4532   //printf("ddx: %f, ddy: %f\n", ddx, ddy);
4533   //printf("dddx: %f, dddy: %f\n", dddx, dddy);
4534 
4535   int t = 0;
4536   int dt = AFD_ONE;
4537 
4538   immutable float tol = ctx.tessTol*4;
4539 
4540   while (t < AFD_ONE) {
4541     // Flatness measure.
4542     float d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4543 
4544     // printf("d: %f, th: %f\n", d, th);
4545 
4546     // Go to higher resolution if we're moving a lot or overshooting the end.
4547     while ((d > tol && dt > 1) || (t+dt > AFD_ONE)) {
4548       // printf("up\n");
4549 
4550       // Apply L to the curve. Increase curve resolution.
4551       dx = 0.5f*dx-(1.0f/8.0f)*ddx+(1.0f/16.0f)*dddx;
4552       dy = 0.5f*dy-(1.0f/8.0f)*ddy+(1.0f/16.0f)*dddy;
4553       ddx = (1.0f/4.0f)*ddx-(1.0f/8.0f)*dddx;
4554       ddy = (1.0f/4.0f)*ddy-(1.0f/8.0f)*dddy;
4555       dddx = (1.0f/8.0f)*dddx;
4556       dddy = (1.0f/8.0f)*dddy;
4557 
4558       // Half the stepsize.
4559       dt >>= 1;
4560 
4561       // Recompute d
4562       d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4563     }
4564 
4565     // Go to lower resolution if we're really flat
4566     // and we aren't going to overshoot the end.
4567     // XXX: tol/32 is just a guess for when we are too flat.
4568     while ((d > 0 && d < tol/32.0f && dt < AFD_ONE) && (t+2*dt <= AFD_ONE)) {
4569       // printf("down\n");
4570 
4571       // Apply L^(-1) to the curve. Decrease curve resolution.
4572       dx = 2*dx+ddx;
4573       dy = 2*dy+ddy;
4574       ddx = 4*ddx+4*dddx;
4575       ddy = 4*ddy+4*dddy;
4576       dddx = 8*dddx;
4577       dddy = 8*dddy;
4578 
4579       // Double the stepsize.
4580       dt <<= 1;
4581 
4582       // Recompute d
4583       d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4584     }
4585 
4586     // Forward differencing.
4587     px += dx;
4588     py += dy;
4589     dx += ddx;
4590     dy += ddy;
4591     ddx += dddx;
4592     ddy += dddy;
4593 
4594     // Output a point.
4595     nvg__addPoint(ctx, px, py, (t > 0 ? type : 0));
4596 
4597     // Advance along the curve.
4598     t += dt;
4599 
4600     // Ensure we don't overshoot.
4601     assert(t <= AFD_ONE);
4602   }
4603 }
4604 
4605 
4606 void nvg__dashLastPath (NVGContext ctx) nothrow @trusted @nogc {
4607   import core.stdc.stdlib : realloc;
4608   import core.stdc.string : memcpy;
4609 
4610   NVGpathCache* cache = ctx.cache;
4611   if (cache.npaths == 0) return;
4612 
4613   NVGpath* path = nvg__lastPath(ctx);
4614   if (path is null) return;
4615 
4616   NVGstate* state = nvg__getState(ctx);
4617   if (!state.dasherActive) return;
4618 
4619   static NVGpoint* pts = null;
4620   static uint ptsCount = 0;
4621   static uint ptsSize = 0;
4622 
4623   if (path.count < 2) return; // just in case
4624 
4625   // copy path points (reserve one point for closed pathes)
4626   if (ptsSize < path.count+1) {
4627     ptsSize = cast(uint)(path.count+1);
4628     pts = cast(NVGpoint*)realloc(pts, ptsSize*NVGpoint.sizeof);
4629     if (pts is null) assert(0, "NanoVega: out of memory");
4630   }
4631   ptsCount = cast(uint)path.count;
4632   memcpy(pts, &cache.points[path.first], ptsCount*NVGpoint.sizeof);
4633   // add closing point for closed pathes
4634   if (path.closed && !nvg__ptEquals(pts[0].x, pts[0].y, pts[ptsCount-1].x, pts[ptsCount-1].y, ctx.distTol)) {
4635     pts[ptsCount++] = pts[0];
4636   }
4637 
4638   // remove last path (with its points)
4639   --cache.npaths;
4640   cache.npoints -= path.count;
4641 
4642   // add stroked pathes
4643   const(float)* dashes = state.dashes.ptr;
4644   immutable uint dashCount = state.dashCount;
4645   float currDashStart = 0;
4646   uint currDashIdx = 0;
4647   immutable bool firstIsGap = state.firstDashIsGap;
4648 
4649   // calculate lengthes
4650   {
4651     NVGpoint* v1 = &pts[0];
4652     NVGpoint* v2 = &pts[1];
4653     foreach (immutable _; 0..ptsCount) {
4654       float dx = v2.x-v1.x;
4655       float dy = v2.y-v1.y;
4656       v1.len = nvg__normalize(&dx, &dy);
4657       v1 = v2++;
4658     }
4659   }
4660 
4661   void calcDashStart (float ds) {
4662     if (ds < 0) {
4663       ds = ds%state.totalDashLen;
4664       while (ds < 0) ds += state.totalDashLen;
4665     }
4666     currDashIdx = 0;
4667     currDashStart = 0;
4668     while (ds > 0) {
4669       if (ds > dashes[currDashIdx]) {
4670         ds -= dashes[currDashIdx];
4671         ++currDashIdx;
4672         currDashStart = 0;
4673         if (currDashIdx >= dashCount) currDashIdx = 0;
4674       } else {
4675         currDashStart = ds;
4676         ds = 0;
4677       }
4678     }
4679   }
4680 
4681   calcDashStart(state.dashStart);
4682 
4683   uint srcPointIdx = 1;
4684   const(NVGpoint)* v1 = &pts[0];
4685   const(NVGpoint)* v2 = &pts[1];
4686   float currRest = v1.len;
4687   nvg__addPath(ctx);
4688   nvg__addPoint(ctx, v1.x, v1.y, PointFlag.Corner);
4689 
4690   void fixLastPoint () {
4691     auto lpt = nvg__lastPath(ctx);
4692     if (lpt !is null && lpt.count > 0) {
4693       // fix last point
4694       if (auto lps = nvg__lastPoint(ctx)) lps.flags = PointFlag.Corner;
4695       // fix first point
4696       NVGpathCache* cache = ctx.cache;
4697       cache.points[lpt.first].flags = PointFlag.Corner;
4698     }
4699   }
4700 
4701   for (;;) {
4702     immutable float dlen = dashes[currDashIdx];
4703     if (dlen == 0) {
4704       ++currDashIdx;
4705       if (currDashIdx >= dashCount) currDashIdx = 0;
4706       continue;
4707     }
4708     immutable float dashRest = dlen-currDashStart;
4709     if ((currDashIdx&1) != firstIsGap) {
4710       // this is "moveto" command, so create new path
4711       fixLastPoint();
4712       nvg__addPath(ctx);
4713     }
4714     if (currRest > dashRest) {
4715       currRest -= dashRest;
4716       ++currDashIdx;
4717       if (currDashIdx >= dashCount) currDashIdx = 0;
4718       currDashStart = 0;
4719       nvg__addPoint(ctx,
4720         v2.x-(v2.x-v1.x)*currRest/v1.len,
4721         v2.y-(v2.y-v1.y)*currRest/v1.len,
4722         PointFlag.Corner
4723       );
4724     } else {
4725       currDashStart += currRest;
4726       nvg__addPoint(ctx, v2.x, v2.y, v1.flags); //k8:fix flags here?
4727       ++srcPointIdx;
4728       v1 = v2;
4729       currRest = v1.len;
4730       if (srcPointIdx >= ptsCount) break;
4731       v2 = &pts[srcPointIdx];
4732     }
4733   }
4734   fixLastPoint();
4735 }
4736 
4737 
4738 version(nanovg_bench_flatten) import iv.timer : Timer;
4739 
4740 void nvg__flattenPaths(bool asStroke) (NVGContext ctx) nothrow @trusted @nogc {
4741   version(nanovg_bench_flatten) {
4742     Timer timer;
4743     char[128] tmbuf;
4744     int bzcount;
4745   }
4746   NVGpathCache* cache = ctx.cache;
4747   NVGstate* state = nvg__getState(ctx);
4748 
4749   // check if we already did flattening
4750   static if (asStroke) {
4751     if (state.dashCount >= 2) {
4752       if (cache.npaths > 0 && state.lastFlattenDashCount == state.dashCount) return; // already flattened
4753       state.dasherActive = true;
4754       state.lastFlattenDashCount = state.dashCount;
4755     } else {
4756       if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened
4757       state.dasherActive = false;
4758       state.lastFlattenDashCount = 0;
4759     }
4760   } else {
4761     if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened
4762     state.lastFlattenDashCount = 0; // so next stroke flattening will redo it
4763     state.dasherActive = false;
4764   }
4765 
4766   // clear path cache
4767   cache.npaths = 0;
4768   cache.npoints = 0;
4769 
4770   // flatten
4771   version(nanovg_bench_flatten) timer.restart();
4772   int i = 0;
4773   while (i < ctx.ncommands) {
4774     final switch (cast(Command)ctx.commands[i]) {
4775       case Command.MoveTo:
4776         //assert(i+3 <= ctx.ncommands);
4777         static if (asStroke) {
4778           if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx);
4779         }
4780         nvg__addPath(ctx);
4781         const p = &ctx.commands[i+1];
4782         nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner);
4783         i += 3;
4784         break;
4785       case Command.LineTo:
4786         //assert(i+3 <= ctx.ncommands);
4787         const p = &ctx.commands[i+1];
4788         nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner);
4789         i += 3;
4790         break;
4791       case Command.BezierTo:
4792         //assert(i+7 <= ctx.ncommands);
4793         const last = nvg__lastPoint(ctx);
4794         if (last !is null) {
4795           const cp1 = &ctx.commands[i+1];
4796           const cp2 = &ctx.commands[i+3];
4797           const p = &ctx.commands[i+5];
4798           if (ctx.tesselatortype == NVGTesselation.DeCasteljau) {
4799             nvg__tesselateBezier(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
4800           } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) {
4801             nvg__tesselateBezierMcSeem(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
4802           } else {
4803             nvg__tesselateBezierAFD(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], PointFlag.Corner);
4804           }
4805           version(nanovg_bench_flatten) ++bzcount;
4806         }
4807         i += 7;
4808         break;
4809       case Command.Close:
4810         //assert(i+1 <= ctx.ncommands);
4811         nvg__closePath(ctx);
4812         i += 1;
4813         break;
4814       case Command.Winding:
4815         //assert(i+2 <= ctx.ncommands);
4816         nvg__pathWinding(ctx, cast(NVGWinding)ctx.commands[i+1]);
4817         i += 2;
4818         break;
4819     }
4820   }
4821   static if (asStroke) {
4822     if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx);
4823   }
4824   version(nanovg_bench_flatten) {{
4825     timer.stop();
4826     auto xb = timer.toBuffer(tmbuf[]);
4827     import core.stdc.stdio : printf;
4828     printf("flattening time: [%.*s] (%d beziers)\n", cast(uint)xb.length, xb.ptr, bzcount);
4829   }}
4830 
4831   cache.bounds.ptr[0] = cache.bounds.ptr[1] = float.max;
4832   cache.bounds.ptr[2] = cache.bounds.ptr[3] = -float.max;
4833 
4834   // calculate the direction and length of line segments
4835   version(nanovg_bench_flatten) timer.restart();
4836   foreach (int j; 0..cache.npaths) {
4837     NVGpath* path = &cache.paths[j];
4838     NVGpoint* pts = &cache.points[path.first];
4839 
4840     // if the first and last points are the same, remove the last, mark as closed path
4841     NVGpoint* p0 = &pts[path.count-1];
4842     NVGpoint* p1 = &pts[0];
4843     if (nvg__ptEquals(p0.x, p0.y, p1.x, p1.y, ctx.distTol)) {
4844       --path.count;
4845       p0 = &pts[path.count-1];
4846       path.closed = true;
4847     }
4848 
4849     // enforce winding
4850     if (path.count > 2) {
4851       immutable float area = nvg__polyArea(pts, path.count);
4852       if (path.mWinding == NVGWinding.CCW && area < 0.0f) nvg__polyReverse(pts, path.count);
4853       if (path.mWinding == NVGWinding.CW && area > 0.0f) nvg__polyReverse(pts, path.count);
4854     }
4855 
4856     foreach (immutable _; 0..path.count) {
4857       // calculate segment direction and length
4858       p0.dx = p1.x-p0.x;
4859       p0.dy = p1.y-p0.y;
4860       p0.len = nvg__normalize(&p0.dx, &p0.dy);
4861       // update bounds
4862       cache.bounds.ptr[0] = nvg__min(cache.bounds.ptr[0], p0.x);
4863       cache.bounds.ptr[1] = nvg__min(cache.bounds.ptr[1], p0.y);
4864       cache.bounds.ptr[2] = nvg__max(cache.bounds.ptr[2], p0.x);
4865       cache.bounds.ptr[3] = nvg__max(cache.bounds.ptr[3], p0.y);
4866       // advance
4867       p0 = p1++;
4868     }
4869   }
4870   version(nanovg_bench_flatten) {{
4871     timer.stop();
4872     auto xb = timer.toBuffer(tmbuf[]);
4873     import core.stdc.stdio : printf;
4874     printf("segment calculation time: [%.*s]\n", cast(uint)xb.length, xb.ptr);
4875   }}
4876 }
4877 
4878 int nvg__curveDivs (float r, float arc, float tol) nothrow @trusted @nogc {
4879   immutable float da = nvg__acosf(r/(r+tol))*2.0f;
4880   return nvg__max(2, cast(int)nvg__ceilf(arc/da));
4881 }
4882 
4883 void nvg__chooseBevel (int bevel, NVGpoint* p0, NVGpoint* p1, float w, float* x0, float* y0, float* x1, float* y1) nothrow @trusted @nogc {
4884   if (bevel) {
4885     *x0 = p1.x+p0.dy*w;
4886     *y0 = p1.y-p0.dx*w;
4887     *x1 = p1.x+p1.dy*w;
4888     *y1 = p1.y-p1.dx*w;
4889   } else {
4890     *x0 = p1.x+p1.dmx*w;
4891     *y0 = p1.y+p1.dmy*w;
4892     *x1 = p1.x+p1.dmx*w;
4893     *y1 = p1.y+p1.dmy*w;
4894   }
4895 }
4896 
4897 NVGVertex* nvg__roundJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, int ncap, float fringe) nothrow @trusted @nogc {
4898   float dlx0 = p0.dy;
4899   float dly0 = -p0.dx;
4900   float dlx1 = p1.dy;
4901   float dly1 = -p1.dx;
4902   //NVG_NOTUSED(fringe);
4903 
4904   if (p1.flags&PointFlag.Left) {
4905     float lx0 = void, ly0 = void, lx1 = void, ly1 = void;
4906     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1);
4907     immutable float a0 = nvg__atan2f(-dly0, -dlx0);
4908     float a1 = nvg__atan2f(-dly1, -dlx1);
4909     if (a1 > a0) a1 -= NVG_PI*2;
4910 
4911     nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4912     nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4913 
4914     int n = nvg__clamp(cast(int)nvg__ceilf(((a0-a1)/NVG_PI)*ncap), 2, ncap);
4915     for (int i = 0; i < n; ++i) {
4916       float u = i/cast(float)(n-1);
4917       float a = a0+u*(a1-a0);
4918       float rx = p1.x+nvg__cosf(a)*rw;
4919       float ry = p1.y+nvg__sinf(a)*rw;
4920       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4921       nvg__vset(dst, rx, ry, ru, 1); ++dst;
4922     }
4923 
4924     nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4925     nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4926 
4927   } else {
4928     float rx0 = void, ry0 = void, rx1 = void, ry1 = void;
4929     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1);
4930     immutable float a0 = nvg__atan2f(dly0, dlx0);
4931     float a1 = nvg__atan2f(dly1, dlx1);
4932     if (a1 < a0) a1 += NVG_PI*2;
4933 
4934     nvg__vset(dst, p1.x+dlx0*rw, p1.y+dly0*rw, lu, 1); ++dst;
4935     nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4936 
4937     int n = nvg__clamp(cast(int)nvg__ceilf(((a1-a0)/NVG_PI)*ncap), 2, ncap);
4938     for (int i = 0; i < n; i++) {
4939       float u = i/cast(float)(n-1);
4940       float a = a0+u*(a1-a0);
4941       float lx = p1.x+nvg__cosf(a)*lw;
4942       float ly = p1.y+nvg__sinf(a)*lw;
4943       nvg__vset(dst, lx, ly, lu, 1); ++dst;
4944       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4945     }
4946 
4947     nvg__vset(dst, p1.x+dlx1*rw, p1.y+dly1*rw, lu, 1); ++dst;
4948     nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
4949 
4950   }
4951   return dst;
4952 }
4953 
4954 NVGVertex* nvg__bevelJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, float fringe) nothrow @trusted @nogc {
4955   float rx0, ry0, rx1, ry1;
4956   float lx0, ly0, lx1, ly1;
4957   float dlx0 = p0.dy;
4958   float dly0 = -p0.dx;
4959   float dlx1 = p1.dy;
4960   float dly1 = -p1.dx;
4961   //NVG_NOTUSED(fringe);
4962 
4963   if (p1.flags&PointFlag.Left) {
4964     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1);
4965 
4966     nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4967     nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4968 
4969     if (p1.flags&PointFlag.Bevel) {
4970       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4971       nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4972 
4973       nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4974       nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4975     } else {
4976       rx0 = p1.x-p1.dmx*rw;
4977       ry0 = p1.y-p1.dmy*rw;
4978 
4979       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4980       nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4981 
4982       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4983       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4984 
4985       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4986       nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4987     }
4988 
4989     nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4990     nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4991 
4992   } else {
4993     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1);
4994 
4995     nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
4996     nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4997 
4998     if (p1.flags&PointFlag.Bevel) {
4999       nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
5000       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
5001 
5002       nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
5003       nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
5004     } else {
5005       lx0 = p1.x+p1.dmx*lw;
5006       ly0 = p1.y+p1.dmy*lw;
5007 
5008       nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
5009       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
5010 
5011       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
5012       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
5013 
5014       nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
5015       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
5016     }
5017 
5018     nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
5019     nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
5020   }
5021 
5022   return dst;
5023 }
5024 
5025 NVGVertex* nvg__buttCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc {
5026   immutable float px = p.x-dx*d;
5027   immutable float py = p.y-dy*d;
5028   immutable float dlx = dy;
5029   immutable float dly = -dx;
5030   nvg__vset(dst, px+dlx*w-dx*aa, py+dly*w-dy*aa, 0, 0); ++dst;
5031   nvg__vset(dst, px-dlx*w-dx*aa, py-dly*w-dy*aa, 1, 0); ++dst;
5032   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5033   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5034   return dst;
5035 }
5036 
5037 NVGVertex* nvg__buttCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc {
5038   immutable float px = p.x+dx*d;
5039   immutable float py = p.y+dy*d;
5040   immutable float dlx = dy;
5041   immutable float dly = -dx;
5042   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5043   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5044   nvg__vset(dst, px+dlx*w+dx*aa, py+dly*w+dy*aa, 0, 0); ++dst;
5045   nvg__vset(dst, px-dlx*w+dx*aa, py-dly*w+dy*aa, 1, 0); ++dst;
5046   return dst;
5047 }
5048 
5049 NVGVertex* nvg__roundCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc {
5050   immutable float px = p.x;
5051   immutable float py = p.y;
5052   immutable float dlx = dy;
5053   immutable float dly = -dx;
5054   //NVG_NOTUSED(aa);
5055   immutable float ncpf = cast(float)(ncap-1);
5056   foreach (int i; 0..ncap) {
5057     float a = i/*/cast(float)(ncap-1)*//ncpf*NVG_PI;
5058     float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w;
5059     nvg__vset(dst, px-dlx*ax-dx*ay, py-dly*ax-dy*ay, 0, 1); ++dst;
5060     nvg__vset(dst, px, py, 0.5f, 1); ++dst;
5061   }
5062   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5063   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5064   return dst;
5065 }
5066 
5067 NVGVertex* nvg__roundCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc {
5068   immutable float px = p.x;
5069   immutable float py = p.y;
5070   immutable float dlx = dy;
5071   immutable float dly = -dx;
5072   //NVG_NOTUSED(aa);
5073   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5074   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5075   immutable float ncpf = cast(float)(ncap-1);
5076   foreach (int i; 0..ncap) {
5077     float a = i/*cast(float)(ncap-1)*//ncpf*NVG_PI;
5078     float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w;
5079     nvg__vset(dst, px, py, 0.5f, 1); ++dst;
5080     nvg__vset(dst, px-dlx*ax+dx*ay, py-dly*ax+dy*ay, 0, 1); ++dst;
5081   }
5082   return dst;
5083 }
5084 
5085 void nvg__calculateJoins (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5086   NVGpathCache* cache = ctx.cache;
5087   float iw = 0.0f;
5088 
5089   if (w > 0.0f) iw = 1.0f/w;
5090 
5091   // Calculate which joins needs extra vertices to append, and gather vertex count.
5092   foreach (int i; 0..cache.npaths) {
5093     NVGpath* path = &cache.paths[i];
5094     NVGpoint* pts = &cache.points[path.first];
5095     NVGpoint* p0 = &pts[path.count-1];
5096     NVGpoint* p1 = &pts[0];
5097     int nleft = 0;
5098 
5099     path.nbevel = 0;
5100 
5101     foreach (int j; 0..path.count) {
5102       //float dlx0, dly0, dlx1, dly1, dmr2, cross, limit;
5103       immutable float dlx0 = p0.dy;
5104       immutable float dly0 = -p0.dx;
5105       immutable float dlx1 = p1.dy;
5106       immutable float dly1 = -p1.dx;
5107       // Calculate extrusions
5108       p1.dmx = (dlx0+dlx1)*0.5f;
5109       p1.dmy = (dly0+dly1)*0.5f;
5110       immutable float dmr2 = p1.dmx*p1.dmx+p1.dmy*p1.dmy;
5111       if (dmr2 > 0.000001f) {
5112         float scale = 1.0f/dmr2;
5113         if (scale > 600.0f) scale = 600.0f;
5114         p1.dmx *= scale;
5115         p1.dmy *= scale;
5116       }
5117 
5118       // Clear flags, but keep the corner.
5119       p1.flags = (p1.flags&PointFlag.Corner) ? PointFlag.Corner : 0;
5120 
5121       // Keep track of left turns.
5122       immutable float cross = p1.dx*p0.dy-p0.dx*p1.dy;
5123       if (cross > 0.0f) {
5124         nleft++;
5125         p1.flags |= PointFlag.Left;
5126       }
5127 
5128       // Calculate if we should use bevel or miter for inner join.
5129       immutable float limit = nvg__max(1.01f, nvg__min(p0.len, p1.len)*iw);
5130       if ((dmr2*limit*limit) < 1.0f) p1.flags |= PointFlag.InnerBevelPR;
5131 
5132       // Check to see if the corner needs to be beveled.
5133       if (p1.flags&PointFlag.Corner) {
5134         if ((dmr2*miterLimit*miterLimit) < 1.0f || lineJoin == NVGLineCap.Bevel || lineJoin == NVGLineCap.Round) {
5135           p1.flags |= PointFlag.Bevel;
5136         }
5137       }
5138 
5139       if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) path.nbevel++;
5140 
5141       p0 = p1++;
5142     }
5143 
5144     path.convex = (nleft == path.count);
5145   }
5146 }
5147 
5148 void nvg__expandStroke (NVGContext ctx, float w, int lineCap, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5149   NVGpathCache* cache = ctx.cache;
5150   immutable float aa = ctx.fringeWidth;
5151   int ncap = nvg__curveDivs(w, NVG_PI, ctx.tessTol); // Calculate divisions per half circle.
5152 
5153   nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
5154 
5155   // Calculate max vertex usage.
5156   int cverts = 0;
5157   foreach (int i; 0..cache.npaths) {
5158     NVGpath* path = &cache.paths[i];
5159     immutable bool loop = path.closed;
5160     if (lineJoin == NVGLineCap.Round) {
5161       cverts += (path.count+path.nbevel*(ncap+2)+1)*2; // plus one for loop
5162     } else {
5163       cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop
5164     }
5165     if (!loop) {
5166       // space for caps
5167       if (lineCap == NVGLineCap.Round) {
5168         cverts += (ncap*2+2)*2;
5169       } else {
5170         cverts += (3+3)*2;
5171       }
5172     }
5173   }
5174 
5175   NVGVertex* verts = nvg__allocTempVerts(ctx, cverts);
5176   if (verts is null) return;
5177 
5178   foreach (int i; 0..cache.npaths) {
5179     NVGpath* path = &cache.paths[i];
5180     NVGpoint* pts = &cache.points[path.first];
5181     NVGpoint* p0;
5182     NVGpoint* p1;
5183     int s, e;
5184 
5185     path.fill = null;
5186     path.nfill = 0;
5187 
5188     // Calculate fringe or stroke
5189     immutable bool loop = path.closed;
5190     NVGVertex* dst = verts;
5191     path.stroke = dst;
5192 
5193     if (loop) {
5194       // Looping
5195       p0 = &pts[path.count-1];
5196       p1 = &pts[0];
5197       s = 0;
5198       e = path.count;
5199     } else {
5200       // Add cap
5201       p0 = &pts[0];
5202       p1 = &pts[1];
5203       s = 1;
5204       e = path.count-1;
5205     }
5206 
5207     if (!loop) {
5208       // Add cap
5209       float dx = p1.x-p0.x;
5210       float dy = p1.y-p0.y;
5211       nvg__normalize(&dx, &dy);
5212            if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa);
5213       else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa);
5214       else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa);
5215     }
5216 
5217     foreach (int j; s..e) {
5218       if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) {
5219         if (lineJoin == NVGLineCap.Round) {
5220           dst = nvg__roundJoin(dst, p0, p1, w, w, 0, 1, ncap, aa);
5221         } else {
5222           dst = nvg__bevelJoin(dst, p0, p1, w, w, 0, 1, aa);
5223         }
5224       } else {
5225         nvg__vset(dst, p1.x+(p1.dmx*w), p1.y+(p1.dmy*w), 0, 1); ++dst;
5226         nvg__vset(dst, p1.x-(p1.dmx*w), p1.y-(p1.dmy*w), 1, 1); ++dst;
5227       }
5228       p0 = p1++;
5229     }
5230 
5231     if (loop) {
5232       // Loop it
5233       nvg__vset(dst, verts[0].x, verts[0].y, 0, 1); ++dst;
5234       nvg__vset(dst, verts[1].x, verts[1].y, 1, 1); ++dst;
5235     } else {
5236       // Add cap
5237       float dx = p1.x-p0.x;
5238       float dy = p1.y-p0.y;
5239       nvg__normalize(&dx, &dy);
5240            if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa);
5241       else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa);
5242       else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa);
5243     }
5244 
5245     path.nstroke = cast(int)(dst-verts);
5246 
5247     verts = dst;
5248   }
5249 }
5250 
5251 void nvg__expandFill (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5252   NVGpathCache* cache = ctx.cache;
5253   immutable float aa = ctx.fringeWidth;
5254   bool fringe = (w > 0.0f);
5255 
5256   nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
5257 
5258   // Calculate max vertex usage.
5259   int cverts = 0;
5260   foreach (int i; 0..cache.npaths) {
5261     NVGpath* path = &cache.paths[i];
5262     cverts += path.count+path.nbevel+1;
5263     if (fringe) cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop
5264   }
5265 
5266   NVGVertex* verts = nvg__allocTempVerts(ctx, cverts);
5267   if (verts is null) return;
5268 
5269   bool convex = (cache.npaths == 1 && cache.paths[0].convex);
5270 
5271   foreach (int i; 0..cache.npaths) {
5272     NVGpath* path = &cache.paths[i];
5273     NVGpoint* pts = &cache.points[path.first];
5274 
5275     // Calculate shape vertices.
5276     immutable float woff = 0.5f*aa;
5277     NVGVertex* dst = verts;
5278     path.fill = dst;
5279 
5280     if (fringe) {
5281       // Looping
5282       NVGpoint* p0 = &pts[path.count-1];
5283       NVGpoint* p1 = &pts[0];
5284       foreach (int j; 0..path.count) {
5285         if (p1.flags&PointFlag.Bevel) {
5286           immutable float dlx0 = p0.dy;
5287           immutable float dly0 = -p0.dx;
5288           immutable float dlx1 = p1.dy;
5289           immutable float dly1 = -p1.dx;
5290           if (p1.flags&PointFlag.Left) {
5291             immutable float lx = p1.x+p1.dmx*woff;
5292             immutable float ly = p1.y+p1.dmy*woff;
5293             nvg__vset(dst, lx, ly, 0.5f, 1); ++dst;
5294           } else {
5295             immutable float lx0 = p1.x+dlx0*woff;
5296             immutable float ly0 = p1.y+dly0*woff;
5297             immutable float lx1 = p1.x+dlx1*woff;
5298             immutable float ly1 = p1.y+dly1*woff;
5299             nvg__vset(dst, lx0, ly0, 0.5f, 1); ++dst;
5300             nvg__vset(dst, lx1, ly1, 0.5f, 1); ++dst;
5301           }
5302         } else {
5303           nvg__vset(dst, p1.x+(p1.dmx*woff), p1.y+(p1.dmy*woff), 0.5f, 1); ++dst;
5304         }
5305         p0 = p1++;
5306       }
5307     } else {
5308       foreach (int j; 0..path.count) {
5309         nvg__vset(dst, pts[j].x, pts[j].y, 0.5f, 1);
5310         ++dst;
5311       }
5312     }
5313 
5314     path.nfill = cast(int)(dst-verts);
5315     verts = dst;
5316 
5317     // Calculate fringe
5318     if (fringe) {
5319       float lw = w+woff;
5320       immutable float rw = w-woff;
5321       float lu = 0;
5322       immutable float ru = 1;
5323       dst = verts;
5324       path.stroke = dst;
5325 
5326       // Create only half a fringe for convex shapes so that
5327       // the shape can be rendered without stenciling.
5328       if (convex) {
5329         lw = woff; // This should generate the same vertex as fill inset above.
5330         lu = 0.5f; // Set outline fade at middle.
5331       }
5332 
5333       // Looping
5334       NVGpoint* p0 = &pts[path.count-1];
5335       NVGpoint* p1 = &pts[0];
5336 
5337       foreach (int j; 0..path.count) {
5338         if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) {
5339           dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx.fringeWidth);
5340         } else {
5341           nvg__vset(dst, p1.x+(p1.dmx*lw), p1.y+(p1.dmy*lw), lu, 1); ++dst;
5342           nvg__vset(dst, p1.x-(p1.dmx*rw), p1.y-(p1.dmy*rw), ru, 1); ++dst;
5343         }
5344         p0 = p1++;
5345       }
5346 
5347       // Loop it
5348       nvg__vset(dst, verts[0].x, verts[0].y, lu, 1); ++dst;
5349       nvg__vset(dst, verts[1].x, verts[1].y, ru, 1); ++dst;
5350 
5351       path.nstroke = cast(int)(dst-verts);
5352       verts = dst;
5353     } else {
5354       path.stroke = null;
5355       path.nstroke = 0;
5356     }
5357   }
5358 }
5359 
5360 
5361 // ////////////////////////////////////////////////////////////////////////// //
5362 // Paths
5363 
5364 /// Clears the current path and sub-paths.
5365 /// Group: paths
5366 @scriptable
5367 public void beginPath (NVGContext ctx) nothrow @trusted @nogc {
5368   ctx.ncommands = 0;
5369   ctx.pathPickRegistered &= NVGPickKind.All; // reset "registered" flags
5370   nvg__clearPathCache(ctx);
5371 }
5372 
5373 public alias newPath = beginPath; /// Ditto.
5374 
5375 /// Starts new sub-path with specified point as first point.
5376 /// Group: paths
5377 @scriptable
5378 public void moveTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
5379   nvg__appendCommands(ctx, Command.MoveTo, x, y);
5380 }
5381 
5382 /// Starts new sub-path with specified point as first point.
5383 /// Arguments: [x, y]*
5384 /// Group: paths
5385 public void moveTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5386   enum ArgC = 2;
5387   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [moveTo] call");
5388   if (args.length < ArgC) return;
5389   nvg__appendCommands(ctx, Command.MoveTo, args[$-2..$]);
5390 }
5391 
5392 /// Adds line segment from the last point in the path to the specified point.
5393 /// Group: paths
5394 @scriptable
5395 public void lineTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
5396   nvg__appendCommands(ctx, Command.LineTo, x, y);
5397 }
5398 
5399 /// Adds line segment from the last point in the path to the specified point.
5400 /// Arguments: [x, y]*
5401 /// Group: paths
5402 public void lineTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5403   enum ArgC = 2;
5404   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [lineTo] call");
5405   if (args.length < ArgC) return;
5406   foreach (immutable idx; 0..args.length/ArgC) {
5407     nvg__appendCommands(ctx, Command.LineTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]);
5408   }
5409 }
5410 
5411 /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
5412 /// Group: paths
5413 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 {
5414   nvg__appendCommands(ctx, Command.BezierTo, c1x, c1y, c2x, c2y, x, y);
5415 }
5416 
5417 /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
5418 /// Arguments: [c1x, c1y, c2x, c2y, x, y]*
5419 /// Group: paths
5420 public void bezierTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5421   enum ArgC = 6;
5422   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [bezierTo] call");
5423   if (args.length < ArgC) return;
5424   foreach (immutable idx; 0..args.length/ArgC) {
5425     nvg__appendCommands(ctx, Command.BezierTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]);
5426   }
5427 }
5428 
5429 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
5430 /// Group: paths
5431 public void quadTo (NVGContext ctx, in float cx, in float cy, in float x, in float y) nothrow @trusted @nogc {
5432   immutable float x0 = ctx.commandx;
5433   immutable float y0 = ctx.commandy;
5434   nvg__appendCommands(ctx,
5435     Command.BezierTo,
5436     x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
5437     x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
5438     x, y,
5439   );
5440 }
5441 
5442 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
5443 /// Arguments: [cx, cy, x, y]*
5444 /// Group: paths
5445 public void quadTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5446   enum ArgC = 4;
5447   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [quadTo] call");
5448   if (args.length < ArgC) return;
5449   const(float)* aptr = args.ptr;
5450   foreach (immutable idx; 0..args.length/ArgC) {
5451     immutable float x0 = ctx.commandx;
5452     immutable float y0 = ctx.commandy;
5453     immutable float cx = *aptr++;
5454     immutable float cy = *aptr++;
5455     immutable float x = *aptr++;
5456     immutable float y = *aptr++;
5457     nvg__appendCommands(ctx,
5458       Command.BezierTo,
5459       x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
5460       x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
5461       x, y,
5462     );
5463   }
5464 }
5465 
5466 /// Adds an arc segment at the corner defined by the last path point, and two specified points.
5467 /// Group: paths
5468 public void arcTo (NVGContext ctx, in float x1, in float y1, in float x2, in float y2, in float radius) nothrow @trusted @nogc {
5469   if (ctx.ncommands == 0) return;
5470 
5471   immutable float x0 = ctx.commandx;
5472   immutable float y0 = ctx.commandy;
5473 
5474   // handle degenerate cases
5475   if (nvg__ptEquals(x0, y0, x1, y1, ctx.distTol) ||
5476       nvg__ptEquals(x1, y1, x2, y2, ctx.distTol) ||
5477       nvg__distPtSeg(x1, y1, x0, y0, x2, y2) < ctx.distTol*ctx.distTol ||
5478       radius < ctx.distTol)
5479   {
5480     ctx.lineTo(x1, y1);
5481     return;
5482   }
5483 
5484   // calculate tangential circle to lines (x0, y0)-(x1, y1) and (x1, y1)-(x2, y2)
5485   float dx0 = x0-x1;
5486   float dy0 = y0-y1;
5487   float dx1 = x2-x1;
5488   float dy1 = y2-y1;
5489   nvg__normalize(&dx0, &dy0);
5490   nvg__normalize(&dx1, &dy1);
5491   immutable float a = nvg__acosf(dx0*dx1+dy0*dy1);
5492   immutable float d = radius/nvg__tanf(a/2.0f);
5493 
5494   //printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d);
5495 
5496   if (d > 10000.0f) {
5497     ctx.lineTo(x1, y1);
5498     return;
5499   }
5500 
5501   float cx = void, cy = void, a0 = void, a1 = void;
5502   NVGWinding dir;
5503   if (nvg__cross(dx0, dy0, dx1, dy1) > 0.0f) {
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.CW;
5509     //printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
5510   } else {
5511     cx = x1+dx0*d+-dy0*radius;
5512     cy = y1+dy0*d+dx0*radius;
5513     a0 = nvg__atan2f(-dx0, dy0);
5514     a1 = nvg__atan2f(dx1, -dy1);
5515     dir = NVGWinding.CCW;
5516     //printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
5517   }
5518 
5519   ctx.arc(dir, cx, cy, radius, a0, a1); // first is line
5520 }
5521 
5522 
5523 /// Adds an arc segment at the corner defined by the last path point, and two specified points.
5524 /// Arguments: [x1, y1, x2, y2, radius]*
5525 /// Group: paths
5526 public void arcTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5527   enum ArgC = 5;
5528   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arcTo] call");
5529   if (args.length < ArgC) return;
5530   if (ctx.ncommands == 0) return;
5531   const(float)* aptr = args.ptr;
5532   foreach (immutable idx; 0..args.length/ArgC) {
5533     immutable float x0 = ctx.commandx;
5534     immutable float y0 = ctx.commandy;
5535     immutable float x1 = *aptr++;
5536     immutable float y1 = *aptr++;
5537     immutable float x2 = *aptr++;
5538     immutable float y2 = *aptr++;
5539     immutable float radius = *aptr++;
5540     ctx.arcTo(x1, y1, x2, y2, radius);
5541   }
5542 }
5543 
5544 /// Closes current sub-path with a line segment.
5545 /// Group: paths
5546 @scriptable
5547 public void closePath (NVGContext ctx) nothrow @trusted @nogc {
5548   nvg__appendCommands(ctx, Command.Close);
5549 }
5550 
5551 /// Sets the current sub-path winding, see NVGWinding and NVGSolidity.
5552 /// Group: paths
5553 public void pathWinding (NVGContext ctx, NVGWinding dir) nothrow @trusted @nogc {
5554   nvg__appendCommands(ctx, Command.Winding, cast(float)dir);
5555 }
5556 
5557 /// Ditto.
5558 public void pathWinding (NVGContext ctx, NVGSolidity dir) nothrow @trusted @nogc {
5559   nvg__appendCommands(ctx, Command.Winding, cast(float)dir);
5560 }
5561 
5562 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r,
5563  * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW).
5564  * Angles are specified in radians.
5565  *
5566  * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo
5567  *
5568  * Group: paths
5569  */
5570 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 {
5571   static assert(mode == "original" || mode == "move" || mode == "line");
5572 
5573   float[3+5*7+100] vals = void;
5574   //int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo);
5575   static if (mode == "original") {
5576     immutable int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo);
5577   } else static if (mode == "move") {
5578     enum move = Command.MoveTo;
5579   } else static if (mode == "line") {
5580     enum move = Command.LineTo;
5581   } else {
5582     static assert(0, "wtf?!");
5583   }
5584 
5585   // Clamp angles
5586   float da = a1-a0;
5587   if (dir == NVGWinding.CW) {
5588     if (nvg__absf(da) >= NVG_PI*2) {
5589       da = NVG_PI*2;
5590     } else {
5591       while (da < 0.0f) da += NVG_PI*2;
5592     }
5593   } else {
5594     if (nvg__absf(da) >= NVG_PI*2) {
5595       da = -NVG_PI*2;
5596     } else {
5597       while (da > 0.0f) da -= NVG_PI*2;
5598     }
5599   }
5600 
5601   // Split arc into max 90 degree segments.
5602   immutable int ndivs = nvg__max(1, nvg__min(cast(int)(nvg__absf(da)/(NVG_PI*0.5f)+0.5f), 5));
5603   immutable float hda = (da/cast(float)ndivs)/2.0f;
5604   float kappa = nvg__absf(4.0f/3.0f*(1.0f-nvg__cosf(hda))/nvg__sinf(hda));
5605 
5606   if (dir == NVGWinding.CCW) kappa = -kappa;
5607 
5608   int nvals = 0;
5609   float px = 0, py = 0, ptanx = 0, ptany = 0;
5610   foreach (int i; 0..ndivs+1) {
5611     immutable float a = a0+da*(i/cast(float)ndivs);
5612     immutable float dx = nvg__cosf(a);
5613     immutable float dy = nvg__sinf(a);
5614     immutable float x = cx+dx*r;
5615     immutable float y = cy+dy*r;
5616     immutable float tanx = -dy*r*kappa;
5617     immutable float tany = dx*r*kappa;
5618 
5619     if (i == 0) {
5620       if (vals.length-nvals < 3) {
5621         // flush
5622         nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5623         nvals = 0;
5624       }
5625       vals.ptr[nvals++] = cast(float)move;
5626       vals.ptr[nvals++] = x;
5627       vals.ptr[nvals++] = y;
5628     } else {
5629       if (vals.length-nvals < 7) {
5630         // flush
5631         nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5632         nvals = 0;
5633       }
5634       vals.ptr[nvals++] = Command.BezierTo;
5635       vals.ptr[nvals++] = px+ptanx;
5636       vals.ptr[nvals++] = py+ptany;
5637       vals.ptr[nvals++] = x-tanx;
5638       vals.ptr[nvals++] = y-tany;
5639       vals.ptr[nvals++] = x;
5640       vals.ptr[nvals++] = y;
5641     }
5642     px = x;
5643     py = y;
5644     ptanx = tanx;
5645     ptany = tany;
5646   }
5647 
5648   nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5649 }
5650 
5651 
5652 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r,
5653  * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW).
5654  * Angles are specified in radians.
5655  *
5656  * Arguments: [cx, cy, r, a0, a1]*
5657  *
5658  * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo
5659  *
5660  * Group: paths
5661  */
5662 public void arc(string mode="original") (NVGContext ctx, NVGWinding dir, in float[] args) nothrow @trusted @nogc {
5663   static assert(mode == "original" || mode == "move" || mode == "line");
5664   enum ArgC = 5;
5665   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arc] call");
5666   if (args.length < ArgC) return;
5667   const(float)* aptr = args.ptr;
5668   foreach (immutable idx; 0..args.length/ArgC) {
5669     immutable cx = *aptr++;
5670     immutable cy = *aptr++;
5671     immutable r = *aptr++;
5672     immutable a0 = *aptr++;
5673     immutable a1 = *aptr++;
5674     ctx.arc!mode(dir, cx, cy, r, a0, a1);
5675   }
5676 }
5677 
5678 /// Creates new rectangle shaped sub-path.
5679 /// Group: paths
5680 @scriptable
5681 public void rect (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc {
5682   nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5683     Command.MoveTo, x, y,
5684     Command.LineTo, x, y+h,
5685     Command.LineTo, x+w, y+h,
5686     Command.LineTo, x+w, y,
5687     Command.Close,
5688   );
5689 }
5690 
5691 /// Creates new rectangle shaped sub-path.
5692 /// Arguments: [x, y, w, h]*
5693 /// Group: paths
5694 public void rect (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5695   enum ArgC = 4;
5696   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [rect] call");
5697   if (args.length < ArgC) return;
5698   const(float)* aptr = args.ptr;
5699   foreach (immutable idx; 0..args.length/ArgC) {
5700     immutable x = *aptr++;
5701     immutable y = *aptr++;
5702     immutable w = *aptr++;
5703     immutable h = *aptr++;
5704     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5705       Command.MoveTo, x, y,
5706       Command.LineTo, x, y+h,
5707       Command.LineTo, x+w, y+h,
5708       Command.LineTo, x+w, y,
5709       Command.Close,
5710     );
5711   }
5712 }
5713 
5714 /// Creates new rounded rectangle shaped sub-path.
5715 /// Group: paths
5716 @scriptable
5717 public void roundedRect (NVGContext ctx, in float x, in float y, in float w, in float h, in float radius) nothrow @trusted @nogc {
5718   ctx.roundedRectVarying(x, y, w, h, radius, radius, radius, radius);
5719 }
5720 
5721 /// Creates new rounded rectangle shaped sub-path.
5722 /// Arguments: [x, y, w, h, radius]*
5723 /// Group: paths
5724 public void roundedRect (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5725   enum ArgC = 5;
5726   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRect] call");
5727   if (args.length < ArgC) return;
5728   const(float)* aptr = args.ptr;
5729   foreach (immutable idx; 0..args.length/ArgC) {
5730     immutable x = *aptr++;
5731     immutable y = *aptr++;
5732     immutable w = *aptr++;
5733     immutable h = *aptr++;
5734     immutable r = *aptr++;
5735     ctx.roundedRectVarying(x, y, w, h, r, r, r, r);
5736   }
5737 }
5738 
5739 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it.
5740 /// Group: paths
5741 @scriptable
5742 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 {
5743   if (rw < 0.1f || rh < 0.1f) {
5744     rect(ctx, x, y, w, h);
5745   } else {
5746     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5747       Command.MoveTo, x+rw, y,
5748       Command.LineTo, x+w-rw, y,
5749       Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh,
5750       Command.LineTo, x+w, y+h-rh,
5751       Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h,
5752       Command.LineTo, x+rw, y+h,
5753       Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh,
5754       Command.LineTo, x, y+rh,
5755       Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y,
5756       Command.Close,
5757     );
5758   }
5759 }
5760 
5761 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it.
5762 /// Arguments: [x, y, w, h, rw, rh]*
5763 /// Group: paths
5764 public void roundedRectEllipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5765   enum ArgC = 6;
5766   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectEllipse] call");
5767   if (args.length < ArgC) return;
5768   const(float)* aptr = args.ptr;
5769   foreach (immutable idx; 0..args.length/ArgC) {
5770     immutable x = *aptr++;
5771     immutable y = *aptr++;
5772     immutable w = *aptr++;
5773     immutable h = *aptr++;
5774     immutable rw = *aptr++;
5775     immutable rh = *aptr++;
5776     if (rw < 0.1f || rh < 0.1f) {
5777       rect(ctx, x, y, w, h);
5778     } else {
5779       nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5780         Command.MoveTo, x+rw, y,
5781         Command.LineTo, x+w-rw, y,
5782         Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh,
5783         Command.LineTo, x+w, y+h-rh,
5784         Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h,
5785         Command.LineTo, x+rw, y+h,
5786         Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh,
5787         Command.LineTo, x, y+rh,
5788         Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y,
5789         Command.Close,
5790       );
5791     }
5792   }
5793 }
5794 
5795 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner.
5796 /// Group: paths
5797 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 {
5798   if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) {
5799     ctx.rect(x, y, w, h);
5800   } else {
5801     immutable float halfw = nvg__absf(w)*0.5f;
5802     immutable float halfh = nvg__absf(h)*0.5f;
5803     immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h);
5804     immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h);
5805     immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h);
5806     immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h);
5807     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5808       Command.MoveTo, x, y+ryTL,
5809       Command.LineTo, x, y+h-ryBL,
5810       Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h,
5811       Command.LineTo, x+w-rxBR, y+h,
5812       Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR,
5813       Command.LineTo, x+w, y+ryTR,
5814       Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y,
5815       Command.LineTo, x+rxTL, y,
5816       Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL,
5817       Command.Close,
5818     );
5819   }
5820 }
5821 
5822 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner.
5823 /// Arguments: [x, y, w, h, radTopLeft, radTopRight, radBottomRight, radBottomLeft]*
5824 /// Group: paths
5825 public void roundedRectVarying (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5826   enum ArgC = 8;
5827   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectVarying] call");
5828   if (args.length < ArgC) return;
5829   const(float)* aptr = args.ptr;
5830   foreach (immutable idx; 0..args.length/ArgC) {
5831     immutable x = *aptr++;
5832     immutable y = *aptr++;
5833     immutable w = *aptr++;
5834     immutable h = *aptr++;
5835     immutable radTopLeft = *aptr++;
5836     immutable radTopRight = *aptr++;
5837     immutable radBottomRight = *aptr++;
5838     immutable radBottomLeft = *aptr++;
5839     if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) {
5840       ctx.rect(x, y, w, h);
5841     } else {
5842       immutable float halfw = nvg__absf(w)*0.5f;
5843       immutable float halfh = nvg__absf(h)*0.5f;
5844       immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h);
5845       immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h);
5846       immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h);
5847       immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h);
5848       nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5849         Command.MoveTo, x, y+ryTL,
5850         Command.LineTo, x, y+h-ryBL,
5851         Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h,
5852         Command.LineTo, x+w-rxBR, y+h,
5853         Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR,
5854         Command.LineTo, x+w, y+ryTR,
5855         Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y,
5856         Command.LineTo, x+rxTL, y,
5857         Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL,
5858         Command.Close,
5859       );
5860     }
5861   }
5862 }
5863 
5864 /// Creates new ellipse shaped sub-path.
5865 /// Group: paths
5866 public void ellipse (NVGContext ctx, in float cx, in float cy, in float rx, in float ry) nothrow @trusted @nogc {
5867   nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5868     Command.MoveTo, cx-rx, cy,
5869     Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,
5870     Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,
5871     Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,
5872     Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,
5873     Command.Close,
5874   );
5875 }
5876 
5877 /// Creates new ellipse shaped sub-path.
5878 /// Arguments: [cx, cy, rx, ry]*
5879 /// Group: paths
5880 public void ellipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5881   enum ArgC = 4;
5882   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [ellipse] call");
5883   if (args.length < ArgC) return;
5884   const(float)* aptr = args.ptr;
5885   foreach (immutable idx; 0..args.length/ArgC) {
5886     immutable cx = *aptr++;
5887     immutable cy = *aptr++;
5888     immutable rx = *aptr++;
5889     immutable ry = *aptr++;
5890     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5891       Command.MoveTo, cx-rx, cy,
5892       Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,
5893       Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,
5894       Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,
5895       Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,
5896       Command.Close,
5897     );
5898   }
5899 }
5900 
5901 /// Creates new circle shaped sub-path.
5902 /// Group: paths
5903 public void circle (NVGContext ctx, in float cx, in float cy, in float r) nothrow @trusted @nogc {
5904   ctx.ellipse(cx, cy, r, r);
5905 }
5906 
5907 /// Creates new circle shaped sub-path.
5908 /// Arguments: [cx, cy, r]*
5909 /// Group: paths
5910 public void circle (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5911   enum ArgC = 3;
5912   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [circle] call");
5913   if (args.length < ArgC) return;
5914   const(float)* aptr = args.ptr;
5915   foreach (immutable idx; 0..args.length/ArgC) {
5916     immutable cx = *aptr++;
5917     immutable cy = *aptr++;
5918     immutable r = *aptr++;
5919     ctx.ellipse(cx, cy, r, r);
5920   }
5921 }
5922 
5923 // Debug function to dump cached path data.
5924 debug public void debugDumpPathCache (NVGContext ctx) nothrow @trusted @nogc {
5925   import core.stdc.stdio : printf;
5926   const(NVGpath)* path;
5927   printf("Dumping %d cached paths\n", ctx.cache.npaths);
5928   for (int i = 0; i < ctx.cache.npaths; ++i) {
5929     path = &ctx.cache.paths[i];
5930     printf("-Path %d\n", i);
5931     if (path.nfill) {
5932       printf("-fill: %d\n", path.nfill);
5933       for (int j = 0; j < path.nfill; ++j) printf("%f\t%f\n", path.fill[j].x, path.fill[j].y);
5934     }
5935     if (path.nstroke) {
5936       printf("-stroke: %d\n", path.nstroke);
5937       for (int j = 0; j < path.nstroke; ++j) printf("%f\t%f\n", path.stroke[j].x, path.stroke[j].y);
5938     }
5939   }
5940 }
5941 
5942 // Flatten path, prepare it for fill operation.
5943 void nvg__prepareFill (NVGContext ctx) nothrow @trusted @nogc {
5944   NVGpathCache* cache = ctx.cache;
5945   NVGstate* state = nvg__getState(ctx);
5946 
5947   nvg__flattenPaths!false(ctx);
5948 
5949   if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) {
5950     nvg__expandFill(ctx, ctx.fringeWidth, NVGLineCap.Miter, 2.4f);
5951   } else {
5952     nvg__expandFill(ctx, 0.0f, NVGLineCap.Miter, 2.4f);
5953   }
5954 
5955   cache.evenOddMode = state.evenOddMode;
5956   cache.fringeWidth = ctx.fringeWidth;
5957   cache.fillReady = true;
5958   cache.strokeReady = false;
5959   cache.clipmode = NVGClipMode.None;
5960 }
5961 
5962 // Flatten path, prepare it for stroke operation.
5963 void nvg__prepareStroke (NVGContext ctx) nothrow @trusted @nogc {
5964   NVGstate* state = nvg__getState(ctx);
5965   NVGpathCache* cache = ctx.cache;
5966 
5967   nvg__flattenPaths!true(ctx);
5968 
5969   immutable float scale = nvg__getAverageScale(state.xform);
5970   float strokeWidth = nvg__clamp(state.strokeWidth*scale, 0.0f, 200.0f);
5971 
5972   if (strokeWidth < ctx.fringeWidth) {
5973     // If the stroke width is less than pixel size, use alpha to emulate coverage.
5974     // Since coverage is area, scale by alpha*alpha.
5975     immutable float alpha = nvg__clamp(strokeWidth/ctx.fringeWidth, 0.0f, 1.0f);
5976     cache.strokeAlphaMul = alpha*alpha;
5977     strokeWidth = ctx.fringeWidth;
5978   } else {
5979     cache.strokeAlphaMul = 1.0f;
5980   }
5981   cache.strokeWidth = strokeWidth;
5982 
5983   if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) {
5984     nvg__expandStroke(ctx, strokeWidth*0.5f+ctx.fringeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit);
5985   } else {
5986     nvg__expandStroke(ctx, strokeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit);
5987   }
5988 
5989   cache.fringeWidth = ctx.fringeWidth;
5990   cache.fillReady = false;
5991   cache.strokeReady = true;
5992   cache.clipmode = NVGClipMode.None;
5993 }
5994 
5995 /// Fills the current path with current fill style.
5996 /// Group: paths
5997 @scriptable
5998 public void fill (NVGContext ctx) nothrow @trusted @nogc {
5999   NVGstate* state = nvg__getState(ctx);
6000 
6001   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) {
6002     ctx.pathPickRegistered |= NVGPickKind.Fill<<16;
6003     ctx.currFillHitId = ctx.pathPickId;
6004   }
6005 
6006   nvg__prepareFill(ctx);
6007 
6008   // apply global alpha
6009   NVGPaint fillPaint = state.fill;
6010   fillPaint.innerColor.a *= state.alpha;
6011   fillPaint.middleColor.a *= state.alpha;
6012   fillPaint.outerColor.a *= state.alpha;
6013 
6014   ctx.appendCurrentPathToCache(ctx.recset, state.fill);
6015 
6016   if (ctx.recblockdraw) return;
6017 
6018   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);
6019 
6020   // count triangles
6021   foreach (int i; 0..ctx.cache.npaths) {
6022     NVGpath* path = &ctx.cache.paths[i];
6023     ctx.fillTriCount += path.nfill-2;
6024     ctx.fillTriCount += path.nstroke-2;
6025     ctx.drawCallCount += 2;
6026   }
6027 }
6028 
6029 /// Fills the current path with current stroke style.
6030 /// Group: paths
6031 @scriptable
6032 public void stroke (NVGContext ctx) nothrow @trusted @nogc {
6033   NVGstate* state = nvg__getState(ctx);
6034 
6035   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) {
6036     ctx.pathPickRegistered |= NVGPickKind.Stroke<<16;
6037     ctx.currStrokeHitId = ctx.pathPickId;
6038   }
6039 
6040   nvg__prepareStroke(ctx);
6041 
6042   NVGpathCache* cache = ctx.cache;
6043 
6044   NVGPaint strokePaint = state.stroke;
6045   strokePaint.innerColor.a *= cache.strokeAlphaMul;
6046   strokePaint.middleColor.a *= cache.strokeAlphaMul;
6047   strokePaint.outerColor.a *= cache.strokeAlphaMul;
6048 
6049   // apply global alpha
6050   strokePaint.innerColor.a *= state.alpha;
6051   strokePaint.middleColor.a *= state.alpha;
6052   strokePaint.outerColor.a *= state.alpha;
6053 
6054   ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
6055 
6056   if (ctx.recblockdraw) return;
6057 
6058   ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths);
6059 
6060   // count triangles
6061   foreach (int i; 0..ctx.cache.npaths) {
6062     NVGpath* path = &ctx.cache.paths[i];
6063     ctx.strokeTriCount += path.nstroke-2;
6064     ++ctx.drawCallCount;
6065   }
6066 }
6067 
6068 /// Sets current path as clipping region.
6069 /// Group: clipping
6070 public void clip (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc {
6071   NVGstate* state = nvg__getState(ctx);
6072 
6073   if (aclipmode == NVGClipMode.None) return;
6074   if (ctx.recblockdraw) return; //???
6075 
6076   if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr);
6077 
6078   /*
6079   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) {
6080     ctx.pathPickRegistered |= NVGPickKind.Fill<<16;
6081     ctx.currFillHitId = ctx.pathPickId;
6082   }
6083   */
6084 
6085   nvg__prepareFill(ctx);
6086 
6087   // apply global alpha
6088   NVGPaint fillPaint = state.fill;
6089   fillPaint.innerColor.a *= state.alpha;
6090   fillPaint.middleColor.a *= state.alpha;
6091   fillPaint.outerColor.a *= state.alpha;
6092 
6093   //ctx.appendCurrentPathToCache(ctx.recset, state.fill);
6094 
6095   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);
6096 
6097   // count triangles
6098   foreach (int i; 0..ctx.cache.npaths) {
6099     NVGpath* path = &ctx.cache.paths[i];
6100     ctx.fillTriCount += path.nfill-2;
6101     ctx.fillTriCount += path.nstroke-2;
6102     ctx.drawCallCount += 2;
6103   }
6104 }
6105 
6106 /// Sets current path as clipping region.
6107 /// Group: clipping
6108 public alias clipFill = clip;
6109 
6110 /// Sets current path' stroke as clipping region.
6111 /// Group: clipping
6112 public void clipStroke (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc {
6113   NVGstate* state = nvg__getState(ctx);
6114 
6115   if (aclipmode == NVGClipMode.None) return;
6116   if (ctx.recblockdraw) return; //???
6117 
6118   if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr);
6119 
6120   /*
6121   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) {
6122     ctx.pathPickRegistered |= NVGPickKind.Stroke<<16;
6123     ctx.currStrokeHitId = ctx.pathPickId;
6124   }
6125   */
6126 
6127   nvg__prepareStroke(ctx);
6128 
6129   NVGpathCache* cache = ctx.cache;
6130 
6131   NVGPaint strokePaint = state.stroke;
6132   strokePaint.innerColor.a *= cache.strokeAlphaMul;
6133   strokePaint.middleColor.a *= cache.strokeAlphaMul;
6134   strokePaint.outerColor.a *= cache.strokeAlphaMul;
6135 
6136   // apply global alpha
6137   strokePaint.innerColor.a *= state.alpha;
6138   strokePaint.middleColor.a *= state.alpha;
6139   strokePaint.outerColor.a *= state.alpha;
6140 
6141   //ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
6142 
6143   ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, aclipmode, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths);
6144 
6145   // count triangles
6146   foreach (int i; 0..ctx.cache.npaths) {
6147     NVGpath* path = &ctx.cache.paths[i];
6148     ctx.strokeTriCount += path.nstroke-2;
6149     ++ctx.drawCallCount;
6150   }
6151 }
6152 
6153 
6154 // ////////////////////////////////////////////////////////////////////////// //
6155 // Picking API
6156 
6157 // most of the code is by Michael Wynne <mike@mikesspace.net>
6158 // https://github.com/memononen/nanovg/pull/230
6159 // https://github.com/MikeWW/nanovg
6160 
6161 /// Pick type query. Used in [hitTest] and [hitTestAll].
6162 /// Group: picking_api
6163 public enum NVGPickKind : ubyte {
6164   Fill = 0x01, ///
6165   Stroke = 0x02, ///
6166   All = 0x03, ///
6167 }
6168 
6169 /// Marks the fill of the current path as pickable with the specified id.
6170 /// Note that you can create and mark path without rasterizing it.
6171 /// Group: picking_api
6172 public void currFillHitId (NVGContext ctx, int id) nothrow @trusted @nogc {
6173   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6174   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/false);
6175   nvg__pickSceneInsert(ps, pp);
6176 }
6177 
6178 public alias currFillPickId = currFillHitId; /// Ditto.
6179 
6180 /// Marks the stroke of the current path as pickable with the specified id.
6181 /// Note that you can create and mark path without rasterizing it.
6182 /// Group: picking_api
6183 public void currStrokeHitId (NVGContext ctx, int id) nothrow @trusted @nogc {
6184   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6185   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/true);
6186   nvg__pickSceneInsert(ps, pp);
6187 }
6188 
6189 public alias currStrokePickId = currStrokeHitId; /// Ditto.
6190 
6191 // Marks the saved path set (fill) as pickable with the specified id.
6192 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!)
6193 // Group: picking_api
6194 /+
6195 public void pathSetFillHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc {
6196   if (svp is null) return;
6197   if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context");
6198   foreach (ref cp; svp.caches[0..svp.ncaches]) {
6199     NVGpickScene* ps = nvg__pickSceneGet(ctx);
6200     NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/false);
6201     nvg__pickSceneInsert(ps, pp);
6202   }
6203 }
6204 +/
6205 
6206 // Marks the saved path set (stroke) as pickable with the specified id.
6207 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!)
6208 // Group: picking_api
6209 /+
6210 public void pathSetStrokeHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc {
6211   if (svp is null) return;
6212   if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context");
6213   foreach (ref cp; svp.caches[0..svp.ncaches]) {
6214     NVGpickScene* ps = nvg__pickSceneGet(ctx);
6215     NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/true);
6216     nvg__pickSceneInsert(ps, pp);
6217   }
6218 }
6219 +/
6220 
6221 private template IsGoodHitTestDG(DG) {
6222   enum IsGoodHitTestDG =
6223     __traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); }) ||
6224     __traits(compiles, (){ DG dg; dg(cast(int)42, cast(int)666); });
6225 }
6226 
6227 private template IsGoodHitTestInternalDG(DG) {
6228   enum IsGoodHitTestInternalDG =
6229     __traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); }) ||
6230     __traits(compiles, (){ DG dg; NVGpickPath* pp; dg(pp); });
6231 }
6232 
6233 /// Call delegate [dg] for each path under the specified position (in no particular order).
6234 /// Returns the id of the path for which delegate [dg] returned true or [NVGNoPick].
6235 /// dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending).
6236 /// Group: picking_api
6237 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) {
6238   if (ctx.pickScene is null || ctx.pickScene.npaths == 0 || (kind&NVGPickKind.All) == 0) return -1;
6239 
6240   NVGpickScene* ps = ctx.pickScene;
6241   int levelwidth = 1<<(ps.nlevels-1);
6242   int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth);
6243   int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth);
6244   int npicked = 0;
6245 
6246   // if we are interested only in most-toplevel path, there is no reason to check paths with worser order.
6247   // but we cannot just get out on the first path found, 'cause we are using quad tree to speed up bounds
6248   // checking, so path walking order is not guaranteed.
6249   static if (bestOrder) {
6250     int lastBestOrder = int.min;
6251   }
6252 
6253   //{ import core.stdc.stdio; printf("npaths=%d\n", ps.npaths); }
6254   for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) {
6255     for (NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; pp !is null; pp = pp.next) {
6256       //{ 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); }
6257       static if (bestOrder) {
6258         // reject earlier paths
6259         if (pp.order <= lastBestOrder) continue; // not interesting
6260       }
6261       immutable uint kpx = kind&pp.flags&3;
6262       if (kpx == 0) continue; // not interesting
6263       if (!nvg__pickPathTestBounds(ctx, ps, pp, x, y)) continue; // not interesting
6264       //{ import core.stdc.stdio; printf("in bounds!\n"); }
6265       int hit = 0;
6266       if (kpx&NVGPickKind.Stroke) hit = nvg__pickPathStroke(ps, pp, x, y);
6267       if (!hit && (kpx&NVGPickKind.Fill)) hit = nvg__pickPath(ps, pp, x, y);
6268       if (!hit) continue;
6269       //{ import core.stdc.stdio; printf("  HIT!\n"); }
6270       static if (bestOrder) lastBestOrder = pp.order;
6271       static if (IsGoodHitTestDG!DG) {
6272         static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) {
6273           if (dg(pp.id, cast(int)pp.order)) return pp.id;
6274         } else {
6275           dg(pp.id, cast(int)pp.order);
6276         }
6277       } else {
6278         static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) {
6279           if (dg(pp)) return pp.id;
6280         } else {
6281           dg(pp);
6282         }
6283       }
6284     }
6285     cellx >>= 1;
6286     celly >>= 1;
6287     levelwidth >>= 1;
6288   }
6289 
6290   return -1;
6291 }
6292 
6293 /// Fills ids with a list of the top most hit ids (from bottom to top) under the specified position.
6294 /// Returns the slice of [ids].
6295 /// Group: picking_api
6296 public int[] hitTestAll (NVGContext ctx, in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc {
6297   if (ctx.pickScene is null || ids.length == 0) return ids[0..0];
6298 
6299   int npicked = 0;
6300   NVGpickScene* ps = ctx.pickScene;
6301 
6302   ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
6303     if (npicked == ps.cpicked) {
6304       int cpicked = ps.cpicked+ps.cpicked;
6305       NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked);
6306       if (picked is null) return true; // abort
6307       ps.cpicked = cpicked;
6308       ps.picked = picked;
6309     }
6310     ps.picked[npicked] = pp;
6311     ++npicked;
6312     return false; // go on
6313   });
6314 
6315   qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths);
6316 
6317   assert(npicked >= 0);
6318   if (npicked > ids.length) npicked = cast(int)ids.length;
6319   foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id;
6320 
6321   return ids[0..npicked];
6322 }
6323 
6324 /// Returns the id of the pickable shape containing x,y or [NVGNoPick] if no shape was found.
6325 /// Group: picking_api
6326 public int hitTest (NVGContext ctx, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc {
6327   if (ctx.pickScene is null) return -1;
6328 
6329   int bestOrder = int.min;
6330   int bestID = -1;
6331 
6332   ctx.hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) {
6333     if (pp.order > bestOrder) {
6334       bestOrder = pp.order;
6335       bestID = pp.id;
6336     }
6337   });
6338 
6339   return bestID;
6340 }
6341 
6342 /// Returns `true` if the path with the given id contains x,y.
6343 /// Group: picking_api
6344 public bool hitTestForId (NVGContext ctx, in int id, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc {
6345   if (ctx.pickScene is null || id == NVGNoPick) return false;
6346 
6347   bool res = false;
6348 
6349   ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) {
6350     if (pp.id == id) {
6351       res = true;
6352       return true; // stop
6353     }
6354     return false; // continue
6355   });
6356 
6357   return res;
6358 }
6359 
6360 /// Returns `true` if the given point is within the fill of the currently defined path.
6361 /// This operation can be done before rasterizing the current path.
6362 /// Group: picking_api
6363 public bool hitTestCurrFill (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
6364   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6365   int oldnpoints = ps.npoints;
6366   int oldnsegments = ps.nsegments;
6367   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/false);
6368   if (pp is null) return false; // oops
6369   scope(exit) {
6370     nvg__freePickPath(ps, pp);
6371     ps.npoints = oldnpoints;
6372     ps.nsegments = oldnsegments;
6373   }
6374   return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPath(ps, pp, x, y) : false);
6375 }
6376 
6377 public alias isPointInPath = hitTestCurrFill; /// Ditto.
6378 
6379 /// Returns `true` if the given point is within the stroke of the currently defined path.
6380 /// This operation can be done before rasterizing the current path.
6381 /// Group: picking_api
6382 public bool hitTestCurrStroke (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
6383   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6384   int oldnpoints = ps.npoints;
6385   int oldnsegments = ps.nsegments;
6386   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/true);
6387   if (pp is null) return false; // oops
6388   scope(exit) {
6389     nvg__freePickPath(ps, pp);
6390     ps.npoints = oldnpoints;
6391     ps.nsegments = oldnsegments;
6392   }
6393   return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPathStroke(ps, pp, x, y) : false);
6394 }
6395 
6396 
6397 nothrow @trusted @nogc {
6398 extern(C) {
6399   private alias _compare_fp_t = int function (const void*, const void*) nothrow @nogc;
6400   private extern(C) void qsort (scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc;
6401 
6402   extern(C) int nvg__comparePaths (const void* a, const void* b) {
6403     return (*cast(const(NVGpickPath)**)b).order-(*cast(const(NVGpickPath)**)a).order;
6404   }
6405 }
6406 
6407 enum NVGPickEPS = 0.0001f;
6408 
6409 // Segment flags
6410 enum NVGSegmentFlags {
6411   Corner = 1,
6412   Bevel = 2,
6413   InnerBevel = 4,
6414   Cap = 8,
6415   Endcap = 16,
6416 }
6417 
6418 // Path flags
6419 enum NVGPathFlags : ushort {
6420   Fill = NVGPickKind.Fill,
6421   Stroke = NVGPickKind.Stroke,
6422   Scissor = 0x80,
6423 }
6424 
6425 struct NVGsegment {
6426   int firstPoint; // Index into NVGpickScene.points
6427   short type; // NVG_LINETO or NVG_BEZIERTO
6428   short flags; // Flags relate to the corner between the prev segment and this one.
6429   float[4] bounds;
6430   float[2] startDir; // Direction at t == 0
6431   float[2] endDir; // Direction at t == 1
6432   float[2] miterDir; // Direction of miter of corner between the prev segment and this one.
6433 }
6434 
6435 struct NVGpickSubPath {
6436   short winding; // TODO: Merge to flag field
6437   bool closed; // TODO: Merge to flag field
6438 
6439   int firstSegment; // Index into NVGpickScene.segments
6440   int nsegments;
6441 
6442   float[4] bounds;
6443 
6444   NVGpickSubPath* next;
6445 }
6446 
6447 struct NVGpickPath {
6448   int id;
6449   short flags;
6450   short order;
6451   float strokeWidth;
6452   float miterLimit;
6453   short lineCap;
6454   short lineJoin;
6455   bool evenOddMode;
6456 
6457   float[4] bounds;
6458   int scissor; // Indexes into ps->points and defines scissor rect as XVec, YVec and Center
6459 
6460   NVGpickSubPath* subPaths;
6461   NVGpickPath* next;
6462   NVGpickPath* cellnext;
6463 }
6464 
6465 struct NVGpickScene {
6466   int npaths;
6467 
6468   NVGpickPath* paths; // Linked list of paths
6469   NVGpickPath* lastPath; // The last path in the paths linked list (the first path added)
6470   NVGpickPath* freePaths; // Linked list of free paths
6471 
6472   NVGpickSubPath* freeSubPaths; // Linked list of free sub paths
6473 
6474   int width;
6475   int height;
6476 
6477   // Points for all path sub paths.
6478   float* points;
6479   int npoints;
6480   int cpoints;
6481 
6482   // Segments for all path sub paths
6483   NVGsegment* segments;
6484   int nsegments;
6485   int csegments;
6486 
6487   // Implicit quadtree
6488   float xdim; // Width / (1 << nlevels)
6489   float ydim; // Height / (1 << nlevels)
6490   int ncells; // Total number of cells in all levels
6491   int nlevels;
6492   NVGpickPath*** levels; // Index: [Level][LevelY * LevelW + LevelX] Value: Linked list of paths
6493 
6494   // Temp storage for picking
6495   int cpicked;
6496   NVGpickPath** picked;
6497 }
6498 
6499 
6500 // bounds utilities
6501 void nvg__initBounds (ref float[4] bounds) {
6502   bounds.ptr[0] = bounds.ptr[1] = float.max;
6503   bounds.ptr[2] = bounds.ptr[3] = -float.max;
6504 }
6505 
6506 void nvg__expandBounds (ref float[4] bounds, const(float)* points, int npoints) {
6507   npoints *= 2;
6508   for (int i = 0; i < npoints; i += 2) {
6509     bounds.ptr[0] = nvg__min(bounds.ptr[0], points[i]);
6510     bounds.ptr[1] = nvg__min(bounds.ptr[1], points[i+1]);
6511     bounds.ptr[2] = nvg__max(bounds.ptr[2], points[i]);
6512     bounds.ptr[3] = nvg__max(bounds.ptr[3], points[i+1]);
6513   }
6514 }
6515 
6516 void nvg__unionBounds (ref float[4] bounds, const scope ref float[4] boundsB) {
6517   bounds.ptr[0] = nvg__min(bounds.ptr[0], boundsB.ptr[0]);
6518   bounds.ptr[1] = nvg__min(bounds.ptr[1], boundsB.ptr[1]);
6519   bounds.ptr[2] = nvg__max(bounds.ptr[2], boundsB.ptr[2]);
6520   bounds.ptr[3] = nvg__max(bounds.ptr[3], boundsB.ptr[3]);
6521 }
6522 
6523 void nvg__intersectBounds (ref float[4] bounds, const scope ref float[4] boundsB) {
6524   bounds.ptr[0] = nvg__max(boundsB.ptr[0], bounds.ptr[0]);
6525   bounds.ptr[1] = nvg__max(boundsB.ptr[1], bounds.ptr[1]);
6526   bounds.ptr[2] = nvg__min(boundsB.ptr[2], bounds.ptr[2]);
6527   bounds.ptr[3] = nvg__min(boundsB.ptr[3], bounds.ptr[3]);
6528 
6529   bounds.ptr[2] = nvg__max(bounds.ptr[0], bounds.ptr[2]);
6530   bounds.ptr[3] = nvg__max(bounds.ptr[1], bounds.ptr[3]);
6531 }
6532 
6533 bool nvg__pointInBounds (in float x, in float y, const scope ref float[4] bounds) {
6534   pragma(inline, true);
6535   return (x >= bounds.ptr[0] && x <= bounds.ptr[2] && y >= bounds.ptr[1] && y <= bounds.ptr[3]);
6536 }
6537 
6538 // building paths & sub paths
6539 int nvg__pickSceneAddPoints (NVGpickScene* ps, const(float)* xy, int n) {
6540   import core.stdc.string : memcpy;
6541   if (ps.npoints+n > ps.cpoints) {
6542     import core.stdc.stdlib : realloc;
6543     int cpoints = ps.npoints+n+(ps.cpoints<<1);
6544     float* points = cast(float*)realloc(ps.points, float.sizeof*2*cpoints);
6545     if (points is null) assert(0, "NanoVega: out of memory");
6546     ps.points = points;
6547     ps.cpoints = cpoints;
6548   }
6549   int i = ps.npoints;
6550   if (xy !is null) memcpy(&ps.points[i*2], xy, float.sizeof*2*n);
6551   ps.npoints += n;
6552   return i;
6553 }
6554 
6555 void nvg__pickSubPathAddSegment (NVGpickScene* ps, NVGpickSubPath* psp, int firstPoint, int type, short flags) {
6556   NVGsegment* seg = null;
6557   if (ps.nsegments == ps.csegments) {
6558     int csegments = 1+ps.csegments+(ps.csegments<<1);
6559     NVGsegment* segments = cast(NVGsegment*)realloc(ps.segments, NVGsegment.sizeof*csegments);
6560     if (segments is null) assert(0, "NanoVega: out of memory");
6561     ps.segments = segments;
6562     ps.csegments = csegments;
6563   }
6564 
6565   if (psp.firstSegment == -1) psp.firstSegment = ps.nsegments;
6566 
6567   seg = &ps.segments[ps.nsegments];
6568   ++ps.nsegments;
6569   seg.firstPoint = firstPoint;
6570   seg.type = cast(short)type;
6571   seg.flags = flags;
6572   ++psp.nsegments;
6573 
6574   nvg__segmentDir(ps, psp, seg, 0, seg.startDir);
6575   nvg__segmentDir(ps, psp, seg, 1, seg.endDir);
6576 }
6577 
6578 void nvg__segmentDir (NVGpickScene* ps, NVGpickSubPath* psp, NVGsegment* seg, float t, ref float[2] d) {
6579   const(float)* points = &ps.points[seg.firstPoint*2];
6580   immutable float x0 = points[0*2+0], x1 = points[1*2+0];
6581   immutable float y0 = points[0*2+1], y1 = points[1*2+1];
6582   switch (seg.type) {
6583     case Command.LineTo:
6584       d.ptr[0] = x1-x0;
6585       d.ptr[1] = y1-y0;
6586       nvg__normalize(&d.ptr[0], &d.ptr[1]);
6587       break;
6588     case Command.BezierTo:
6589       immutable float x2 = points[2*2+0];
6590       immutable float y2 = points[2*2+1];
6591       immutable float x3 = points[3*2+0];
6592       immutable float y3 = points[3*2+1];
6593 
6594       immutable float omt = 1.0f-t;
6595       immutable float omt2 = omt*omt;
6596       immutable float t2 = t*t;
6597 
6598       d.ptr[0] =
6599         3.0f*omt2*(x1-x0)+
6600         6.0f*omt*t*(x2-x1)+
6601         3.0f*t2*(x3-x2);
6602 
6603       d.ptr[1] =
6604         3.0f*omt2*(y1-y0)+
6605         6.0f*omt*t*(y2-y1)+
6606         3.0f*t2*(y3-y2);
6607 
6608       nvg__normalize(&d.ptr[0], &d.ptr[1]);
6609       break;
6610     default:
6611       break;
6612   }
6613 }
6614 
6615 void nvg__pickSubPathAddFillSupports (NVGpickScene* ps, NVGpickSubPath* psp) {
6616   if (psp.firstSegment == -1) return;
6617   NVGsegment* segments = &ps.segments[psp.firstSegment];
6618   for (int s = 0; s < psp.nsegments; ++s) {
6619     NVGsegment* seg = &segments[s];
6620     const(float)* points = &ps.points[seg.firstPoint*2];
6621     if (seg.type == Command.LineTo) {
6622       nvg__initBounds(seg.bounds);
6623       nvg__expandBounds(seg.bounds, points, 2);
6624     } else {
6625       nvg__bezierBounds(points, seg.bounds);
6626     }
6627   }
6628 }
6629 
6630 void nvg__pickSubPathAddStrokeSupports (NVGpickScene* ps, NVGpickSubPath* psp, float strokeWidth, int lineCap, int lineJoin, float miterLimit) {
6631   if (psp.firstSegment == -1) return;
6632   immutable bool closed = psp.closed;
6633   const(float)* points = ps.points;
6634   NVGsegment* seg = null;
6635   NVGsegment* segments = &ps.segments[psp.firstSegment];
6636   int nsegments = psp.nsegments;
6637   NVGsegment* prevseg = (closed ? &segments[psp.nsegments-1] : null);
6638 
6639   int ns = 0; // nsupports
6640   float[32] supportingPoints = void;
6641   int firstPoint, lastPoint;
6642 
6643   if (!closed) {
6644     segments[0].flags |= NVGSegmentFlags.Cap;
6645     segments[nsegments-1].flags |= NVGSegmentFlags.Endcap;
6646   }
6647 
6648   for (int s = 0; s < nsegments; ++s) {
6649     seg = &segments[s];
6650     nvg__initBounds(seg.bounds);
6651 
6652     firstPoint = seg.firstPoint*2;
6653     lastPoint = firstPoint+(seg.type == Command.LineTo ? 2 : 6);
6654 
6655     ns = 0;
6656 
6657     // First two supporting points are either side of the start point
6658     supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[1]*strokeWidth;
6659     supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.startDir.ptr[0]*strokeWidth;
6660 
6661     supportingPoints.ptr[ns++] = points[firstPoint]+seg.startDir.ptr[1]*strokeWidth;
6662     supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[0]*strokeWidth;
6663 
6664     // Second two supporting points are either side of the end point
6665     supportingPoints.ptr[ns++] = points[lastPoint]-seg.endDir.ptr[1]*strokeWidth;
6666     supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[0]*strokeWidth;
6667 
6668     supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[1]*strokeWidth;
6669     supportingPoints.ptr[ns++] = points[lastPoint+1]-seg.endDir.ptr[0]*strokeWidth;
6670 
6671     if ((seg.flags&NVGSegmentFlags.Corner) && prevseg !is null) {
6672       seg.miterDir.ptr[0] = 0.5f*(-prevseg.endDir.ptr[1]-seg.startDir.ptr[1]);
6673       seg.miterDir.ptr[1] = 0.5f*(prevseg.endDir.ptr[0]+seg.startDir.ptr[0]);
6674 
6675       immutable float M2 = seg.miterDir.ptr[0]*seg.miterDir.ptr[0]+seg.miterDir.ptr[1]*seg.miterDir.ptr[1];
6676 
6677       if (M2 > 0.000001f) {
6678         float scale = 1.0f/M2;
6679         if (scale > 600.0f) scale = 600.0f;
6680         seg.miterDir.ptr[0] *= scale;
6681         seg.miterDir.ptr[1] *= scale;
6682       }
6683 
6684       //NVG_PICK_DEBUG_VECTOR_SCALE(&points[firstPoint], seg.miterDir, 10);
6685 
6686       // Add an additional support at the corner on the other line
6687       supportingPoints.ptr[ns++] = points[firstPoint]-prevseg.endDir.ptr[1]*strokeWidth;
6688       supportingPoints.ptr[ns++] = points[firstPoint+1]+prevseg.endDir.ptr[0]*strokeWidth;
6689 
6690       if (lineJoin == NVGLineCap.Miter || lineJoin == NVGLineCap.Bevel) {
6691         // Set a corner as beveled if the join type is bevel or mitered and
6692         // miterLimit is hit.
6693         if (lineJoin == NVGLineCap.Bevel || (M2*miterLimit*miterLimit) < 1.0f) {
6694           seg.flags |= NVGSegmentFlags.Bevel;
6695         } else {
6696           // Corner is mitered - add miter point as a support
6697           supportingPoints.ptr[ns++] = points[firstPoint]+seg.miterDir.ptr[0]*strokeWidth;
6698           supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.miterDir.ptr[1]*strokeWidth;
6699         }
6700       } else if (lineJoin == NVGLineCap.Round) {
6701         // ... and at the midpoint of the corner arc
6702         float[2] vertexN = [ -seg.startDir.ptr[0]+prevseg.endDir.ptr[0], -seg.startDir.ptr[1]+prevseg.endDir.ptr[1] ];
6703         nvg__normalize(&vertexN[0], &vertexN[1]);
6704 
6705         supportingPoints.ptr[ns++] = points[firstPoint]+vertexN[0]*strokeWidth;
6706         supportingPoints.ptr[ns++] = points[firstPoint+1]+vertexN[1]*strokeWidth;
6707       }
6708     }
6709 
6710     if (seg.flags&NVGSegmentFlags.Cap) {
6711       switch (lineCap) {
6712         case NVGLineCap.Butt:
6713           // supports for butt already added
6714           break;
6715         case NVGLineCap.Square:
6716           // square cap supports are just the original two supports moved out along the direction
6717           supportingPoints.ptr[ns++] = supportingPoints.ptr[0]-seg.startDir.ptr[0]*strokeWidth;
6718           supportingPoints.ptr[ns++] = supportingPoints.ptr[1]-seg.startDir.ptr[1]*strokeWidth;
6719           supportingPoints.ptr[ns++] = supportingPoints.ptr[2]-seg.startDir.ptr[0]*strokeWidth;
6720           supportingPoints.ptr[ns++] = supportingPoints.ptr[3]-seg.startDir.ptr[1]*strokeWidth;
6721           break;
6722         case NVGLineCap.Round:
6723           // add one additional support for the round cap along the dir
6724           supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[0]*strokeWidth;
6725           supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[1]*strokeWidth;
6726           break;
6727         default:
6728           break;
6729       }
6730     }
6731 
6732     if (seg.flags&NVGSegmentFlags.Endcap) {
6733       // end supporting points, either side of line
6734       int end = 4;
6735       switch(lineCap) {
6736         case NVGLineCap.Butt:
6737           // supports for butt already added
6738           break;
6739         case NVGLineCap.Square:
6740           // square cap supports are just the original two supports moved out along the direction
6741           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+0]+seg.endDir.ptr[0]*strokeWidth;
6742           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+1]+seg.endDir.ptr[1]*strokeWidth;
6743           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+2]+seg.endDir.ptr[0]*strokeWidth;
6744           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+3]+seg.endDir.ptr[1]*strokeWidth;
6745           break;
6746         case NVGLineCap.Round:
6747           // add one additional support for the round cap along the dir
6748           supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[0]*strokeWidth;
6749           supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[1]*strokeWidth;
6750           break;
6751         default:
6752           break;
6753       }
6754     }
6755 
6756     nvg__expandBounds(seg.bounds, supportingPoints.ptr, ns/2);
6757 
6758     prevseg = seg;
6759   }
6760 }
6761 
6762 NVGpickPath* nvg__pickPathCreate (NVGContext context, const(float)[] acommands, int id, bool forStroke) {
6763   NVGpickScene* ps = nvg__pickSceneGet(context);
6764   if (ps is null) return null;
6765 
6766   int i = 0;
6767 
6768   int ncommands = cast(int)acommands.length;
6769   const(float)* commands = acommands.ptr;
6770 
6771   NVGpickPath* pp = null;
6772   NVGpickSubPath* psp = null;
6773   float[2] start = void;
6774   int firstPoint;
6775 
6776   //bool hasHoles = false;
6777   NVGpickSubPath* prev = null;
6778 
6779   float[8] points = void;
6780   float[2] inflections = void;
6781   int ninflections = 0;
6782 
6783   NVGstate* state = nvg__getState(context);
6784   float[4] totalBounds = void;
6785   NVGsegment* segments = null;
6786   const(NVGsegment)* seg = null;
6787   NVGpickSubPath *curpsp;
6788 
6789   pp = nvg__allocPickPath(ps);
6790   if (pp is null) return null;
6791 
6792   pp.id = id;
6793 
6794   bool hasPoints = false;
6795 
6796   void closeIt () {
6797     if (psp is null || !hasPoints) return;
6798     if (ps.points[(ps.npoints-1)*2] != start.ptr[0] || ps.points[(ps.npoints-1)*2+1] != start.ptr[1]) {
6799       firstPoint = nvg__pickSceneAddPoints(ps, start.ptr, 1);
6800       nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, Command.LineTo, NVGSegmentFlags.Corner);
6801     }
6802     psp.closed = true;
6803   }
6804 
6805   while (i < ncommands) {
6806     int cmd = cast(int)commands[i++];
6807     switch (cmd) {
6808       case Command.MoveTo: // one coordinate pair
6809         const(float)* tfxy = commands+i;
6810         i += 2;
6811 
6812         // new starting point
6813         start.ptr[0..2] = tfxy[0..2];
6814 
6815         // start a new path for each sub path to handle sub paths that intersect other sub paths
6816         prev = psp;
6817         psp = nvg__allocPickSubPath(ps);
6818         if (psp is null) { psp = prev; break; }
6819         psp.firstSegment = -1;
6820         psp.winding = NVGSolidity.Solid;
6821         psp.next = prev;
6822 
6823         nvg__pickSceneAddPoints(ps, tfxy, 1);
6824         hasPoints = true;
6825         break;
6826       case Command.LineTo: // one coordinate pair
6827         const(float)* tfxy = commands+i;
6828         i += 2;
6829         firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 1);
6830         nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner);
6831         hasPoints = true;
6832         break;
6833       case Command.BezierTo: // three coordinate pairs
6834         const(float)* tfxy = commands+i;
6835         i += 3*2;
6836 
6837         // Split the curve at it's dx==0 or dy==0 inflection points.
6838         // Thus:
6839         //    A horizontal line only ever interects the curves once.
6840         //  and
6841         //    Finding the closest point on any curve converges more reliably.
6842 
6843         // NOTE: We could just split on dy==0 here.
6844 
6845         memcpy(&points.ptr[0], &ps.points[(ps.npoints-1)*2], float.sizeof*2);
6846         memcpy(&points.ptr[2], tfxy, float.sizeof*2*3);
6847 
6848         ninflections = 0;
6849         nvg__bezierInflections(points.ptr, 1, &ninflections, inflections.ptr);
6850         nvg__bezierInflections(points.ptr, 0, &ninflections, inflections.ptr);
6851 
6852         if (ninflections) {
6853           float previnfl = 0;
6854           float[8] pointsA = void, pointsB = void;
6855 
6856           nvg__smallsort(inflections.ptr, ninflections);
6857 
6858           for (int infl = 0; infl < ninflections; ++infl) {
6859             if (nvg__absf(inflections.ptr[infl]-previnfl) < NVGPickEPS) continue;
6860 
6861             immutable float t = (inflections.ptr[infl]-previnfl)*(1.0f/(1.0f-previnfl));
6862 
6863             previnfl = inflections.ptr[infl];
6864 
6865             nvg__splitBezier(points.ptr, t, pointsA.ptr, pointsB.ptr);
6866 
6867             firstPoint = nvg__pickSceneAddPoints(ps, &pointsA.ptr[2], 3);
6868             nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, (infl == 0) ? NVGSegmentFlags.Corner : 0);
6869 
6870             memcpy(points.ptr, pointsB.ptr, float.sizeof*8);
6871           }
6872 
6873           firstPoint = nvg__pickSceneAddPoints(ps, &pointsB.ptr[2], 3);
6874           nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, 0);
6875         } else {
6876           firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 3);
6877           nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner);
6878         }
6879         hasPoints = true;
6880         break;
6881       case Command.Close:
6882         closeIt();
6883         break;
6884       case Command.Winding:
6885         psp.winding = cast(short)cast(int)commands[i];
6886         //if (psp.winding == NVGSolidity.Hole) hasHoles = true;
6887         i += 1;
6888         break;
6889       default:
6890         break;
6891     }
6892   }
6893 
6894   // force-close filled paths
6895   if (psp !is null && !forStroke && hasPoints && !psp.closed) closeIt();
6896 
6897   pp.flags = (forStroke ? NVGPathFlags.Stroke : NVGPathFlags.Fill);
6898   pp.subPaths = psp;
6899   pp.strokeWidth = state.strokeWidth*0.5f;
6900   pp.miterLimit = state.miterLimit;
6901   pp.lineCap = cast(short)state.lineCap;
6902   pp.lineJoin = cast(short)state.lineJoin;
6903   pp.evenOddMode = nvg__getState(context).evenOddMode;
6904 
6905   nvg__initBounds(totalBounds);
6906 
6907   for (curpsp = psp; curpsp; curpsp = curpsp.next) {
6908     if (forStroke) {
6909       nvg__pickSubPathAddStrokeSupports(ps, curpsp, pp.strokeWidth, pp.lineCap, pp.lineJoin, pp.miterLimit);
6910     } else {
6911       nvg__pickSubPathAddFillSupports(ps, curpsp);
6912     }
6913 
6914     if (curpsp.firstSegment == -1) continue;
6915     segments = &ps.segments[curpsp.firstSegment];
6916     nvg__initBounds(curpsp.bounds);
6917     for (int s = 0; s < curpsp.nsegments; ++s) {
6918       seg = &segments[s];
6919       //NVG_PICK_DEBUG_BOUNDS(seg.bounds);
6920       nvg__unionBounds(curpsp.bounds, seg.bounds);
6921     }
6922 
6923     nvg__unionBounds(totalBounds, curpsp.bounds);
6924   }
6925 
6926   // Store the scissor rect if present.
6927   if (state.scissor.extent.ptr[0] != -1.0f) {
6928     // Use points storage to store the scissor data
6929     pp.scissor = nvg__pickSceneAddPoints(ps, null, 4);
6930     float* scissor = &ps.points[pp.scissor*2];
6931 
6932     //memcpy(scissor, state.scissor.xform.ptr, 6*float.sizeof);
6933     scissor[0..6] = state.scissor.xform.mat[];
6934     memcpy(scissor+6, state.scissor.extent.ptr, 2*float.sizeof);
6935 
6936     pp.flags |= NVGPathFlags.Scissor;
6937   }
6938 
6939   memcpy(pp.bounds.ptr, totalBounds.ptr, float.sizeof*4);
6940 
6941   return pp;
6942 }
6943 
6944 
6945 // Struct management
6946 NVGpickPath* nvg__allocPickPath (NVGpickScene* ps) {
6947   NVGpickPath* pp = ps.freePaths;
6948   if (pp !is null) {
6949     ps.freePaths = pp.next;
6950   } else {
6951     pp = cast(NVGpickPath*)malloc(NVGpickPath.sizeof);
6952   }
6953   memset(pp, 0, NVGpickPath.sizeof);
6954   return pp;
6955 }
6956 
6957 // Put a pick path and any sub paths (back) to the free lists.
6958 void nvg__freePickPath (NVGpickScene* ps, NVGpickPath* pp) {
6959   // Add all sub paths to the sub path free list.
6960   // Finds the end of the path sub paths, links that to the current
6961   // sub path free list head and replaces the head ptr with the
6962   // head path sub path entry.
6963   NVGpickSubPath* psp = null;
6964   for (psp = pp.subPaths; psp !is null && psp.next !is null; psp = psp.next) {}
6965 
6966   if (psp) {
6967     psp.next = ps.freeSubPaths;
6968     ps.freeSubPaths = pp.subPaths;
6969   }
6970   pp.subPaths = null;
6971 
6972   // Add the path to the path freelist
6973   pp.next = ps.freePaths;
6974   ps.freePaths = pp;
6975   if (pp.next is null) ps.lastPath = pp;
6976 }
6977 
6978 NVGpickSubPath* nvg__allocPickSubPath (NVGpickScene* ps) {
6979   NVGpickSubPath* psp = ps.freeSubPaths;
6980   if (psp !is null) {
6981     ps.freeSubPaths = psp.next;
6982   } else {
6983     psp = cast(NVGpickSubPath*)malloc(NVGpickSubPath.sizeof);
6984     if (psp is null) return null;
6985   }
6986   memset(psp, 0, NVGpickSubPath.sizeof);
6987   return psp;
6988 }
6989 
6990 void nvg__returnPickSubPath (NVGpickScene* ps, NVGpickSubPath* psp) {
6991   psp.next = ps.freeSubPaths;
6992   ps.freeSubPaths = psp;
6993 }
6994 
6995 NVGpickScene* nvg__allocPickScene () {
6996   NVGpickScene* ps = cast(NVGpickScene*)malloc(NVGpickScene.sizeof);
6997   if (ps is null) return null;
6998   memset(ps, 0, NVGpickScene.sizeof);
6999   ps.nlevels = 5;
7000   return ps;
7001 }
7002 
7003 void nvg__deletePickScene (NVGpickScene* ps) {
7004   NVGpickPath* pp;
7005   NVGpickSubPath* psp;
7006 
7007   // Add all paths (and thus sub paths) to the free list(s).
7008   while (ps.paths !is null) {
7009     pp = ps.paths.next;
7010     nvg__freePickPath(ps, ps.paths);
7011     ps.paths = pp;
7012   }
7013 
7014   // Delete all paths
7015   while (ps.freePaths !is null) {
7016     pp = ps.freePaths;
7017     ps.freePaths = pp.next;
7018     while (pp.subPaths !is null) {
7019       psp = pp.subPaths;
7020       pp.subPaths = psp.next;
7021       free(psp);
7022     }
7023     free(pp);
7024   }
7025 
7026   // Delete all sub paths
7027   while (ps.freeSubPaths !is null) {
7028     psp = ps.freeSubPaths.next;
7029     free(ps.freeSubPaths);
7030     ps.freeSubPaths = psp;
7031   }
7032 
7033   ps.npoints = 0;
7034   ps.nsegments = 0;
7035 
7036   if (ps.levels !is null) {
7037     free(ps.levels[0]);
7038     free(ps.levels);
7039   }
7040 
7041   if (ps.picked !is null) free(ps.picked);
7042   if (ps.points !is null) free(ps.points);
7043   if (ps.segments !is null) free(ps.segments);
7044 
7045   free(ps);
7046 }
7047 
7048 NVGpickScene* nvg__pickSceneGet (NVGContext ctx) {
7049   if (ctx.pickScene is null) ctx.pickScene = nvg__allocPickScene();
7050   return ctx.pickScene;
7051 }
7052 
7053 
7054 // Applies Casteljau's algorithm to a cubic bezier for a given parameter t
7055 // points is 4 points (8 floats)
7056 // lvl1 is 3 points (6 floats)
7057 // lvl2 is 2 points (4 floats)
7058 // lvl3 is 1 point (2 floats)
7059 void nvg__casteljau (const(float)* points, float t, float* lvl1, float* lvl2, float* lvl3) {
7060   enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0;
7061   enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1;
7062 
7063   // Level 1
7064   lvl1[x0] = (points[x1]-points[x0])*t+points[x0];
7065   lvl1[y0] = (points[y1]-points[y0])*t+points[y0];
7066 
7067   lvl1[x1] = (points[x2]-points[x1])*t+points[x1];
7068   lvl1[y1] = (points[y2]-points[y1])*t+points[y1];
7069 
7070   lvl1[x2] = (points[x3]-points[x2])*t+points[x2];
7071   lvl1[y2] = (points[y3]-points[y2])*t+points[y2];
7072 
7073   // Level 2
7074   lvl2[x0] = (lvl1[x1]-lvl1[x0])*t+lvl1[x0];
7075   lvl2[y0] = (lvl1[y1]-lvl1[y0])*t+lvl1[y0];
7076 
7077   lvl2[x1] = (lvl1[x2]-lvl1[x1])*t+lvl1[x1];
7078   lvl2[y1] = (lvl1[y2]-lvl1[y1])*t+lvl1[y1];
7079 
7080   // Level 3
7081   lvl3[x0] = (lvl2[x1]-lvl2[x0])*t+lvl2[x0];
7082   lvl3[y0] = (lvl2[y1]-lvl2[y0])*t+lvl2[y0];
7083 }
7084 
7085 // Calculates a point on a bezier at point t.
7086 void nvg__bezierEval (const(float)* points, float t, ref float[2] tpoint) {
7087   immutable float omt = 1-t;
7088   immutable float omt3 = omt*omt*omt;
7089   immutable float omt2 = omt*omt;
7090   immutable float t3 = t*t*t;
7091   immutable float t2 = t*t;
7092 
7093   tpoint.ptr[0] =
7094     points[0]*omt3+
7095     points[2]*3.0f*omt2*t+
7096     points[4]*3.0f*omt*t2+
7097     points[6]*t3;
7098 
7099   tpoint.ptr[1] =
7100     points[1]*omt3+
7101     points[3]*3.0f*omt2*t+
7102     points[5]*3.0f*omt*t2+
7103     points[7]*t3;
7104 }
7105 
7106 // Splits a cubic bezier curve into two parts at point t.
7107 void nvg__splitBezier (const(float)* points, float t, float* pointsA, float* pointsB) {
7108   enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0;
7109   enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1;
7110 
7111   float[6] lvl1 = void;
7112   float[4] lvl2 = void;
7113   float[2] lvl3 = void;
7114 
7115   nvg__casteljau(points, t, lvl1.ptr, lvl2.ptr, lvl3.ptr);
7116 
7117   // First half
7118   pointsA[x0] = points[x0];
7119   pointsA[y0] = points[y0];
7120 
7121   pointsA[x1] = lvl1.ptr[x0];
7122   pointsA[y1] = lvl1.ptr[y0];
7123 
7124   pointsA[x2] = lvl2.ptr[x0];
7125   pointsA[y2] = lvl2.ptr[y0];
7126 
7127   pointsA[x3] = lvl3.ptr[x0];
7128   pointsA[y3] = lvl3.ptr[y0];
7129 
7130   // Second half
7131   pointsB[x0] = lvl3.ptr[x0];
7132   pointsB[y0] = lvl3.ptr[y0];
7133 
7134   pointsB[x1] = lvl2.ptr[x1];
7135   pointsB[y1] = lvl2.ptr[y1];
7136 
7137   pointsB[x2] = lvl1.ptr[x2];
7138   pointsB[y2] = lvl1.ptr[y2];
7139 
7140   pointsB[x3] = points[x3];
7141   pointsB[y3] = points[y3];
7142 }
7143 
7144 // Calculates the inflection points in coordinate coord (X = 0, Y = 1) of a cubic bezier.
7145 // Appends any found inflection points to the array inflections and increments *ninflections.
7146 // So finds the parameters where dx/dt or dy/dt is 0
7147 void nvg__bezierInflections (const(float)* points, int coord, int* ninflections, float* inflections) {
7148   immutable float v0 = points[0*2+coord], v1 = points[1*2+coord], v2 = points[2*2+coord], v3 = points[3*2+coord];
7149   float[2] t = void;
7150   int nvalid = *ninflections;
7151 
7152   immutable float a = 3.0f*( -v0+3.0f*v1-3.0f*v2+v3 );
7153   immutable float b = 6.0f*( v0-2.0f*v1+v2 );
7154   immutable float c = 3.0f*( v1-v0 );
7155 
7156   float d = b*b-4.0f*a*c;
7157   if (nvg__absf(d-0.0f) < NVGPickEPS) {
7158     // Zero or one root
7159     t.ptr[0] = -b/2.0f*a;
7160     if (t.ptr[0] > NVGPickEPS && t.ptr[0] < (1.0f-NVGPickEPS)) {
7161       inflections[nvalid] = t.ptr[0];
7162       ++nvalid;
7163     }
7164   } else if (d > NVGPickEPS) {
7165     // zero, one or two roots
7166     d = nvg__sqrtf(d);
7167 
7168     t.ptr[0] = (-b+d)/(2.0f*a);
7169     t.ptr[1] = (-b-d)/(2.0f*a);
7170 
7171     for (int i = 0; i < 2; ++i) {
7172       if (t.ptr[i] > NVGPickEPS && t.ptr[i] < (1.0f-NVGPickEPS)) {
7173         inflections[nvalid] = t.ptr[i];
7174         ++nvalid;
7175       }
7176     }
7177   } else {
7178     // zero roots
7179   }
7180 
7181   *ninflections = nvalid;
7182 }
7183 
7184 // Sort a small number of floats in ascending order (0 < n < 6)
7185 void nvg__smallsort (float* values, int n) {
7186   bool bSwapped = true;
7187   for (int j = 0; j < n-1 && bSwapped; ++j) {
7188     bSwapped = false;
7189     for (int i = 0; i < n-1; ++i) {
7190       if (values[i] > values[i+1]) {
7191         auto tmp = values[i];
7192         values[i] = values[i+1];
7193         values[i+1] = tmp;
7194       }
7195     }
7196   }
7197 }
7198 
7199 // Calculates the bounding rect of a given cubic bezier curve.
7200 void nvg__bezierBounds (const(float)* points, ref float[4] bounds) {
7201   float[4] inflections = void;
7202   int ninflections = 0;
7203   float[2] tpoint = void;
7204 
7205   nvg__initBounds(bounds);
7206 
7207   // Include start and end points in bounds
7208   nvg__expandBounds(bounds, &points[0], 1);
7209   nvg__expandBounds(bounds, &points[6], 1);
7210 
7211   // Calculate dx==0 and dy==0 inflection points and add them to the bounds
7212 
7213   nvg__bezierInflections(points, 0, &ninflections, inflections.ptr);
7214   nvg__bezierInflections(points, 1, &ninflections, inflections.ptr);
7215 
7216   foreach (immutable int i; 0..ninflections) {
7217     nvg__bezierEval(points, inflections[i], tpoint);
7218     nvg__expandBounds(bounds, tpoint.ptr, 1);
7219   }
7220 }
7221 
7222 // Checks to see if a line originating from x,y along the +ve x axis
7223 // intersects the given line (points[0],points[1]) -> (points[2], points[3]).
7224 // Returns `true` on intersection.
7225 // Horizontal lines are never hit.
7226 bool nvg__intersectLine (const(float)* points, float x, float y) {
7227   immutable float x1 = points[0];
7228   immutable float y1 = points[1];
7229   immutable float x2 = points[2];
7230   immutable float y2 = points[3];
7231   immutable float d = y2-y1;
7232   if (d > NVGPickEPS || d < -NVGPickEPS) {
7233     immutable float s = (x2-x1)/d;
7234     immutable float lineX = x1+(y-y1)*s;
7235     return (lineX > x);
7236   } else {
7237     return false;
7238   }
7239 }
7240 
7241 // Checks to see if a line originating from x,y along the +ve x axis intersects the given bezier.
7242 // It is assumed that the line originates from within the bounding box of
7243 // the bezier and that the curve has no dy=0 inflection points.
7244 // Returns the number of intersections found (which is either 1 or 0).
7245 int nvg__intersectBezier (const(float)* points, float x, float y) {
7246   immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0];
7247   immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1];
7248 
7249   if (y0 == y1 && y1 == y2 && y2 == y3) return 0;
7250 
7251   // Initial t guess
7252   float t = void;
7253        if (y3 != y0) t = (y-y0)/(y3-y0);
7254   else if (x3 != x0) t = (x-x0)/(x3-x0);
7255   else t = 0.5f;
7256 
7257   // A few Newton iterations
7258   for (int iter = 0; iter < 6; ++iter) {
7259     immutable float omt = 1-t;
7260     immutable float omt2 = omt*omt;
7261     immutable float t2 = t*t;
7262     immutable float omt3 = omt2*omt;
7263     immutable float t3 = t2*t;
7264 
7265     immutable float ty = y0*omt3 +
7266       y1*3.0f*omt2*t +
7267       y2*3.0f*omt*t2 +
7268       y3*t3;
7269 
7270     // Newton iteration
7271     immutable float dty = 3.0f*omt2*(y1-y0) +
7272       6.0f*omt*t*(y2-y1) +
7273       3.0f*t2*(y3-y2);
7274 
7275     // dty will never == 0 since:
7276     //  Either omt, omt2 are zero OR t2 is zero
7277     //  y0 != y1 != y2 != y3 (checked above)
7278     t = t-(ty-y)/dty;
7279   }
7280 
7281   {
7282     immutable float omt = 1-t;
7283     immutable float omt2 = omt*omt;
7284     immutable float t2 = t*t;
7285     immutable float omt3 = omt2*omt;
7286     immutable float t3 = t2*t;
7287 
7288     immutable float tx =
7289       x0*omt3+
7290       x1*3.0f*omt2*t+
7291       x2*3.0f*omt*t2+
7292       x3*t3;
7293 
7294     return (tx > x ? 1 : 0);
7295   }
7296 }
7297 
7298 // Finds the closest point on a line to a given point
7299 void nvg__closestLine (const(float)* points, float x, float y, float* closest, float* ot) {
7300   immutable float x1 = points[0];
7301   immutable float y1 = points[1];
7302   immutable float x2 = points[2];
7303   immutable float y2 = points[3];
7304   immutable float pqx = x2-x1;
7305   immutable float pqz = y2-y1;
7306   immutable float dx = x-x1;
7307   immutable float dz = y-y1;
7308   immutable float d = pqx*pqx+pqz*pqz;
7309   float t = pqx*dx+pqz*dz;
7310   if (d > 0) t /= d;
7311   if (t < 0) t = 0; else if (t > 1) t = 1;
7312   closest[0] = x1+t*pqx;
7313   closest[1] = y1+t*pqz;
7314   *ot = t;
7315 }
7316 
7317 // Finds the closest point on a curve for a given point (x,y).
7318 // Assumes that the curve has no dx==0 or dy==0 inflection points.
7319 void nvg__closestBezier (const(float)* points, float x, float y, float* closest, float *ot) {
7320   immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0];
7321   immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1];
7322 
7323   // This assumes that the curve has no dy=0 inflection points.
7324 
7325   // Initial t guess
7326   float t = 0.5f;
7327 
7328   // A few Newton iterations
7329   for (int iter = 0; iter < 6; ++iter) {
7330     immutable float omt = 1-t;
7331     immutable float omt2 = omt*omt;
7332     immutable float t2 = t*t;
7333     immutable float omt3 = omt2*omt;
7334     immutable float t3 = t2*t;
7335 
7336     immutable float ty =
7337       y0*omt3+
7338       y1*3.0f*omt2*t+
7339       y2*3.0f*omt*t2+
7340       y3*t3;
7341 
7342     immutable float tx =
7343       x0*omt3+
7344       x1*3.0f*omt2*t+
7345       x2*3.0f*omt*t2+
7346       x3*t3;
7347 
7348     // Newton iteration
7349     immutable float dty =
7350       3.0f*omt2*(y1-y0)+
7351       6.0f*omt*t*(y2-y1)+
7352       3.0f*t2*(y3-y2);
7353 
7354     immutable float ddty =
7355       6.0f*omt*(y2-2.0f*y1+y0)+
7356       6.0f*t*(y3-2.0f*y2+y1);
7357 
7358     immutable float dtx =
7359       3.0f*omt2*(x1-x0)+
7360       6.0f*omt*t*(x2-x1)+
7361       3.0f*t2*(x3-x2);
7362 
7363     immutable float ddtx =
7364       6.0f*omt*(x2-2.0f*x1+x0)+
7365       6.0f*t*(x3-2.0f*x2+x1);
7366 
7367     immutable float errorx = tx-x;
7368     immutable float errory = ty-y;
7369 
7370     immutable float n = errorx*dtx+errory*dty;
7371     if (n == 0) break;
7372 
7373     immutable float d = dtx*dtx+dty*dty+errorx*ddtx+errory*ddty;
7374     if (d != 0) t = t-n/d; else break;
7375   }
7376 
7377   t = nvg__max(0, nvg__min(1.0, t));
7378   *ot = t;
7379   {
7380     immutable float omt = 1-t;
7381     immutable float omt2 = omt*omt;
7382     immutable float t2 = t*t;
7383     immutable float omt3 = omt2*omt;
7384     immutable float t3 = t2*t;
7385 
7386     immutable float ty =
7387       y0*omt3+
7388       y1*3.0f*omt2*t+
7389       y2*3.0f*omt*t2+
7390       y3*t3;
7391 
7392     immutable float tx =
7393       x0*omt3+
7394       x1*3.0f*omt2*t+
7395       x2*3.0f*omt*t2+
7396       x3*t3;
7397 
7398     closest[0] = tx;
7399     closest[1] = ty;
7400   }
7401 }
7402 
7403 // Returns:
7404 //  1  If (x,y) is contained by the stroke of the path
7405 //  0  If (x,y) is not contained by the path.
7406 int nvg__pickSubPathStroke (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, float strokeWidth, int lineCap, int lineJoin) {
7407   if (!nvg__pointInBounds(x, y, psp.bounds)) return 0;
7408   if (psp.firstSegment == -1) return 0;
7409 
7410   float[2] closest = void;
7411   float[2] d = void;
7412   float t = void;
7413 
7414   // trace a line from x,y out along the positive x axis and count the number of intersections
7415   int nsegments = psp.nsegments;
7416   const(NVGsegment)* seg = ps.segments+psp.firstSegment;
7417   const(NVGsegment)* prevseg = (psp.closed ? &ps.segments[psp.firstSegment+nsegments-1] : null);
7418   immutable float strokeWidthSqd = strokeWidth*strokeWidth;
7419 
7420   for (int s = 0; s < nsegments; ++s, prevseg = seg, ++seg) {
7421     if (nvg__pointInBounds(x, y, seg.bounds)) {
7422       // Line potentially hits stroke.
7423       switch (seg.type) {
7424         case Command.LineTo:
7425           nvg__closestLine(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t);
7426           break;
7427         case Command.BezierTo:
7428           nvg__closestBezier(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t);
7429           break;
7430         default:
7431           continue;
7432       }
7433 
7434       d.ptr[0] = x-closest.ptr[0];
7435       d.ptr[1] = y-closest.ptr[1];
7436 
7437       if ((t >= NVGPickEPS && t <= 1.0f-NVGPickEPS) ||
7438           (seg.flags&(NVGSegmentFlags.Corner|NVGSegmentFlags.Cap|NVGSegmentFlags.Endcap)) == 0 ||
7439           (lineJoin == NVGLineCap.Round))
7440       {
7441         // Closest point is in the middle of the line/curve, at a rounded join/cap
7442         // or at a smooth join
7443         immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7444         if (distSqd < strokeWidthSqd) return 1;
7445       } else if ((t > 1.0f-NVGPickEPS && (seg.flags&NVGSegmentFlags.Endcap)) ||
7446                  (t < NVGPickEPS && (seg.flags&NVGSegmentFlags.Cap))) {
7447         switch (lineCap) {
7448           case NVGLineCap.Butt:
7449             immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7450             immutable float dirD = (t < NVGPickEPS ?
7451               -(d.ptr[0]*seg.startDir.ptr[0]+d.ptr[1]*seg.startDir.ptr[1]) :
7452                 d.ptr[0]*seg.endDir.ptr[0]+d.ptr[1]*seg.endDir.ptr[1]);
7453             if (dirD < -NVGPickEPS && distSqd < strokeWidthSqd) return 1;
7454             break;
7455           case NVGLineCap.Square:
7456             if (nvg__absf(d.ptr[0]) < strokeWidth && nvg__absf(d.ptr[1]) < strokeWidth) return 1;
7457             break;
7458           case NVGLineCap.Round:
7459             immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7460             if (distSqd < strokeWidthSqd) return 1;
7461             break;
7462           default:
7463             break;
7464         }
7465       } else if (seg.flags&NVGSegmentFlags.Corner) {
7466         // Closest point is at a corner
7467         const(NVGsegment)* seg0, seg1;
7468 
7469         if (t < NVGPickEPS) {
7470           seg0 = prevseg;
7471           seg1 = seg;
7472         } else {
7473           seg0 = seg;
7474           seg1 = (s == nsegments-1 ? &ps.segments[psp.firstSegment] : seg+1);
7475         }
7476 
7477         if (!(seg1.flags&NVGSegmentFlags.Bevel)) {
7478           immutable float prevNDist = -seg0.endDir.ptr[1]*d.ptr[0]+seg0.endDir.ptr[0]*d.ptr[1];
7479           immutable float curNDist = seg1.startDir.ptr[1]*d.ptr[0]-seg1.startDir.ptr[0]*d.ptr[1];
7480           if (nvg__absf(prevNDist) < strokeWidth && nvg__absf(curNDist) < strokeWidth) return 1;
7481         } else {
7482           d.ptr[0] -= -seg1.startDir.ptr[1]*strokeWidth;
7483           d.ptr[1] -= +seg1.startDir.ptr[0]*strokeWidth;
7484           if (seg1.miterDir.ptr[0]*d.ptr[0]+seg1.miterDir.ptr[1]*d.ptr[1] < 0) return 1;
7485         }
7486       }
7487     }
7488   }
7489 
7490   return 0;
7491 }
7492 
7493 // Returns:
7494 //   1  If (x,y) is contained by the path and the path is solid.
7495 //  -1  If (x,y) is contained by the path and the path is a hole.
7496 //   0  If (x,y) is not contained by the path.
7497 int nvg__pickSubPath (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, bool evenOddMode) {
7498   if (!nvg__pointInBounds(x, y, psp.bounds)) return 0;
7499   if (psp.firstSegment == -1) return 0;
7500 
7501   const(NVGsegment)* seg = &ps.segments[psp.firstSegment];
7502   int nsegments = psp.nsegments;
7503   int nintersections = 0;
7504 
7505   // trace a line from x,y out along the positive x axis and count the number of intersections
7506   for (int s = 0; s < nsegments; ++s, ++seg) {
7507     if ((seg.bounds.ptr[1]-NVGPickEPS) < y &&
7508         (seg.bounds.ptr[3]-NVGPickEPS) > y &&
7509         seg.bounds.ptr[2] > x)
7510     {
7511       // Line hits the box.
7512       switch (seg.type) {
7513         case Command.LineTo:
7514           if (seg.bounds.ptr[0] > x) {
7515             // line originates outside the box
7516             ++nintersections;
7517           } else {
7518             // line originates inside the box
7519             nintersections += nvg__intersectLine(&ps.points[seg.firstPoint*2], x, y);
7520           }
7521           break;
7522         case Command.BezierTo:
7523           if (seg.bounds.ptr[0] > x) {
7524             // line originates outside the box
7525             ++nintersections;
7526           } else {
7527             // line originates inside the box
7528             nintersections += nvg__intersectBezier(&ps.points[seg.firstPoint*2], x, y);
7529           }
7530           break;
7531         default:
7532           break;
7533       }
7534     }
7535   }
7536 
7537   if (evenOddMode) {
7538     return nintersections;
7539   } else {
7540     return (nintersections&1 ? (psp.winding == NVGSolidity.Solid ? 1 : -1) : 0);
7541   }
7542 }
7543 
7544 bool nvg__pickPath (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) {
7545   int pickCount = 0;
7546   const(NVGpickSubPath)* psp = pp.subPaths;
7547   while (psp !is null) {
7548     pickCount += nvg__pickSubPath(ps, psp, x, y, pp.evenOddMode);
7549     psp = psp.next;
7550   }
7551   return ((pp.evenOddMode ? pickCount&1 : pickCount) != 0);
7552 }
7553 
7554 bool nvg__pickPathStroke (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) {
7555   const(NVGpickSubPath)* psp = pp.subPaths;
7556   while (psp !is null) {
7557     if (nvg__pickSubPathStroke(ps, psp, x, y, pp.strokeWidth, pp.lineCap, pp.lineJoin)) return true;
7558     psp = psp.next;
7559   }
7560   return false;
7561 }
7562 
7563 bool nvg__pickPathTestBounds (NVGContext ctx, const NVGpickScene* ps, const NVGpickPath* pp, float x, float y) {
7564   if (nvg__pointInBounds(x, y, pp.bounds)) {
7565     //{ import core.stdc.stdio; printf("  (0): in bounds!\n"); }
7566     if (pp.flags&NVGPathFlags.Scissor) {
7567       const(float)* scissor = &ps.points[pp.scissor*2];
7568       // untransform scissor translation
7569       float stx = void, sty = void;
7570       ctx.gpuUntransformPoint(&stx, &sty, scissor[4], scissor[5]);
7571       immutable float rx = x-stx;
7572       immutable float ry = y-sty;
7573       //{ 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]); }
7574       if (nvg__absf((scissor[0]*rx)+(scissor[1]*ry)) > scissor[6] ||
7575           nvg__absf((scissor[2]*rx)+(scissor[3]*ry)) > scissor[7])
7576       {
7577         //{ import core.stdc.stdio; printf("    (1): scissor reject!\n"); }
7578         return false;
7579       }
7580     }
7581     return true;
7582   }
7583   return false;
7584 }
7585 
7586 int nvg__countBitsUsed (uint v) pure {
7587   pragma(inline, true);
7588   import core.bitop : bsr;
7589   return (v != 0 ? bsr(v)+1 : 0);
7590 }
7591 
7592 void nvg__pickSceneInsert (NVGpickScene* ps, NVGpickPath* pp) {
7593   if (ps is null || pp is null) return;
7594 
7595   int[4] cellbounds;
7596   int base = ps.nlevels-1;
7597   int level;
7598   int levelwidth;
7599   int levelshift;
7600   int levelx;
7601   int levely;
7602   NVGpickPath** cell = null;
7603 
7604   // Bit tricks for inserting into an implicit quadtree.
7605 
7606   // Calc bounds of path in cells at the lowest level
7607   cellbounds.ptr[0] = cast(int)(pp.bounds.ptr[0]/ps.xdim);
7608   cellbounds.ptr[1] = cast(int)(pp.bounds.ptr[1]/ps.ydim);
7609   cellbounds.ptr[2] = cast(int)(pp.bounds.ptr[2]/ps.xdim);
7610   cellbounds.ptr[3] = cast(int)(pp.bounds.ptr[3]/ps.ydim);
7611 
7612   // Find which bits differ between the min/max x/y coords
7613   cellbounds.ptr[0] ^= cellbounds.ptr[2];
7614   cellbounds.ptr[1] ^= cellbounds.ptr[3];
7615 
7616   // Use the number of bits used (countBitsUsed(x) == sizeof(int) * 8 - clz(x);
7617   // to calculate the level to insert at (the level at which the bounds fit in a single cell)
7618   level = nvg__min(base-nvg__countBitsUsed(cellbounds.ptr[0]), base-nvg__countBitsUsed(cellbounds.ptr[1]));
7619   if (level < 0) level = 0;
7620   //{ 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]); }
7621   //level = 0;
7622 
7623   // Find the correct cell in the chosen level, clamping to the edges.
7624   levelwidth = 1<<level;
7625   levelshift = (ps.nlevels-level)-1;
7626   levelx = nvg__clamp(cellbounds.ptr[2]>>levelshift, 0, levelwidth-1);
7627   levely = nvg__clamp(cellbounds.ptr[3]>>levelshift, 0, levelwidth-1);
7628 
7629   // Insert the path into the linked list at that cell.
7630   cell = &ps.levels[level][levely*levelwidth+levelx];
7631 
7632   pp.cellnext = *cell;
7633   *cell = pp;
7634 
7635   if (ps.paths is null) ps.lastPath = pp;
7636   pp.next = ps.paths;
7637   ps.paths = pp;
7638 
7639   // Store the order (depth) of the path for picking ops.
7640   pp.order = cast(short)ps.npaths;
7641   ++ps.npaths;
7642 }
7643 
7644 void nvg__pickBeginFrame (NVGContext ctx, int width, int height) {
7645   NVGpickScene* ps = nvg__pickSceneGet(ctx);
7646 
7647   //NVG_PICK_DEBUG_NEWFRAME();
7648 
7649   // Return all paths & sub paths from last frame to the free list
7650   while (ps.paths !is null) {
7651     NVGpickPath* pp = ps.paths.next;
7652     nvg__freePickPath(ps, ps.paths);
7653     ps.paths = pp;
7654   }
7655 
7656   ps.paths = null;
7657   ps.npaths = 0;
7658 
7659   // Store the screen metrics for the quadtree
7660   ps.width = width;
7661   ps.height = height;
7662 
7663   immutable float lowestSubDiv = cast(float)(1<<(ps.nlevels-1));
7664   ps.xdim = cast(float)width/lowestSubDiv;
7665   ps.ydim = cast(float)height/lowestSubDiv;
7666 
7667   // Allocate the quadtree if required.
7668   if (ps.levels is null) {
7669     int ncells = 1;
7670 
7671     ps.levels = cast(NVGpickPath***)malloc((NVGpickPath**).sizeof*ps.nlevels);
7672     for (int l = 0; l < ps.nlevels; ++l) {
7673       int leveldim = 1<<l;
7674       ncells += leveldim*leveldim;
7675     }
7676 
7677     ps.levels[0] = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ncells);
7678 
7679     int cell = 1;
7680     for (int l = 1; l < ps.nlevels; ++l) {
7681       ps.levels[l] = &ps.levels[0][cell];
7682       int leveldim = 1<<l;
7683       cell += leveldim*leveldim;
7684     }
7685 
7686     ps.ncells = ncells;
7687   }
7688   memset(ps.levels[0], 0, ps.ncells*(NVGpickPath*).sizeof);
7689 
7690   // Allocate temporary storage for nvgHitTestAll results if required.
7691   if (ps.picked is null) {
7692     ps.cpicked = 16;
7693     ps.picked = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ps.cpicked);
7694   }
7695 
7696   ps.npoints = 0;
7697   ps.nsegments = 0;
7698 }
7699 } // nothrow @trusted @nogc
7700 
7701 
7702 /// Return outline of the current path. Returned outline is not flattened.
7703 /// Group: paths
7704 public NVGPathOutline getCurrPathOutline (NVGContext ctx) nothrow @trusted @nogc {
7705   if (ctx is null || !ctx.contextAlive || ctx.ncommands == 0) return NVGPathOutline.init;
7706 
7707   auto res = NVGPathOutline.createNew();
7708 
7709   const(float)[] acommands = ctx.commands[0..ctx.ncommands];
7710   int ncommands = cast(int)acommands.length;
7711   const(float)* commands = acommands.ptr;
7712 
7713   float cx = 0, cy = 0;
7714   float[2] start = void;
7715   float[4] totalBounds = [float.max, float.max, -float.max, -float.max];
7716   float[8] bcp = void; // bezier curve points; used to calculate bounds
7717 
7718   void addToBounds (in float x, in float y) nothrow @trusted @nogc {
7719     totalBounds.ptr[0] = nvg__min(totalBounds.ptr[0], x);
7720     totalBounds.ptr[1] = nvg__min(totalBounds.ptr[1], y);
7721     totalBounds.ptr[2] = nvg__max(totalBounds.ptr[2], x);
7722     totalBounds.ptr[3] = nvg__max(totalBounds.ptr[3], y);
7723   }
7724 
7725   bool hasPoints = false;
7726 
7727   void closeIt () nothrow @trusted @nogc {
7728     if (!hasPoints) return;
7729     if (cx != start.ptr[0] || cy != start.ptr[1]) {
7730       res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo);
7731       res.ds.putArgs(start[]);
7732       cx = start.ptr[0];
7733       cy = start.ptr[1];
7734       addToBounds(cx, cy);
7735     }
7736   }
7737 
7738   int i = 0;
7739   while (i < ncommands) {
7740     int cmd = cast(int)commands[i++];
7741     switch (cmd) {
7742       case Command.MoveTo: // one coordinate pair
7743         const(float)* tfxy = commands+i;
7744         i += 2;
7745         // add command
7746         res.ds.putCommand(NVGPathOutline.Command.Kind.MoveTo);
7747         res.ds.putArgs(tfxy[0..2]);
7748         // new starting point
7749         start.ptr[0..2] = tfxy[0..2];
7750         cx = tfxy[0];
7751         cy = tfxy[0];
7752         addToBounds(cx, cy);
7753         hasPoints = true;
7754         break;
7755       case Command.LineTo: // one coordinate pair
7756         const(float)* tfxy = commands+i;
7757         i += 2;
7758         // add command
7759         res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo);
7760         res.ds.putArgs(tfxy[0..2]);
7761         cx = tfxy[0];
7762         cy = tfxy[0];
7763         addToBounds(cx, cy);
7764         hasPoints = true;
7765         break;
7766       case Command.BezierTo: // three coordinate pairs
7767         const(float)* tfxy = commands+i;
7768         i += 3*2;
7769         // add command
7770         res.ds.putCommand(NVGPathOutline.Command.Kind.BezierTo);
7771         res.ds.putArgs(tfxy[0..6]);
7772         // bounds
7773         bcp.ptr[0] = cx;
7774         bcp.ptr[1] = cy;
7775         bcp.ptr[2..8] = tfxy[0..6];
7776         nvg__bezierBounds(bcp.ptr, totalBounds);
7777         cx = tfxy[4];
7778         cy = tfxy[5];
7779         hasPoints = true;
7780         break;
7781       case Command.Close:
7782         closeIt();
7783         hasPoints = false;
7784         break;
7785       case Command.Winding:
7786         //psp.winding = cast(short)cast(int)commands[i];
7787         i += 1;
7788         break;
7789       default:
7790         break;
7791     }
7792   }
7793 
7794   res.ds.bounds[] = totalBounds[];
7795   return res;
7796 }
7797 
7798 
7799 // ////////////////////////////////////////////////////////////////////////// //
7800 // Text
7801 
7802 /** Creates font by loading it from the disk from specified file name.
7803  * Returns handle to the font or FONS_INVALID (aka -1) on error.
7804  * Use "fontname:noaa" as [name] to turn off antialiasing (if font driver supports that).
7805  *
7806  * On POSIX systems it is possible to use fontconfig font names too.
7807  * `:noaa` in font path is still allowed, but it must be the last option.
7808  *
7809  * Group: text_api
7810  */
7811 public int createFont (NVGContext ctx, const(char)[] name, const(char)[] path) nothrow @trusted {
7812   return ctx.fs.addFont(name, path, ctx.params.fontAA);
7813 }
7814 
7815 /** Creates font by loading it from the specified memory chunk.
7816  * Returns handle to the font or FONS_INVALID (aka -1) on error.
7817  * Won't free data on error.
7818  *
7819  * Group: text_api
7820  */
7821 public int createFontMem (NVGContext ctx, const(char)[] name, ubyte* data, int ndata, bool freeData) nothrow @trusted @nogc {
7822   return ctx.fs.addFontMem(name, data, ndata, freeData, ctx.params.fontAA);
7823 }
7824 
7825 /// Add fonts from another context.
7826 /// This is more effective than reloading fonts, 'cause font data will be shared.
7827 /// Group: text_api
7828 public void addFontsFrom (NVGContext ctx, NVGContext source) nothrow @trusted @nogc {
7829   if (ctx is null || source is null) return;
7830   ctx.fs.addFontsFrom(source.fs);
7831 }
7832 
7833 /// Finds a loaded font of specified name, and returns handle to it, or FONS_INVALID (aka -1) if the font is not found.
7834 /// Group: text_api
7835 public int findFont (NVGContext ctx, const(char)[] name) nothrow @trusted @nogc {
7836   pragma(inline, true);
7837   return (name.length == 0 ? FONS_INVALID : ctx.fs.getFontByName(name));
7838 }
7839 
7840 /// Sets the font size of current text style.
7841 /// Group: text_api
7842 public void fontSize (NVGContext ctx, float size) nothrow @trusted @nogc {
7843   pragma(inline, true);
7844   nvg__getState(ctx).fontSize = size;
7845 }
7846 
7847 /// Gets the font size of current text style.
7848 /// Group: text_api
7849 public float fontSize (NVGContext ctx) nothrow @trusted @nogc {
7850   pragma(inline, true);
7851   return nvg__getState(ctx).fontSize;
7852 }
7853 
7854 /// Sets the blur of current text style.
7855 /// Group: text_api
7856 public void fontBlur (NVGContext ctx, float blur) nothrow @trusted @nogc {
7857   pragma(inline, true);
7858   nvg__getState(ctx).fontBlur = blur;
7859 }
7860 
7861 /// Gets the blur of current text style.
7862 /// Group: text_api
7863 public float fontBlur (NVGContext ctx) nothrow @trusted @nogc {
7864   pragma(inline, true);
7865   return nvg__getState(ctx).fontBlur;
7866 }
7867 
7868 /// Sets the letter spacing of current text style.
7869 /// Group: text_api
7870 public void textLetterSpacing (NVGContext ctx, float spacing) nothrow @trusted @nogc {
7871   pragma(inline, true);
7872   nvg__getState(ctx).letterSpacing = spacing;
7873 }
7874 
7875 /// Gets the letter spacing of current text style.
7876 /// Group: text_api
7877 public float textLetterSpacing (NVGContext ctx) nothrow @trusted @nogc {
7878   pragma(inline, true);
7879   return nvg__getState(ctx).letterSpacing;
7880 }
7881 
7882 /// Sets the proportional line height of current text style. The line height is specified as multiple of font size.
7883 /// Group: text_api
7884 public void textLineHeight (NVGContext ctx, float lineHeight) nothrow @trusted @nogc {
7885   pragma(inline, true);
7886   nvg__getState(ctx).lineHeight = lineHeight;
7887 }
7888 
7889 /// Gets the proportional line height of current text style. The line height is specified as multiple of font size.
7890 /// Group: text_api
7891 public float textLineHeight (NVGContext ctx) nothrow @trusted @nogc {
7892   pragma(inline, true);
7893   return nvg__getState(ctx).lineHeight;
7894 }
7895 
7896 /// Sets the text align of current text style, see [NVGTextAlign] for options.
7897 /// Group: text_api
7898 public void textAlign (NVGContext ctx, NVGTextAlign talign) nothrow @trusted @nogc {
7899   pragma(inline, true);
7900   nvg__getState(ctx).textAlign = talign;
7901 }
7902 
7903 /// Ditto.
7904 public void textAlign (NVGContext ctx, NVGTextAlign.H h) nothrow @trusted @nogc {
7905   pragma(inline, true);
7906   nvg__getState(ctx).textAlign.horizontal = h;
7907 }
7908 
7909 /// Ditto.
7910 public void textAlign (NVGContext ctx, NVGTextAlign.V v) nothrow @trusted @nogc {
7911   pragma(inline, true);
7912   nvg__getState(ctx).textAlign.vertical = v;
7913 }
7914 
7915 /// Ditto.
7916 public void textAlign (NVGContext ctx, NVGTextAlign.H h, NVGTextAlign.V v) nothrow @trusted @nogc {
7917   pragma(inline, true);
7918   nvg__getState(ctx).textAlign.reset(h, v);
7919 }
7920 
7921 /// Ditto.
7922 public void textAlign (NVGContext ctx, NVGTextAlign.V v, NVGTextAlign.H h) nothrow @trusted @nogc {
7923   pragma(inline, true);
7924   nvg__getState(ctx).textAlign.reset(h, v);
7925 }
7926 
7927 /// Gets the text align of current text style, see [NVGTextAlign] for options.
7928 /// Group: text_api
7929 public NVGTextAlign textAlign (NVGContext ctx) nothrow @trusted @nogc {
7930   pragma(inline, true);
7931   return nvg__getState(ctx).textAlign;
7932 }
7933 
7934 /// Sets the font face based on specified id of current text style.
7935 /// Group: text_api
7936 public void fontFaceId (NVGContext ctx, int font) nothrow @trusted @nogc {
7937   pragma(inline, true);
7938   nvg__getState(ctx).fontId = font;
7939 }
7940 
7941 /// Gets the font face based on specified id of current text style.
7942 /// Group: text_api
7943 public int fontFaceId (NVGContext ctx) nothrow @trusted @nogc {
7944   pragma(inline, true);
7945   return nvg__getState(ctx).fontId;
7946 }
7947 
7948 /** Sets the font face based on specified name of current text style.
7949  *
7950  * The underlying implementation is using O(1) data structure to lookup
7951  * font names, so you probably should use this function instead of [fontFaceId]
7952  * to make your code more robust and less error-prone.
7953  *
7954  * Group: text_api
7955  */
7956 public void fontFace (NVGContext ctx, const(char)[] font) nothrow @trusted @nogc {
7957   pragma(inline, true);
7958   nvg__getState(ctx).fontId = ctx.fs.getFontByName(font);
7959 }
7960 
7961 static if (is(typeof(&fons__nvg__toPath))) {
7962   public enum NanoVegaHasCharToPath = true; ///
7963 } else {
7964   public enum NanoVegaHasCharToPath = false; ///
7965 }
7966 
7967 /// Adds glyph outlines to the current path. Vertical 0 is baseline.
7968 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead.
7969 /// Returns `false` if there is no such glyph, or current font is not scalable.
7970 /// Group: text_api
7971 public bool charToPath (NVGContext ctx, dchar dch, float[] bounds=null) nothrow @trusted @nogc {
7972   NVGstate* state = nvg__getState(ctx);
7973   ctx.fs.fontId = state.fontId;
7974   return ctx.fs.toPath(ctx, dch, bounds);
7975 }
7976 
7977 static if (is(typeof(&fons__nvg__bounds))) {
7978   public enum NanoVegaHasCharPathBounds = true; ///
7979 } else {
7980   public enum NanoVegaHasCharPathBounds = false; ///
7981 }
7982 
7983 /// Returns bounds of the glyph outlines. Vertical 0 is baseline.
7984 /// The glyph is not scaled in any way.
7985 /// Returns `false` if there is no such glyph, or current font is not scalable.
7986 /// Group: text_api
7987 public bool charPathBounds (NVGContext ctx, dchar dch, float[] bounds) nothrow @trusted @nogc {
7988   NVGstate* state = nvg__getState(ctx);
7989   ctx.fs.fontId = state.fontId;
7990   return ctx.fs.getPathBounds(dch, bounds);
7991 }
7992 
7993 /** [charOutline] will return [NVGPathOutline].
7994 
7995  some usage samples:
7996 
7997  ---
7998     float[4] bounds = void;
7999 
8000     nvg.scale(0.5, 0.5);
8001     nvg.translate(500, 800);
8002     nvg.evenOddFill;
8003 
8004     nvg.newPath();
8005     nvg.charToPath('&', bounds[]);
8006     conwriteln(bounds[]);
8007     nvg.fillPaint(nvg.linearGradient(0, 0, 600, 600, NVGColor("#f70"), NVGColor("#ff0")));
8008     nvg.strokeColor(NVGColor("#0f0"));
8009     nvg.strokeWidth = 3;
8010     nvg.fill();
8011     nvg.stroke();
8012     // glyph bounds
8013     nvg.newPath();
8014     nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
8015     nvg.strokeColor(NVGColor("#00f"));
8016     nvg.stroke();
8017 
8018     nvg.newPath();
8019     nvg.charToPath('g', bounds[]);
8020     conwriteln(bounds[]);
8021     nvg.fill();
8022     nvg.strokeColor(NVGColor("#0f0"));
8023     nvg.stroke();
8024     // glyph bounds
8025     nvg.newPath();
8026     nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
8027     nvg.strokeColor(NVGColor("#00f"));
8028     nvg.stroke();
8029 
8030     nvg.newPath();
8031     nvg.moveTo(0, 0);
8032     nvg.lineTo(600, 0);
8033     nvg.strokeColor(NVGColor("#0ff"));
8034     nvg.stroke();
8035 
8036     if (auto ol = nvg.charOutline('Q')) {
8037       scope(exit) ol.kill();
8038       nvg.newPath();
8039       conwriteln("==== length: ", ol.length, " ====");
8040       foreach (const ref cmd; ol.commands) {
8041         //conwriteln("  ", cmd.code, ": ", cmd.args[]);
8042         assert(cmd.valid);
8043         final switch (cmd.code) {
8044           case cmd.Kind.MoveTo: nvg.moveTo(cmd.args[0], cmd.args[1]); break;
8045           case cmd.Kind.LineTo: nvg.lineTo(cmd.args[0], cmd.args[1]); break;
8046           case cmd.Kind.QuadTo: nvg.quadTo(cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]); break;
8047           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;
8048         }
8049       }
8050       nvg.strokeColor(NVGColor("#f00"));
8051       nvg.stroke();
8052     }
8053  ---
8054 
8055  Group: text_api
8056  */
8057 public struct NVGPathOutline {
8058 private nothrow @trusted @nogc:
8059   struct DataStore {
8060     uint rc; // refcount
8061     ubyte* data;
8062     uint used;
8063     uint size;
8064     uint ccount; // number of commands
8065     float[4] bounds = 0; /// outline bounds
8066   nothrow @trusted @nogc:
8067     void putBytes (const(void)[] b) {
8068       if (b.length == 0) return;
8069       if (b.length >= int.max/8) assert(0, "NanoVega: out of memory");
8070       if (int.max/8-used < b.length) assert(0, "NanoVega: out of memory");
8071       if (used+cast(uint)b.length > size) {
8072         import core.stdc.stdlib : realloc;
8073         uint newsz = size;
8074         while (newsz < used+cast(uint)b.length) newsz = (newsz == 0 ? 1024 : newsz < 32768 ? newsz*2 : newsz+8192);
8075         assert(used+cast(uint)b.length <= newsz);
8076         data = cast(ubyte*)realloc(data, newsz);
8077         if (data is null) assert(0, "NanoVega: out of memory");
8078         size = newsz;
8079       }
8080       import core.stdc.string : memcpy;
8081       memcpy(data+used, b.ptr, b.length);
8082       used += cast(uint)b.length;
8083     }
8084     void putCommand (ubyte cmd) { pragma(inline, true); ++ccount; putBytes((&cmd)[0..1]); }
8085     void putArgs (const(float)[] f...) { pragma(inline, true); putBytes(f[]); }
8086   }
8087 
8088   static void incRef (DataStore* ds) {
8089     pragma(inline, true);
8090     if (ds !is null) {
8091       ++ds.rc;
8092       //{ import core.stdc.stdio; printf("ods(%p): incref: newrc=%u\n", ds, ds.rc); }
8093     }
8094   }
8095 
8096   static void decRef (DataStore* ds) {
8097     version(aliced) pragma(inline, true);
8098     if (ds !is null) {
8099       //{ import core.stdc.stdio; printf("ods(%p): decref: newrc=%u\n", ds, ds.rc-1); }
8100       if (--ds.rc == 0) {
8101         import core.stdc.stdlib : free;
8102         import core.stdc.string : memset;
8103         if (ds.data !is null) free(ds.data);
8104         memset(ds, 0, DataStore.sizeof); // just in case
8105         free(ds);
8106         //{ import core.stdc.stdio; printf("  ods(%p): killed.\n"); }
8107       }
8108     }
8109   }
8110 
8111 private:
8112   static NVGPathOutline createNew () {
8113     import core.stdc.stdlib : malloc;
8114     import core.stdc.string : memset;
8115     auto ds = cast(DataStore*)malloc(DataStore.sizeof);
8116     if (ds is null) assert(0, "NanoVega: out of memory");
8117     memset(ds, 0, DataStore.sizeof);
8118     ds.rc = 1;
8119     NVGPathOutline res;
8120     res.dsaddr = cast(usize)ds;
8121     return res;
8122   }
8123 
8124 private:
8125   usize dsaddr; // fool GC
8126 
8127   @property inout(DataStore)* ds () inout pure { pragma(inline, true); return cast(DataStore*)dsaddr; }
8128 
8129 public:
8130   /// commands
8131   static struct Command {
8132     ///
8133     enum Kind : ubyte {
8134       MoveTo, ///
8135       LineTo, ///
8136       QuadTo, ///
8137       BezierTo, ///
8138       End, /// no more commands (this command is not `valid`!)
8139 
8140     }
8141     Kind code; ///
8142     const(float)[] args; ///
8143     @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (code >= Kind.min && code < Kind.End && args.length >= 2); } ///
8144 
8145     static uint arglen (Kind code) pure nothrow @safe @nogc {
8146       pragma(inline, true);
8147       return
8148         code == Kind.MoveTo || code == Kind.LineTo ? 2 :
8149         code == Kind.QuadTo ? 4 :
8150         code == Kind.BezierTo ? 6 :
8151         0;
8152     }
8153 
8154     /// perform NanoVega command with stored data.
8155     void perform (NVGContext ctx) const nothrow @trusted @nogc {
8156       if (ctx is null) return;
8157       final switch (code) {
8158         case Kind.MoveTo: if (args.length > 1) ctx.moveTo(args.ptr[0..2]); break;
8159         case Kind.LineTo: if (args.length > 1) ctx.lineTo(args.ptr[0..2]); break;
8160         case Kind.QuadTo: if (args.length > 3) ctx.quadTo(args.ptr[0..4]); break;
8161         case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(args.ptr[0..6]); break;
8162         case Kind.End: break;
8163       }
8164     }
8165 
8166     /// perform NanoVega command with stored data, transforming points with [xform] transformation matrix.
8167     void perform() (NVGContext ctx, const scope auto ref NVGMatrix xform) const nothrow @trusted @nogc {
8168       if (ctx is null || !valid) return;
8169       float[6] pts = void;
8170       pts[0..args.length] = args[];
8171       foreach (immutable pidx; 0..args.length/2) xform.point(pts.ptr[pidx*2+0], pts.ptr[pidx*2+1]);
8172       final switch (code) {
8173         case Kind.MoveTo: if (args.length > 1) ctx.moveTo(pts.ptr[0..2]); break;
8174         case Kind.LineTo: if (args.length > 1) ctx.lineTo(pts.ptr[0..2]); break;
8175         case Kind.QuadTo: if (args.length > 3) ctx.quadTo(pts.ptr[0..4]); break;
8176         case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(pts.ptr[0..6]); break;
8177         case Kind.End: break;
8178       }
8179     }
8180   }
8181 
8182 public:
8183   /// Create new path with quadratic bezier (first command is MoveTo, second command is QuadTo).
8184   static NVGPathOutline createNewQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) {
8185     auto res = createNew();
8186     res.ds.putCommand(Command.Kind.MoveTo);
8187     res.ds.putArgs(x0, y0);
8188     res.ds.putCommand(Command.Kind.QuadTo);
8189     res.ds.putArgs(cx, cy, x, y);
8190     return res;
8191   }
8192 
8193   /// Create new path with cubic bezier (first command is MoveTo, second command is BezierTo).
8194   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) {
8195     auto res = createNew();
8196     res.ds.putCommand(Command.Kind.MoveTo);
8197     res.ds.putArgs(x1, y1);
8198     res.ds.putCommand(Command.Kind.BezierTo);
8199     res.ds.putArgs(x2, y2, x3, y3, x4, y4);
8200     return res;
8201   }
8202 
8203 public:
8204   this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); }
8205   ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); }
8206 
8207   void opAssign() (const scope auto ref NVGPathOutline a) {
8208     incRef(cast(DataStore*)a.dsaddr);
8209     decRef(cast(DataStore*)dsaddr);
8210     dsaddr = a.dsaddr;
8211   }
8212 
8213   /// Clear storage.
8214   void clear () {
8215     pragma(inline, true);
8216     decRef(ds);
8217     dsaddr = 0;
8218   }
8219 
8220   /// Is this outline empty?
8221   @property empty () const pure { pragma(inline, true); return (dsaddr == 0 || ds.ccount == 0); }
8222 
8223   /// Returns number of commands in outline.
8224   @property int length () const pure { pragma(inline, true); return (dsaddr ? ds.ccount : 0); }
8225 
8226   /// Returns "flattened" path. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8227   NVGPathOutline flatten () const { pragma(inline, true); return flattenInternal(null); }
8228 
8229   /// Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8230   NVGPathOutline flatten() (const scope auto ref NVGMatrix mt) const { pragma(inline, true); return flattenInternal(&mt); }
8231 
8232   // Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8233   private NVGPathOutline flattenInternal (scope NVGMatrix* tfm) const {
8234     import core.stdc.string : memset;
8235 
8236     NVGPathOutline res;
8237     if (dsaddr == 0 || ds.ccount == 0) { res = this; return res; } // nothing to do
8238 
8239     // check if we need to flatten the path
8240     if (tfm is null) {
8241       bool dowork = false;
8242       foreach (const ref cs; commands) {
8243         if (cs.code != Command.Kind.MoveTo && cs.code != Command.Kind.LineTo) {
8244           dowork = true;
8245           break;
8246         }
8247       }
8248       if (!dowork) { res = this; return res; } // nothing to do
8249     }
8250 
8251     NVGcontextinternal ctx;
8252     memset(&ctx, 0, ctx.sizeof);
8253     ctx.cache = nvg__allocPathCache();
8254     scope(exit) {
8255       import core.stdc.stdlib : free;
8256       nvg__deletePathCache(ctx.cache);
8257     }
8258 
8259     ctx.tessTol = 0.25f;
8260     ctx.angleTol = 0; // 0 -- angle tolerance for McSeem Bezier rasterizer
8261     ctx.cuspLimit = 0; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps)
8262     ctx.distTol = 0.01f;
8263     ctx.tesselatortype = NVGTesselation.DeCasteljau;
8264 
8265     nvg__addPath(&ctx); // we need this for `nvg__addPoint()`
8266 
8267     // has some curves or transformations, convert path
8268     res = createNew();
8269     float[8] args = void;
8270 
8271     res.ds.bounds = [float.max, float.max, -float.max, -float.max];
8272 
8273     float lastX = float.max, lastY = float.max;
8274     bool lastWasMove = false;
8275 
8276     void addPoint (float x, float y, Command.Kind cmd=Command.Kind.LineTo) nothrow @trusted @nogc {
8277       if (tfm !is null) tfm.point(x, y);
8278       bool isMove = (cmd == Command.Kind.MoveTo);
8279       if (isMove) {
8280         // moveto
8281         if (lastWasMove && nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return;
8282       } else {
8283         // lineto
8284         if (nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return;
8285       }
8286       lastWasMove = isMove;
8287       lastX = x;
8288       lastY = y;
8289       res.ds.putCommand(cmd);
8290       res.ds.putArgs(x, y);
8291       res.ds.bounds.ptr[0] = nvg__min(res.ds.bounds.ptr[0], x);
8292       res.ds.bounds.ptr[1] = nvg__min(res.ds.bounds.ptr[1], y);
8293       res.ds.bounds.ptr[2] = nvg__max(res.ds.bounds.ptr[2], x);
8294       res.ds.bounds.ptr[3] = nvg__max(res.ds.bounds.ptr[3], y);
8295     }
8296 
8297     // sorry for this pasta
8298     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 {
8299       ctx.cache.npoints = 0;
8300       if (ctx.tesselatortype == NVGTesselation.DeCasteljau) {
8301         nvg__tesselateBezier(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner);
8302       } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) {
8303         nvg__tesselateBezierMcSeem(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner);
8304       } else {
8305         nvg__tesselateBezierAFD(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, PointFlag.Corner);
8306       }
8307       // add generated points
8308       foreach (const ref pt; ctx.cache.points[0..ctx.cache.npoints]) addPoint(pt.x, pt.y);
8309     }
8310 
8311     void flattenQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) {
8312       flattenBezier(
8313         x0, y0,
8314         x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
8315         x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
8316         x, y,
8317         0,
8318       );
8319     }
8320 
8321     float cx = 0, cy = 0;
8322     foreach (const ref cs; commands) {
8323       switch (cs.code) {
8324         case Command.Kind.LineTo:
8325         case Command.Kind.MoveTo:
8326           addPoint(cs.args[0], cs.args[1], cs.code);
8327           cx = cs.args[0];
8328           cy = cs.args[1];
8329           break;
8330         case Command.Kind.QuadTo:
8331           flattenQuad(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3]);
8332           cx = cs.args[2];
8333           cy = cs.args[3];
8334           break;
8335         case Command.Kind.BezierTo:
8336           flattenBezier(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3], cs.args[4], cs.args[5], 0);
8337           cx = cs.args[4];
8338           cy = cs.args[5];
8339           break;
8340         default:
8341           break;
8342       }
8343     }
8344 
8345     return res;
8346   }
8347 
8348   /// Returns forward range with all glyph commands.
8349   auto commands () const nothrow @trusted @nogc {
8350     static struct Range {
8351     private nothrow @trusted @nogc:
8352       usize dsaddr;
8353       uint cpos; // current position in data
8354       uint cleft; // number of commands left
8355       @property const(ubyte)* data () inout pure { pragma(inline, true); return (dsaddr ? (cast(DataStore*)dsaddr).data : null); }
8356     public:
8357       this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); }
8358       ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); }
8359       void opAssign() (const scope auto ref Range a) {
8360         incRef(cast(DataStore*)a.dsaddr);
8361         decRef(cast(DataStore*)dsaddr);
8362         dsaddr = a.dsaddr;
8363         cpos = a.cpos;
8364         cleft = a.cleft;
8365       }
8366       float[4] bounds () const pure { float[4] res = 0; pragma(inline, true); if (dsaddr) res[] = (cast(DataStore*)dsaddr).bounds[]; return res; } /// outline bounds
8367       @property bool empty () const pure { pragma(inline, true); return (cleft == 0); }
8368       @property int length () const pure { pragma(inline, true); return cleft; }
8369       @property Range save () const { pragma(inline, true); Range res = this; return res; }
8370       @property Command front () const {
8371         Command res = void;
8372         if (cleft > 0) {
8373           res.code = cast(Command.Kind)data[cpos];
8374           switch (res.code) {
8375             case Command.Kind.MoveTo:
8376             case Command.Kind.LineTo:
8377               res.args = (cast(const(float*))(data+cpos+1))[0..1*2];
8378               break;
8379             case Command.Kind.QuadTo:
8380               res.args = (cast(const(float*))(data+cpos+1))[0..2*2];
8381               break;
8382             case Command.Kind.BezierTo:
8383               res.args = (cast(const(float*))(data+cpos+1))[0..3*2];
8384               break;
8385             default:
8386               res.code = Command.Kind.End;
8387               res.args = null;
8388               break;
8389           }
8390         } else {
8391           res.code = Command.Kind.End;
8392           res.args = null;
8393         }
8394         return res;
8395       }
8396       void popFront () {
8397         if (cleft <= 1) { cleft = 0; return; } // don't waste time skipping last command
8398         --cleft;
8399         switch (data[cpos]) {
8400           case Command.Kind.MoveTo:
8401           case Command.Kind.LineTo:
8402             cpos += 1+1*2*cast(uint)float.sizeof;
8403             break;
8404           case Command.Kind.QuadTo:
8405             cpos += 1+2*2*cast(uint)float.sizeof;
8406             break;
8407           case Command.Kind.BezierTo:
8408             cpos += 1+3*2*cast(uint)float.sizeof;
8409             break;
8410           default:
8411             cleft = 0;
8412             break;
8413         }
8414       }
8415     }
8416     if (dsaddr) {
8417       incRef(cast(DataStore*)dsaddr); // range anchors it
8418       return Range(dsaddr, 0, ds.ccount);
8419     } else {
8420       return Range.init;
8421     }
8422   }
8423 }
8424 
8425 public alias NVGGlyphOutline = NVGPathOutline; /// For backwards compatibility.
8426 
8427 /// Destroy glyph outiline and free allocated memory.
8428 /// Group: text_api
8429 public void kill (ref NVGPathOutline ol) nothrow @trusted @nogc {
8430   pragma(inline, true);
8431   ol.clear();
8432 }
8433 
8434 static if (is(typeof(&fons__nvg__toOutline))) {
8435   public enum NanoVegaHasCharOutline = true; ///
8436 } else {
8437   public enum NanoVegaHasCharOutline = false; ///
8438 }
8439 
8440 /// Returns glyph outlines as array of commands. Vertical 0 is baseline.
8441 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead.
8442 /// Returns `null` if there is no such glyph, or current font is not scalable.
8443 /// Group: text_api
8444 public NVGPathOutline charOutline (NVGContext ctx, dchar dch) nothrow @trusted @nogc {
8445   import core.stdc.stdlib : malloc;
8446   import core.stdc.string : memcpy;
8447   NVGstate* state = nvg__getState(ctx);
8448   ctx.fs.fontId = state.fontId;
8449   auto oline = NVGPathOutline.createNew();
8450   if (!ctx.fs.toOutline(dch, oline.ds)) oline.clear();
8451   return oline;
8452 }
8453 
8454 
8455 float nvg__quantize (float a, float d) pure nothrow @safe @nogc {
8456   pragma(inline, true);
8457   return (cast(int)(a/d+0.5f))*d;
8458 }
8459 
8460 float nvg__getFontScale (NVGstate* state) /*pure*/ nothrow @safe @nogc {
8461   pragma(inline, true);
8462   return nvg__min(nvg__quantize(nvg__getAverageScale(state.xform), 0.01f), 4.0f);
8463 }
8464 
8465 void nvg__flushTextTexture (NVGContext ctx) nothrow @trusted @nogc {
8466   int[4] dirty = void;
8467   if (ctx.fs.validateTexture(dirty.ptr)) {
8468     auto fontImage = &ctx.fontImages[ctx.fontImageIdx];
8469     // Update texture
8470     if (fontImage.valid) {
8471       int iw, ih;
8472       const(ubyte)* data = ctx.fs.getTextureData(&iw, &ih);
8473       int x = dirty[0];
8474       int y = dirty[1];
8475       int w = dirty[2]-dirty[0];
8476       int h = dirty[3]-dirty[1];
8477       ctx.params.renderUpdateTexture(ctx.params.userPtr, fontImage.id, x, y, w, h, data);
8478     }
8479   }
8480 }
8481 
8482 bool nvg__allocTextAtlas (NVGContext ctx) nothrow @trusted @nogc {
8483   int iw, ih;
8484   nvg__flushTextTexture(ctx);
8485   if (ctx.fontImageIdx >= NVG_MAX_FONTIMAGES-1) return false;
8486   // if next fontImage already have a texture
8487   if (ctx.fontImages[ctx.fontImageIdx+1].valid) {
8488     ctx.imageSize(ctx.fontImages[ctx.fontImageIdx+1], iw, ih);
8489   } else {
8490     // calculate the new font image size and create it
8491     ctx.imageSize(ctx.fontImages[ctx.fontImageIdx], iw, ih);
8492     if (iw > ih) ih *= 2; else iw *= 2;
8493     if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE;
8494     ctx.fontImages[ctx.fontImageIdx+1].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, iw, ih, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null);
8495     if (ctx.fontImages[ctx.fontImageIdx+1].id > 0) {
8496       ctx.fontImages[ctx.fontImageIdx+1].ctx = ctx;
8497       ctx.nvg__imageIncRef(ctx.fontImages[ctx.fontImageIdx+1].id, false); // don't increment driver refcount
8498     }
8499   }
8500   ++ctx.fontImageIdx;
8501   ctx.fs.resetAtlas(iw, ih);
8502   return true;
8503 }
8504 
8505 void nvg__renderText (NVGContext ctx, NVGVertex* verts, int nverts) nothrow @trusted @nogc {
8506   NVGstate* state = nvg__getState(ctx);
8507   NVGPaint paint = state.fill;
8508 
8509   // Render triangles.
8510   paint.image = ctx.fontImages[ctx.fontImageIdx];
8511 
8512   // Apply global alpha
8513   paint.innerColor.a *= state.alpha;
8514   paint.middleColor.a *= state.alpha;
8515   paint.outerColor.a *= state.alpha;
8516 
8517   ctx.params.renderTriangles(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &paint, &state.scissor, verts, nverts);
8518 
8519   ++ctx.drawCallCount;
8520   ctx.textTriCount += nverts/3;
8521 }
8522 
8523 /// Draws text string at specified location. Returns next x position.
8524 /// Group: text_api
8525 public float text(T) (NVGContext ctx, float x, float y, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8526   NVGstate* state = nvg__getState(ctx);
8527   FONSTextIter!T iter, prevIter;
8528   FONSQuad q;
8529   NVGVertex* verts;
8530   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8531   float invscale = 1.0f/scale;
8532   int cverts = 0;
8533   int nverts = 0;
8534 
8535   if (state.fontId == FONS_INVALID) return x;
8536   if (str.length == 0) return x;
8537 
8538   ctx.fs.size = state.fontSize*scale;
8539   ctx.fs.spacing = state.letterSpacing*scale;
8540   ctx.fs.blur = state.fontBlur*scale;
8541   ctx.fs.textAlign = state.textAlign;
8542   ctx.fs.fontId = state.fontId;
8543 
8544   cverts = nvg__max(2, cast(int)(str.length))*6; // conservative estimate
8545   verts = nvg__allocTempVerts(ctx, cverts);
8546   if (verts is null) return x;
8547 
8548   if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Required)) return x;
8549   prevIter = iter;
8550   while (iter.next(q)) {
8551     float[4*2] c = void;
8552     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8553       if (nverts != 0) {
8554         // TODO: add back-end bit to do this just once per frame
8555         nvg__flushTextTexture(ctx);
8556         nvg__renderText(ctx, verts, nverts);
8557         nverts = 0;
8558       }
8559       if (!nvg__allocTextAtlas(ctx)) break; // no memory :(
8560       iter = prevIter;
8561       iter.next(q); // try again
8562       if (iter.prevGlyphIndex < 0) {
8563         // still can not find glyph, try replacement
8564         iter = prevIter;
8565         if (!iter.getDummyChar(q)) break;
8566       }
8567     }
8568     prevIter = iter;
8569     // transform corners
8570     state.xform.point(&c[0], &c[1], q.x0*invscale, q.y0*invscale);
8571     state.xform.point(&c[2], &c[3], q.x1*invscale, q.y0*invscale);
8572     state.xform.point(&c[4], &c[5], q.x1*invscale, q.y1*invscale);
8573     state.xform.point(&c[6], &c[7], q.x0*invscale, q.y1*invscale);
8574     // create triangles
8575     if (nverts+6 <= cverts) {
8576       nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts;
8577       nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts;
8578       nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); ++nverts;
8579       nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts;
8580       nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); ++nverts;
8581       nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts;
8582     }
8583   }
8584 
8585   // TODO: add back-end bit to do this just once per frame
8586   if (nverts > 0) {
8587     nvg__flushTextTexture(ctx);
8588     nvg__renderText(ctx, verts, nverts);
8589   }
8590 
8591   return iter.nextx/scale;
8592 }
8593 
8594 /** Draws multi-line text string at specified location wrapped at the specified width.
8595  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8596  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8597  *
8598  * Group: text_api
8599  */
8600 public void textBox(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8601   NVGstate* state = nvg__getState(ctx);
8602   if (state.fontId == FONS_INVALID) return;
8603 
8604   NVGTextRow!T[2] rows;
8605   auto oldAlign = state.textAlign;
8606   scope(exit) state.textAlign = oldAlign;
8607   auto halign = state.textAlign.horizontal;
8608   float lineh = 0;
8609 
8610   ctx.textMetrics(null, null, &lineh);
8611   state.textAlign.horizontal = NVGTextAlign.H.Left;
8612   for (;;) {
8613     auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]);
8614     //{ import core.stdc.stdio : printf; printf("slen=%u; rlen=%u; bw=%f\n", cast(uint)str.length, cast(uint)rres.length, cast(double)breakRowWidth); }
8615     if (rres.length == 0) break;
8616     foreach (ref row; rres) {
8617       final switch (halign) {
8618         case NVGTextAlign.H.Left: ctx.text(x, y, row.row); break;
8619         case NVGTextAlign.H.Center: ctx.text(x+breakRowWidth*0.5f-row.width*0.5f, y, row.row); break;
8620         case NVGTextAlign.H.Right: ctx.text(x+breakRowWidth-row.width, y, row.row); break;
8621       }
8622       y += lineh*state.lineHeight;
8623     }
8624     str = rres[$-1].rest;
8625   }
8626 }
8627 
8628 private template isGoodPositionDelegate(DG) {
8629   private DG dg;
8630   static if (is(typeof({ NVGGlyphPosition pos; bool res = dg(pos); })) ||
8631              is(typeof({ NVGGlyphPosition pos; dg(pos); })))
8632     enum isGoodPositionDelegate = true;
8633   else
8634     enum isGoodPositionDelegate = false;
8635 }
8636 
8637 /** Calculates the glyph x positions of the specified text.
8638  * Measured values are returned in local coordinate space.
8639  *
8640  * Group: text_api
8641  */
8642 public NVGGlyphPosition[] textGlyphPositions(T) (NVGContext ctx, float x, float y, const(T)[] str, NVGGlyphPosition[] positions) nothrow @trusted @nogc
8643 if (isAnyCharType!T)
8644 {
8645   if (str.length == 0 || positions.length == 0) return positions[0..0];
8646   usize posnum;
8647   auto len = ctx.textGlyphPositions(x, y, str, (const scope ref NVGGlyphPosition pos) {
8648     positions.ptr[posnum++] = pos;
8649     return (posnum < positions.length);
8650   });
8651   return positions[0..len];
8652 }
8653 
8654 /// Ditto.
8655 public int textGlyphPositions(T, DG) (NVGContext ctx, float x, float y, const(T)[] str, scope DG dg)
8656 if (isAnyCharType!T && isGoodPositionDelegate!DG)
8657 {
8658   import std.traits : ReturnType;
8659   static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true;
8660 
8661   NVGstate* state = nvg__getState(ctx);
8662   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8663   float invscale = 1.0f/scale;
8664   FONSTextIter!T iter, prevIter;
8665   FONSQuad q;
8666   int npos = 0;
8667 
8668   if (str.length == 0) return 0;
8669 
8670   ctx.fs.size = state.fontSize*scale;
8671   ctx.fs.spacing = state.letterSpacing*scale;
8672   ctx.fs.blur = state.fontBlur*scale;
8673   ctx.fs.textAlign = state.textAlign;
8674   ctx.fs.fontId = state.fontId;
8675 
8676   if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Optional)) return npos;
8677   prevIter = iter;
8678   while (iter.next(q)) {
8679     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8680       if (!nvg__allocTextAtlas(ctx)) break; // no memory
8681       iter = prevIter;
8682       iter.next(q); // try again
8683       if (iter.prevGlyphIndex < 0) {
8684         // still can not find glyph, try replacement
8685         iter = prevIter;
8686         if (!iter.getDummyChar(q)) break;
8687       }
8688     }
8689     prevIter = iter;
8690     NVGGlyphPosition position = void; //WARNING!
8691     position.strpos = cast(usize)(iter.stringp-str.ptr);
8692     position.x = iter.x*invscale;
8693     position.minx = nvg__min(iter.x, q.x0)*invscale;
8694     position.maxx = nvg__max(iter.nextx, q.x1)*invscale;
8695     ++npos;
8696     static if (RetBool) { if (!dg(position)) return npos; } else dg(position);
8697   }
8698 
8699   return npos;
8700 }
8701 
8702 private template isGoodRowDelegate(CT, DG) {
8703   private DG dg;
8704   static if (is(typeof({ NVGTextRow!CT row; bool res = dg(row); })) ||
8705              is(typeof({ NVGTextRow!CT row; dg(row); })))
8706     enum isGoodRowDelegate = true;
8707   else
8708     enum isGoodRowDelegate = false;
8709 }
8710 
8711 /** Breaks the specified text into lines.
8712  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8713  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8714  *
8715  * Group: text_api
8716  */
8717 public NVGTextRow!T[] textBreakLines(T) (NVGContext ctx, const(T)[] str, float breakRowWidth, NVGTextRow!T[] rows) nothrow @trusted @nogc
8718 if (isAnyCharType!T)
8719 {
8720   if (rows.length == 0) return rows;
8721   if (rows.length > int.max-1) rows = rows[0..int.max-1];
8722   int nrow = 0;
8723   auto count = ctx.textBreakLines(str, breakRowWidth, (const scope ref NVGTextRow!T row) {
8724     rows[nrow++] = row;
8725     return (nrow < rows.length);
8726   });
8727   return rows[0..count];
8728 }
8729 
8730 /** Breaks the specified text into lines.
8731  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8732  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8733  * Returns number of rows.
8734  *
8735  * Group: text_api
8736  */
8737 public int textBreakLines(T, DG) (NVGContext ctx, const(T)[] str, float breakRowWidth, scope DG dg)
8738 if (isAnyCharType!T && isGoodRowDelegate!(T, DG))
8739 {
8740   import std.traits : ReturnType;
8741   static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true;
8742 
8743   enum NVGcodepointType : int {
8744     Space,
8745     NewLine,
8746     Char,
8747   }
8748 
8749   NVGstate* state = nvg__getState(ctx);
8750   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8751   float invscale = 1.0f/scale;
8752   FONSTextIter!T iter, prevIter;
8753   FONSQuad q;
8754   int nrows = 0;
8755   float rowStartX = 0;
8756   float rowWidth = 0;
8757   float rowMinX = 0;
8758   float rowMaxX = 0;
8759   int rowStart = 0;
8760   int rowEnd = 0;
8761   int wordStart = 0;
8762   float wordStartX = 0;
8763   float wordMinX = 0;
8764   int breakEnd = 0;
8765   float breakWidth = 0;
8766   float breakMaxX = 0;
8767   int type = NVGcodepointType.Space, ptype = NVGcodepointType.Space;
8768   uint pcodepoint = 0;
8769 
8770   if (state.fontId == FONS_INVALID) return 0;
8771   if (str.length == 0 || dg is null) return 0;
8772 
8773   ctx.fs.size = state.fontSize*scale;
8774   ctx.fs.spacing = state.letterSpacing*scale;
8775   ctx.fs.blur = state.fontBlur*scale;
8776   ctx.fs.textAlign = state.textAlign;
8777   ctx.fs.fontId = state.fontId;
8778 
8779   breakRowWidth *= scale;
8780 
8781   enum Phase {
8782     Normal, // searching for breaking point
8783     SkipBlanks, // skip leading blanks
8784   }
8785   Phase phase = Phase.SkipBlanks; // don't skip blanks on first line
8786 
8787   if (!iter.setup(ctx.fs, 0, 0, str, FONSBitmapFlag.Optional)) return 0;
8788   prevIter = iter;
8789   while (iter.next(q)) {
8790     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8791       if (!nvg__allocTextAtlas(ctx)) break; // no memory
8792       iter = prevIter;
8793       iter.next(q); // try again
8794       if (iter.prevGlyphIndex < 0) {
8795         // still can not find glyph, try replacement
8796         iter = prevIter;
8797         if (!iter.getDummyChar(q)) break;
8798       }
8799     }
8800     prevIter = iter;
8801     switch (iter.codepoint) {
8802       case 9: // \t
8803       case 11: // \v
8804       case 12: // \f
8805       case 32: // space
8806       case 0x00a0: // NBSP
8807         type = NVGcodepointType.Space;
8808         break;
8809       case 10: // \n
8810         type = (pcodepoint == 13 ? NVGcodepointType.Space : NVGcodepointType.NewLine);
8811         break;
8812       case 13: // \r
8813         type = (pcodepoint == 10 ? NVGcodepointType.Space : NVGcodepointType.NewLine);
8814         break;
8815       case 0x0085: // NEL
8816       case 0x2028: // Line Separator
8817       case 0x2029: // Paragraph Separator
8818         type = NVGcodepointType.NewLine;
8819         break;
8820       default:
8821         type = NVGcodepointType.Char;
8822         break;
8823     }
8824     if (phase == Phase.SkipBlanks) {
8825       // fix row start
8826       rowStart = cast(int)(iter.stringp-str.ptr);
8827       rowEnd = rowStart;
8828       rowStartX = iter.x;
8829       rowWidth = iter.nextx-rowStartX; // q.x1-rowStartX;
8830       rowMinX = q.x0-rowStartX;
8831       rowMaxX = q.x1-rowStartX;
8832       wordStart = rowStart;
8833       wordStartX = iter.x;
8834       wordMinX = q.x0-rowStartX;
8835       breakEnd = rowStart;
8836       breakWidth = 0.0;
8837       breakMaxX = 0.0;
8838       if (type == NVGcodepointType.Space) continue;
8839       phase = Phase.Normal;
8840     }
8841 
8842     if (type == NVGcodepointType.NewLine) {
8843       // always handle new lines
8844       NVGTextRow!T row;
8845       row.string = str;
8846       row.start = rowStart;
8847       row.end = rowEnd;
8848       row.width = rowWidth*invscale;
8849       row.minx = rowMinX*invscale;
8850       row.maxx = rowMaxX*invscale;
8851       ++nrows;
8852       static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8853       phase = Phase.SkipBlanks;
8854     } else {
8855       float nextWidth = iter.nextx-rowStartX;
8856       // track last non-white space character
8857       if (type == NVGcodepointType.Char) {
8858         rowEnd = cast(int)(iter.nextp-str.ptr);
8859         rowWidth = iter.nextx-rowStartX;
8860         rowMaxX = q.x1-rowStartX;
8861       }
8862       // track last end of a word
8863       if (ptype == NVGcodepointType.Char && type == NVGcodepointType.Space) {
8864         breakEnd = cast(int)(iter.stringp-str.ptr);
8865         breakWidth = rowWidth;
8866         breakMaxX = rowMaxX;
8867       }
8868       // track last beginning of a word
8869       if (ptype == NVGcodepointType.Space && type == NVGcodepointType.Char) {
8870         wordStart = cast(int)(iter.stringp-str.ptr);
8871         wordStartX = iter.x;
8872         wordMinX = q.x0-rowStartX;
8873       }
8874       // break to new line when a character is beyond break width
8875       if (type == NVGcodepointType.Char && nextWidth > breakRowWidth) {
8876         // the run length is too long, need to break to new line
8877         NVGTextRow!T row;
8878         row.string = str;
8879         if (breakEnd == rowStart) {
8880           // the current word is longer than the row length, just break it from here
8881           row.start = rowStart;
8882           row.end = cast(int)(iter.stringp-str.ptr);
8883           row.width = rowWidth*invscale;
8884           row.minx = rowMinX*invscale;
8885           row.maxx = rowMaxX*invscale;
8886           ++nrows;
8887           static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8888           rowStartX = iter.x;
8889           rowStart = cast(int)(iter.stringp-str.ptr);
8890           rowEnd = cast(int)(iter.nextp-str.ptr);
8891           rowWidth = iter.nextx-rowStartX;
8892           rowMinX = q.x0-rowStartX;
8893           rowMaxX = q.x1-rowStartX;
8894           wordStart = rowStart;
8895           wordStartX = iter.x;
8896           wordMinX = q.x0-rowStartX;
8897         } else {
8898           // break the line from the end of the last word, and start new line from the beginning of the new
8899           //{ import core.stdc.stdio : printf; printf("rowStart=%u; rowEnd=%u; breakEnd=%u; len=%u\n", rowStart, rowEnd, breakEnd, cast(uint)str.length); }
8900           row.start = rowStart;
8901           row.end = breakEnd;
8902           row.width = breakWidth*invscale;
8903           row.minx = rowMinX*invscale;
8904           row.maxx = breakMaxX*invscale;
8905           ++nrows;
8906           static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8907           rowStartX = wordStartX;
8908           rowStart = wordStart;
8909           rowEnd = cast(int)(iter.nextp-str.ptr);
8910           rowWidth = iter.nextx-rowStartX;
8911           rowMinX = wordMinX;
8912           rowMaxX = q.x1-rowStartX;
8913           // no change to the word start
8914         }
8915         // set null break point
8916         breakEnd = rowStart;
8917         breakWidth = 0.0;
8918         breakMaxX = 0.0;
8919       }
8920     }
8921 
8922     pcodepoint = iter.codepoint;
8923     ptype = type;
8924   }
8925 
8926   // break the line from the end of the last word, and start new line from the beginning of the new
8927   if (phase != Phase.SkipBlanks && rowStart < str.length) {
8928     //{ import core.stdc.stdio : printf; printf("  rowStart=%u; len=%u\n", rowStart, cast(uint)str.length); }
8929     NVGTextRow!T row;
8930     row.string = str;
8931     row.start = rowStart;
8932     row.end = cast(int)str.length;
8933     row.width = rowWidth*invscale;
8934     row.minx = rowMinX*invscale;
8935     row.maxx = rowMaxX*invscale;
8936     ++nrows;
8937     static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8938   }
8939 
8940   return nrows;
8941 }
8942 
8943 /** Returns iterator which you can use to calculate text bounds and advancement.
8944  * This is usable when you need to do some text layouting with wrapping, to avoid
8945  * guesswork ("will advancement for this space stay the same?"), and Schlemiel's
8946  * algorithm. Note that you can copy the returned struct to save iterator state.
8947  *
8948  * You can check if iterator is valid with [valid] property, put new chars with
8949  * [put] method, get current advance with [advance] property, and current
8950  * bounds with `getBounds(ref float[4] bounds)` method.
8951  *
8952  * $(WARNING Don't change font parameters while iterating! Or use [restoreFont] method.)
8953  *
8954  * Group: text_api
8955  */
8956 public struct TextBoundsIterator {
8957 private:
8958   NVGContext ctx;
8959   FONSTextBoundsIterator fsiter; // fontstash iterator
8960   float scale, invscale, xscaled, yscaled;
8961   // font settings
8962   float fsSize, fsSpacing, fsBlur;
8963   int fsFontId;
8964   NVGTextAlign fsAlign;
8965 
8966 public:
8967   /// Setups iteration. Takes current font parameters from the given NanoVega context.
8968   this (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { reset(actx, ax, ay); }
8969 
8970   /// Resets iteration. Takes current font parameters from the given NanoVega context.
8971   void reset (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc {
8972     fsiter = fsiter.init;
8973     this = this.init;
8974     if (actx is null) return;
8975     NVGstate* state = nvg__getState(actx);
8976     if (state is null) return;
8977     if (state.fontId == FONS_INVALID) { ctx = null; return; }
8978 
8979     ctx = actx;
8980     scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8981     invscale = 1.0f/scale;
8982 
8983     fsSize = state.fontSize*scale;
8984     fsSpacing = state.letterSpacing*scale;
8985     fsBlur = state.fontBlur*scale;
8986     fsAlign = state.textAlign;
8987     fsFontId = state.fontId;
8988     restoreFont();
8989 
8990     xscaled = ax*scale;
8991     yscaled = ay*scale;
8992     fsiter.reset(ctx.fs, xscaled, yscaled);
8993   }
8994 
8995   /// Restart iteration. Will not restore font.
8996   void restart () nothrow @trusted @nogc {
8997     if (ctx !is null) fsiter.reset(ctx.fs, xscaled, yscaled);
8998   }
8999 
9000   /// Restore font settings for the context.
9001   void restoreFont () nothrow @trusted @nogc {
9002     if (ctx !is null) {
9003       ctx.fs.size = fsSize;
9004       ctx.fs.spacing = fsSpacing;
9005       ctx.fs.blur = fsBlur;
9006       ctx.fs.textAlign = fsAlign;
9007       ctx.fs.fontId = fsFontId;
9008     }
9009   }
9010 
9011   /// Is this iterator valid?
9012   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null); }
9013 
9014   /// Add chars.
9015   void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { pragma(inline, true); if (ctx !is null) fsiter.put(str[]); }
9016 
9017   /// Returns current advance
9018   @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null ? fsiter.advance*invscale : 0); }
9019 
9020   /// Returns current text bounds.
9021   void getBounds (ref float[4] bounds) nothrow @trusted @nogc {
9022     if (ctx !is null) {
9023       fsiter.getBounds(bounds);
9024       ctx.fs.getLineBounds(yscaled, &bounds[1], &bounds[3]);
9025       bounds[0] *= invscale;
9026       bounds[1] *= invscale;
9027       bounds[2] *= invscale;
9028       bounds[3] *= invscale;
9029     } else {
9030       bounds[] = 0;
9031     }
9032   }
9033 
9034   /// Returns current horizontal text bounds.
9035   void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc {
9036     if (ctx !is null) {
9037       fsiter.getHBounds(xmin, xmax);
9038       xmin *= invscale;
9039       xmax *= invscale;
9040     }
9041   }
9042 
9043   /// Returns current vertical text bounds.
9044   void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc {
9045     if (ctx !is null) {
9046       //fsiter.getVBounds(ymin, ymax);
9047       ctx.fs.getLineBounds(yscaled, &ymin, &ymax);
9048       ymin *= invscale;
9049       ymax *= invscale;
9050     }
9051   }
9052 }
9053 
9054 /// Returns font line height (without line spacing), measured in local coordinate space.
9055 /// Group: text_api
9056 public float textFontHeight (NVGContext ctx) nothrow @trusted @nogc {
9057   float res = void;
9058   ctx.textMetrics(null, null, &res);
9059   return res;
9060 }
9061 
9062 /// Returns font ascender (positive), measured in local coordinate space.
9063 /// Group: text_api
9064 public float textFontAscender (NVGContext ctx) nothrow @trusted @nogc {
9065   float res = void;
9066   ctx.textMetrics(&res, null, null);
9067   return res;
9068 }
9069 
9070 /// Returns font descender (negative), measured in local coordinate space.
9071 /// Group: text_api
9072 public float textFontDescender (NVGContext ctx) nothrow @trusted @nogc {
9073   float res = void;
9074   ctx.textMetrics(null, &res, null);
9075   return res;
9076 }
9077 
9078 /** Measures the specified text string. Returns horizontal and vertical sizes of the measured text.
9079  * Measured values are returned in local coordinate space.
9080  *
9081  * Group: text_api
9082  */
9083 public void textExtents(T) (NVGContext ctx, const(T)[] str, float *w, float *h) nothrow @trusted @nogc if (isAnyCharType!T) {
9084   float[4] bnd = void;
9085   ctx.textBounds(0, 0, str, bnd[]);
9086   if (!ctx.fs.getFontAA(nvg__getState(ctx).fontId)) {
9087     if (w !is null) *w = nvg__lrintf(bnd.ptr[2]-bnd.ptr[0]);
9088     if (h !is null) *h = nvg__lrintf(bnd.ptr[3]-bnd.ptr[1]);
9089   } else {
9090     if (w !is null) *w = bnd.ptr[2]-bnd.ptr[0];
9091     if (h !is null) *h = bnd.ptr[3]-bnd.ptr[1];
9092   }
9093 }
9094 
9095 /** Measures the specified text string. Returns horizontal size of the measured text.
9096  * Measured values are returned in local coordinate space.
9097  *
9098  * Group: text_api
9099  */
9100 public float textWidth(T) (NVGContext ctx, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
9101   float w = void;
9102   ctx.textExtents(str, &w, null);
9103   return w;
9104 }
9105 
9106 /** Measures the specified text string. Parameter bounds should be a float[4],
9107  * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax]
9108  * Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
9109  * Measured values are returned in local coordinate space.
9110  *
9111  * Group: text_api
9112  */
9113 public float textBounds(T) (NVGContext ctx, float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc
9114 if (isAnyCharType!T)
9115 {
9116   NVGstate* state = nvg__getState(ctx);
9117 
9118   if (state.fontId == FONS_INVALID) {
9119     bounds[] = 0;
9120     return 0;
9121   }
9122 
9123   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9124   ctx.fs.size = state.fontSize*scale;
9125   ctx.fs.spacing = state.letterSpacing*scale;
9126   ctx.fs.blur = state.fontBlur*scale;
9127   ctx.fs.textAlign = state.textAlign;
9128   ctx.fs.fontId = state.fontId;
9129 
9130   float[4] b = void;
9131   immutable float width = ctx.fs.getTextBounds(x*scale, y*scale, str, b[]);
9132   immutable float invscale = 1.0f/scale;
9133   if (bounds.length) {
9134     // use line bounds for height
9135     ctx.fs.getLineBounds(y*scale, b.ptr+1, b.ptr+3);
9136     if (bounds.length > 0) bounds.ptr[0] = b.ptr[0]*invscale;
9137     if (bounds.length > 1) bounds.ptr[1] = b.ptr[1]*invscale;
9138     if (bounds.length > 2) bounds.ptr[2] = b.ptr[2]*invscale;
9139     if (bounds.length > 3) bounds.ptr[3] = b.ptr[3]*invscale;
9140   }
9141   return width*invscale;
9142 }
9143 
9144 /// Ditto.
9145 public void textBoxBounds(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str, float[] bounds) if (isAnyCharType!T) {
9146   NVGstate* state = nvg__getState(ctx);
9147   NVGTextRow!T[2] rows;
9148   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9149   float invscale = 1.0f/scale;
9150   float lineh = 0, rminy = 0, rmaxy = 0;
9151   float minx, miny, maxx, maxy;
9152 
9153   if (state.fontId == FONS_INVALID) {
9154     bounds[] = 0;
9155     return;
9156   }
9157 
9158   auto oldAlign = state.textAlign;
9159   scope(exit) state.textAlign = oldAlign;
9160   auto halign = state.textAlign.horizontal;
9161 
9162   ctx.textMetrics(null, null, &lineh);
9163   state.textAlign.horizontal = NVGTextAlign.H.Left;
9164 
9165   minx = maxx = x;
9166   miny = maxy = y;
9167 
9168   ctx.fs.size = state.fontSize*scale;
9169   ctx.fs.spacing = state.letterSpacing*scale;
9170   ctx.fs.blur = state.fontBlur*scale;
9171   ctx.fs.textAlign = state.textAlign;
9172   ctx.fs.fontId = state.fontId;
9173   ctx.fs.getLineBounds(0, &rminy, &rmaxy);
9174   rminy *= invscale;
9175   rmaxy *= invscale;
9176 
9177   for (;;) {
9178     auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]);
9179     if (rres.length == 0) break;
9180     foreach (ref row; rres) {
9181       float rminx, rmaxx, dx = 0;
9182       // horizontal bounds
9183       final switch (halign) {
9184         case NVGTextAlign.H.Left: dx = 0; break;
9185         case NVGTextAlign.H.Center: dx = breakRowWidth*0.5f-row.width*0.5f; break;
9186         case NVGTextAlign.H.Right: dx = breakRowWidth-row.width; break;
9187       }
9188       rminx = x+row.minx+dx;
9189       rmaxx = x+row.maxx+dx;
9190       minx = nvg__min(minx, rminx);
9191       maxx = nvg__max(maxx, rmaxx);
9192       // vertical bounds
9193       miny = nvg__min(miny, y+rminy);
9194       maxy = nvg__max(maxy, y+rmaxy);
9195       y += lineh*state.lineHeight;
9196     }
9197     str = rres[$-1].rest;
9198   }
9199 
9200   if (bounds.length) {
9201     if (bounds.length > 0) bounds.ptr[0] = minx;
9202     if (bounds.length > 1) bounds.ptr[1] = miny;
9203     if (bounds.length > 2) bounds.ptr[2] = maxx;
9204     if (bounds.length > 3) bounds.ptr[3] = maxy;
9205   }
9206 }
9207 
9208 /// Returns the vertical metrics based on the current text style. Measured values are returned in local coordinate space.
9209 /// Group: text_api
9210 public void textMetrics (NVGContext ctx, float* ascender, float* descender, float* lineh) nothrow @trusted @nogc {
9211   NVGstate* state = nvg__getState(ctx);
9212 
9213   if (state.fontId == FONS_INVALID) {
9214     if (ascender !is null) *ascender *= 0;
9215     if (descender !is null) *descender *= 0;
9216     if (lineh !is null) *lineh *= 0;
9217     return;
9218   }
9219 
9220   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9221   immutable float invscale = 1.0f/scale;
9222 
9223   ctx.fs.size = state.fontSize*scale;
9224   ctx.fs.spacing = state.letterSpacing*scale;
9225   ctx.fs.blur = state.fontBlur*scale;
9226   ctx.fs.textAlign = state.textAlign;
9227   ctx.fs.fontId = state.fontId;
9228 
9229   ctx.fs.getVertMetrics(ascender, descender, lineh);
9230   if (ascender !is null) *ascender *= invscale;
9231   if (descender !is null) *descender *= invscale;
9232   if (lineh !is null) *lineh *= invscale;
9233 }
9234 
9235 
9236 // ////////////////////////////////////////////////////////////////////////// //
9237 // fontstash
9238 // ////////////////////////////////////////////////////////////////////////// //
9239 import core.stdc.stdlib : malloc, realloc, free;
9240 import core.stdc.string : memset, memcpy, strncpy, strcmp, strlen;
9241 import core.stdc.stdio : FILE, fopen, fclose, fseek, ftell, fread, SEEK_END, SEEK_SET;
9242 
9243 public:
9244 // welcome to version hell!
9245 version(nanovg_force_stb_ttf) {
9246 } else {
9247   version(nanovg_force_detect) {} else version(nanovg_use_freetype) { version = nanovg_use_freetype_ii; }
9248 }
9249 version(nanovg_ignore_iv_stb_ttf) enum nanovg_ignore_iv_stb_ttf = true; else enum nanovg_ignore_iv_stb_ttf = false;
9250 //version(nanovg_ignore_mono);
9251 
9252 version(nanovg_force_stb_ttf) {
9253   private enum NanoVegaForceFreeType = false;
9254 } else {
9255   version (nanovg_builtin_freetype_bindings) {
9256     version(Posix) {
9257       private enum NanoVegaForceFreeType = true;
9258     } else {
9259       private enum NanoVegaForceFreeType = false;
9260     }
9261   } else {
9262     version(Posix) {
9263       private enum NanoVegaForceFreeType = true;
9264     } else {
9265       private enum NanoVegaForceFreeType = false;
9266     }
9267   }
9268 }
9269 
9270 version(nanovg_use_freetype_ii) {
9271   enum NanoVegaIsUsingSTBTTF = false;
9272   //pragma(msg, "iv.freetype: forced");
9273 } else {
9274   static if (NanoVegaForceFreeType) {
9275     enum NanoVegaIsUsingSTBTTF = false;
9276   } else {
9277     static if (!nanovg_ignore_iv_stb_ttf && __traits(compiles, { import iv.stb.ttf; })) {
9278       import iv.stb.ttf;
9279       enum NanoVegaIsUsingSTBTTF = true;
9280       version(nanovg_report_stb_ttf) pragma(msg, "iv.stb.ttf");
9281     } else static if (__traits(compiles, { import arsd.ttf; })) {
9282       import arsd.ttf;
9283       enum NanoVegaIsUsingSTBTTF = true;
9284       version(nanovg_report_stb_ttf) pragma(msg, "arsd.ttf");
9285     } else static if (__traits(compiles, { import stb_truetype; })) {
9286       import stb_truetype;
9287       enum NanoVegaIsUsingSTBTTF = true;
9288       version(nanovg_report_stb_ttf) pragma(msg, "stb_truetype");
9289     } else static if (__traits(compiles, { import iv.freetype; })) {
9290       version (nanovg_builtin_freetype_bindings) {
9291         enum NanoVegaIsUsingSTBTTF = false;
9292         version = nanovg_builtin_freetype_bindings;
9293       } else {
9294         import iv.freetype;
9295         enum NanoVegaIsUsingSTBTTF = false;
9296       }
9297       version(nanovg_report_stb_ttf) pragma(msg, "freetype");
9298     } else {
9299       static assert(0, "no stb_ttf/iv.freetype found!");
9300     }
9301   }
9302 }
9303 
9304 
9305 // ////////////////////////////////////////////////////////////////////////// //
9306 //version = nanovg_ft_mono;
9307 
9308 /// Invald font id.
9309 /// Group: font_stash
9310 public enum FONS_INVALID = -1;
9311 
9312 public enum FONSBitmapFlag : uint {
9313   Required = 0,
9314   Optional = 1,
9315 }
9316 
9317 public enum FONSError : int {
9318   NoError = 0,
9319   AtlasFull = 1, // Font atlas is full.
9320   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.
9321   StatesOverflow = 3, // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES.
9322   StatesUnderflow = 4, // Trying to pop too many states fonsPopState().
9323 }
9324 
9325 /// Initial parameters for new FontStash.
9326 /// Group: font_stash
9327 public struct FONSParams {
9328   enum Flag : uint {
9329     ZeroTopLeft    = 0U, // default
9330     ZeroBottomLeft = 1U,
9331   }
9332   int width, height;
9333   Flag flags = Flag.ZeroTopLeft;
9334   void* userPtr;
9335   bool function (void* uptr, int width, int height) nothrow @trusted @nogc renderCreate;
9336   int function (void* uptr, int width, int height) nothrow @trusted @nogc renderResize;
9337   void function (void* uptr, int* rect, const(ubyte)* data) nothrow @trusted @nogc renderUpdate;
9338   void function (void* uptr) nothrow @trusted @nogc renderDelete;
9339   @property bool isZeroTopLeft () const pure nothrow @trusted @nogc { pragma(inline, true); return ((flags&Flag.ZeroBottomLeft) == 0); }
9340 }
9341 
9342 //TODO: document this
9343 public struct FONSQuad {
9344   float x0=0, y0=0, s0=0, t0=0;
9345   float x1=0, y1=0, s1=0, t1=0;
9346 }
9347 
9348 //TODO: document this
9349 public struct FONSTextIter(CT) if (isAnyCharType!CT) {
9350   alias CharType = CT;
9351   float x=0, y=0, nextx=0, nexty=0, scale=0, spacing=0;
9352   uint codepoint;
9353   short isize, iblur;
9354   FONSContext stash;
9355   FONSfont* font;
9356   int prevGlyphIndex;
9357   const(CT)* s; // string
9358   const(CT)* n; // next
9359   const(CT)* e; // end
9360   FONSBitmapFlag bitmapOption;
9361   static if (is(CT == char)) {
9362     uint utf8state;
9363   }
9364 
9365   this (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { setup(astash, ax, ay, astr, abitmapOption); }
9366   ~this () nothrow @trusted @nogc { pragma(inline, true); static if (is(CT == char)) utf8state = 0; s = n = e = null; }
9367 
9368   @property const(CT)* stringp () const pure nothrow @trusted @nogc { pragma(inline, true); return s; }
9369   @property const(CT)* nextp () const pure nothrow @trusted @nogc { pragma(inline, true); return n; }
9370   @property const(CT)* endp () const pure nothrow @trusted @nogc { pragma(inline, true); return e; }
9371 
9372   bool setup (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc {
9373     import core.stdc.string : memset;
9374 
9375     memset(&this, 0, this.sizeof);
9376     if (astash is null) return false;
9377 
9378     FONSstate* state = astash.getState;
9379 
9380     if (state.font < 0 || state.font >= astash.nfonts) return false;
9381     font = astash.fonts[state.font];
9382     if (font is null || font.fdata is null) return false;
9383 
9384     isize = cast(short)(state.size*10.0f);
9385     iblur = cast(short)state.blur;
9386     scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
9387 
9388     // align horizontally
9389     if (state.talign.left) {
9390       // empty
9391     } else if (state.talign.right) {
9392       immutable float width = astash.getTextBounds(ax, ay, astr, null);
9393       ax -= width;
9394     } else if (state.talign.center) {
9395       immutable float width = astash.getTextBounds(ax, ay, astr, null);
9396       ax -= width*0.5f;
9397     }
9398 
9399     // align vertically
9400     ay += astash.getVertAlign(font, state.talign, isize);
9401 
9402     x = nextx = ax;
9403     y = nexty = ay;
9404     spacing = state.spacing;
9405 
9406     if (astr.ptr is null) {
9407            static if (is(CharType == char)) astr = "";
9408       else static if (is(CharType == wchar)) astr = ""w;
9409       else static if (is(CharType == dchar)) astr = ""d;
9410       else static assert(0, "wtf?!");
9411     }
9412     s = astr.ptr;
9413     n = astr.ptr;
9414     e = astr.ptr+astr.length;
9415 
9416     codepoint = 0;
9417     prevGlyphIndex = -1;
9418     bitmapOption = abitmapOption;
9419     stash = astash;
9420 
9421     return true;
9422   }
9423 
9424   bool getDummyChar (ref FONSQuad quad) nothrow @trusted @nogc {
9425     if (stash is null || font is null) return false;
9426     // get glyph and quad
9427     x = nextx;
9428     y = nexty;
9429     FONSglyph* glyph = stash.getGlyph(font, 0xFFFD, isize, iblur, bitmapOption);
9430     if (glyph !is null) {
9431       stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9432       prevGlyphIndex = glyph.index;
9433       return true;
9434     } else {
9435       prevGlyphIndex = -1;
9436       return false;
9437     }
9438   }
9439 
9440   bool next (ref FONSQuad quad) nothrow @trusted @nogc {
9441     if (stash is null || font is null) return false;
9442     FONSglyph* glyph = null;
9443     static if (is(CharType == char)) {
9444       const(char)* str = this.n;
9445       this.s = this.n;
9446       if (str is this.e) return false;
9447       const(char)* e = this.e;
9448       for (; str !is e; ++str) {
9449         /*if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;*/
9450         mixin(DecUtfMixin!("this.utf8state", "this.codepoint", "*cast(const(ubyte)*)str"));
9451         if (utf8state) continue;
9452         ++str; // 'cause we'll break anyway
9453         // get glyph and quad
9454         x = nextx;
9455         y = nexty;
9456         glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption);
9457         if (glyph !is null) {
9458           stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9459           prevGlyphIndex = glyph.index;
9460         } else {
9461           prevGlyphIndex = -1;
9462         }
9463         break;
9464       }
9465       this.n = str;
9466     } else {
9467       const(CharType)* str = this.n;
9468       this.s = this.n;
9469       if (str is this.e) return false;
9470       codepoint = cast(uint)(*str++);
9471       if (codepoint > dchar.max) codepoint = 0xFFFD;
9472       // get glyph and quad
9473       x = nextx;
9474       y = nexty;
9475       glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption);
9476       if (glyph !is null) {
9477         stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9478         prevGlyphIndex = glyph.index;
9479       } else {
9480         prevGlyphIndex = -1;
9481       }
9482       this.n = str;
9483     }
9484     return true;
9485   }
9486 }
9487 
9488 
9489 // ////////////////////////////////////////////////////////////////////////// //
9490 //static if (!HasAST) version = nanovg_use_freetype_ii_x;
9491 
9492 /*version(nanovg_use_freetype_ii_x)*/ static if (!NanoVegaIsUsingSTBTTF) {
9493 version(nanovg_builtin_freetype_bindings) {
9494 pragma(lib, "freetype");
9495 private extern(C) nothrow @trusted @nogc {
9496 private import core.stdc.config : c_long, c_ulong;
9497 alias FT_Pos = c_long;
9498 // config/ftconfig.h
9499 alias FT_Int16 = short;
9500 alias FT_UInt16 = ushort;
9501 alias FT_Int32 = int;
9502 alias FT_UInt32 = uint;
9503 alias FT_Fast = int;
9504 alias FT_UFast = uint;
9505 alias FT_Int64 = long;
9506 alias FT_Uint64 = ulong;
9507 // fttypes.h
9508 alias FT_Bool = ubyte;
9509 alias FT_FWord = short;
9510 alias FT_UFWord = ushort;
9511 alias FT_Char = char;
9512 alias FT_Byte = ubyte;
9513 alias FT_Bytes = FT_Byte*;
9514 alias FT_Tag = FT_UInt32;
9515 alias FT_String = char;
9516 alias FT_Short = short;
9517 alias FT_UShort = ushort;
9518 alias FT_Int = int;
9519 alias FT_UInt = uint;
9520 alias FT_Long = c_long;
9521 alias FT_ULong = c_ulong;
9522 alias FT_F2Dot14 = short;
9523 alias FT_F26Dot6 = c_long;
9524 alias FT_Fixed = c_long;
9525 alias FT_Error = int;
9526 alias FT_Pointer = void*;
9527 alias FT_Offset = usize;
9528 alias FT_PtrDist = ptrdiff_t;
9529 
9530 struct FT_UnitVector {
9531   FT_F2Dot14 x;
9532   FT_F2Dot14 y;
9533 }
9534 
9535 struct FT_Matrix {
9536   FT_Fixed xx, xy;
9537   FT_Fixed yx, yy;
9538 }
9539 
9540 struct FT_Data {
9541   const(FT_Byte)* pointer;
9542   FT_Int length;
9543 }
9544 alias FT_Face = FT_FaceRec*;
9545 struct FT_FaceRec {
9546   FT_Long num_faces;
9547   FT_Long face_index;
9548   FT_Long face_flags;
9549   FT_Long style_flags;
9550   FT_Long num_glyphs;
9551   FT_String* family_name;
9552   FT_String* style_name;
9553   FT_Int num_fixed_sizes;
9554   FT_Bitmap_Size* available_sizes;
9555   FT_Int num_charmaps;
9556   FT_CharMap* charmaps;
9557   FT_Generic generic;
9558   FT_BBox bbox;
9559   FT_UShort units_per_EM;
9560   FT_Short ascender;
9561   FT_Short descender;
9562   FT_Short height;
9563   FT_Short max_advance_width;
9564   FT_Short max_advance_height;
9565   FT_Short underline_position;
9566   FT_Short underline_thickness;
9567   FT_GlyphSlot glyph;
9568   FT_Size size;
9569   FT_CharMap charmap;
9570   FT_Driver driver;
9571   FT_Memory memory;
9572   FT_Stream stream;
9573   FT_ListRec sizes_list;
9574   FT_Generic autohint;
9575   void* extensions;
9576   FT_Face_Internal internal;
9577 }
9578 struct FT_Bitmap_Size {
9579   FT_Short height;
9580   FT_Short width;
9581   FT_Pos size;
9582   FT_Pos x_ppem;
9583   FT_Pos y_ppem;
9584 }
9585 alias FT_CharMap = FT_CharMapRec*;
9586 struct FT_CharMapRec {
9587   FT_Face face;
9588   FT_Encoding encoding;
9589   FT_UShort platform_id;
9590   FT_UShort encoding_id;
9591 }
9592 extern(C) nothrow @nogc { alias FT_Generic_Finalizer = void function (void* object); }
9593 struct FT_Generic {
9594   void* data;
9595   FT_Generic_Finalizer finalizer;
9596 }
9597 struct FT_Vector {
9598   FT_Pos x;
9599   FT_Pos y;
9600 }
9601 struct FT_BBox {
9602   FT_Pos xMin, yMin;
9603   FT_Pos xMax, yMax;
9604 }
9605 alias FT_Pixel_Mode = int;
9606 enum {
9607   FT_PIXEL_MODE_NONE = 0,
9608   FT_PIXEL_MODE_MONO,
9609   FT_PIXEL_MODE_GRAY,
9610   FT_PIXEL_MODE_GRAY2,
9611   FT_PIXEL_MODE_GRAY4,
9612   FT_PIXEL_MODE_LCD,
9613   FT_PIXEL_MODE_LCD_V,
9614   FT_PIXEL_MODE_MAX
9615 }
9616 struct FT_Bitmap {
9617   uint rows;
9618   uint width;
9619   int pitch;
9620   ubyte* buffer;
9621   ushort num_grays;
9622   ubyte pixel_mode;
9623   ubyte palette_mode;
9624   void* palette;
9625 }
9626 struct FT_Outline {
9627   short n_contours;
9628   short n_points;
9629   FT_Vector* points;
9630   byte* tags;
9631   short* contours;
9632   int flags;
9633 }
9634 alias FT_GlyphSlot = FT_GlyphSlotRec*;
9635 struct FT_GlyphSlotRec {
9636   FT_Library library;
9637   FT_Face face;
9638   FT_GlyphSlot next;
9639   FT_UInt reserved;
9640   FT_Generic generic;
9641   FT_Glyph_Metrics metrics;
9642   FT_Fixed linearHoriAdvance;
9643   FT_Fixed linearVertAdvance;
9644   FT_Vector advance;
9645   FT_Glyph_Format format;
9646   FT_Bitmap bitmap;
9647   FT_Int bitmap_left;
9648   FT_Int bitmap_top;
9649   FT_Outline outline;
9650   FT_UInt num_subglyphs;
9651   FT_SubGlyph subglyphs;
9652   void* control_data;
9653   c_long control_len;
9654   FT_Pos lsb_delta;
9655   FT_Pos rsb_delta;
9656   void* other;
9657   FT_Slot_Internal internal;
9658 }
9659 alias FT_Size = FT_SizeRec*;
9660 struct FT_SizeRec {
9661   FT_Face face;
9662   FT_Generic generic;
9663   FT_Size_Metrics metrics;
9664   FT_Size_Internal internal;
9665 }
9666 alias FT_Encoding = FT_Tag;
9667 alias FT_Face_Internal = void*;
9668 alias FT_Driver = void*;
9669 alias FT_Memory = void*;
9670 alias FT_Stream = void*;
9671 alias FT_Library = void*;
9672 alias FT_SubGlyph = void*;
9673 alias FT_Slot_Internal = void*;
9674 alias FT_Size_Internal = void*;
9675 alias FT_ListNode = FT_ListNodeRec*;
9676 alias FT_List = FT_ListRec*;
9677 struct FT_ListNodeRec {
9678   FT_ListNode prev;
9679   FT_ListNode next;
9680   void* data;
9681 }
9682 struct FT_ListRec {
9683   FT_ListNode head;
9684   FT_ListNode tail;
9685 }
9686 struct FT_Glyph_Metrics {
9687   FT_Pos width;
9688   FT_Pos height;
9689   FT_Pos horiBearingX;
9690   FT_Pos horiBearingY;
9691   FT_Pos horiAdvance;
9692   FT_Pos vertBearingX;
9693   FT_Pos vertBearingY;
9694   FT_Pos vertAdvance;
9695 }
9696 alias FT_Glyph_Format = FT_Tag;
9697 FT_Tag FT_MAKE_TAG (char x1, char x2, char x3, char x4) pure nothrow @safe @nogc {
9698   pragma(inline, true);
9699   return cast(FT_UInt32)((x1<<24)|(x2<<16)|(x3<<8)|x4);
9700 }
9701 enum : FT_Tag {
9702   FT_GLYPH_FORMAT_NONE = 0,
9703   FT_GLYPH_FORMAT_COMPOSITE = FT_MAKE_TAG('c','o','m','p'),
9704   FT_GLYPH_FORMAT_BITMAP = FT_MAKE_TAG('b','i','t','s'),
9705   FT_GLYPH_FORMAT_OUTLINE = FT_MAKE_TAG('o','u','t','l'),
9706   FT_GLYPH_FORMAT_PLOTTER = FT_MAKE_TAG('p','l','o','t'),
9707 }
9708 struct FT_Size_Metrics {
9709   FT_UShort x_ppem;
9710   FT_UShort y_ppem;
9711 
9712   FT_Fixed x_scale;
9713   FT_Fixed y_scale;
9714 
9715   FT_Pos ascender;
9716   FT_Pos descender;
9717   FT_Pos height;
9718   FT_Pos max_advance;
9719 }
9720 enum FT_LOAD_DEFAULT = 0x0U;
9721 enum FT_LOAD_NO_SCALE = 1U<<0;
9722 enum FT_LOAD_NO_HINTING = 1U<<1;
9723 enum FT_LOAD_RENDER = 1U<<2;
9724 enum FT_LOAD_NO_BITMAP = 1U<<3;
9725 enum FT_LOAD_VERTICAL_LAYOUT = 1U<<4;
9726 enum FT_LOAD_FORCE_AUTOHINT = 1U<<5;
9727 enum FT_LOAD_CROP_BITMAP = 1U<<6;
9728 enum FT_LOAD_PEDANTIC = 1U<<7;
9729 enum FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1U<<9;
9730 enum FT_LOAD_NO_RECURSE = 1U<<10;
9731 enum FT_LOAD_IGNORE_TRANSFORM = 1U<<11;
9732 enum FT_LOAD_MONOCHROME = 1U<<12;
9733 enum FT_LOAD_LINEAR_DESIGN = 1U<<13;
9734 enum FT_LOAD_NO_AUTOHINT = 1U<<15;
9735 enum FT_LOAD_COLOR = 1U<<20;
9736 enum FT_LOAD_COMPUTE_METRICS = 1U<<21;
9737 enum FT_FACE_FLAG_KERNING = 1U<<6;
9738 alias FT_Kerning_Mode = int;
9739 enum /*FT_Kerning_Mode*/ {
9740   FT_KERNING_DEFAULT = 0,
9741   FT_KERNING_UNFITTED,
9742   FT_KERNING_UNSCALED
9743 }
9744 extern(C) nothrow @nogc {
9745   alias FT_Outline_MoveToFunc = int function (const(FT_Vector)*, void*);
9746   alias FT_Outline_LineToFunc = int function (const(FT_Vector)*, void*);
9747   alias FT_Outline_ConicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, void*);
9748   alias FT_Outline_CubicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, const(FT_Vector)*, void*);
9749 }
9750 struct FT_Outline_Funcs {
9751   FT_Outline_MoveToFunc move_to;
9752   FT_Outline_LineToFunc line_to;
9753   FT_Outline_ConicToFunc conic_to;
9754   FT_Outline_CubicToFunc cubic_to;
9755   int shift;
9756   FT_Pos delta;
9757 }
9758 
9759 FT_Error FT_Init_FreeType (FT_Library*);
9760 FT_Error FT_New_Memory_Face (FT_Library, const(FT_Byte)*, FT_Long, FT_Long, FT_Face*);
9761 FT_UInt FT_Get_Char_Index (FT_Face, FT_ULong);
9762 FT_Error FT_Set_Pixel_Sizes (FT_Face, FT_UInt, FT_UInt);
9763 FT_Error FT_Load_Glyph (FT_Face, FT_UInt, FT_Int32);
9764 FT_Error FT_Get_Advance (FT_Face, FT_UInt, FT_Int32, FT_Fixed*);
9765 FT_Error FT_Get_Kerning (FT_Face, FT_UInt, FT_UInt, FT_UInt, FT_Vector*);
9766 void FT_Outline_Get_CBox (const(FT_Outline)*, FT_BBox*);
9767 FT_Error FT_Outline_Decompose (FT_Outline*, const(FT_Outline_Funcs)*, void*);
9768 }
9769 } else version(bindbc) {
9770   import bindbc.freetype;
9771   alias FT_KERNING_DEFAULT = FT_Kerning_Mode.FT_KERNING_DEFAULT;
9772   alias FT_KERNING_UNFITTED = FT_Kerning_Mode.FT_KERNING_UNFITTED;
9773   alias FT_KERNING_UNSCALED = FT_Kerning_Mode.FT_KERNING_UNSCALED;
9774 } else {
9775   import iv.freetype;
9776 }
9777 
9778 struct FONSttFontImpl {
9779   FT_Face font;
9780   bool mono; // no aa?
9781 }
9782 
9783 __gshared FT_Library ftLibrary;
9784 
9785 int fons__tt_init (FONSContext context) nothrow @trusted @nogc {
9786   FT_Error ftError;
9787   //FONS_NOTUSED(context);
9788   ftError = FT_Init_FreeType(&ftLibrary);
9789   return (ftError == 0);
9790 }
9791 
9792 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc {
9793   font.mono = v;
9794 }
9795 
9796 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc {
9797   return font.mono;
9798 }
9799 
9800 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc {
9801   FT_Error ftError;
9802   //font.font.userdata = stash;
9803   ftError = FT_New_Memory_Face(ftLibrary, cast(const(FT_Byte)*)data, dataSize, 0, &font.font);
9804   return ftError == 0;
9805 }
9806 
9807 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc {
9808   *ascent = font.font.ascender;
9809   *descent = font.font.descender;
9810   *lineGap = font.font.height-(*ascent - *descent);
9811 }
9812 
9813 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc {
9814   return size/(font.font.ascender-font.font.descender);
9815 }
9816 
9817 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc {
9818   return FT_Get_Char_Index(font.font, codepoint);
9819 }
9820 
9821 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 {
9822   FT_Error ftError;
9823   FT_GlyphSlot ftGlyph;
9824   //version(nanovg_ignore_mono) enum exflags = 0;
9825   //else version(nanovg_ft_mono) enum exflags = FT_LOAD_MONOCHROME; else enum exflags = 0;
9826   uint exflags = (font.mono ? FT_LOAD_MONOCHROME : 0);
9827   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)));
9828   if (ftError) return 0;
9829   ftError = FT_Load_Glyph(font.font, glyph, FT_LOAD_RENDER|/*FT_LOAD_NO_AUTOHINT|*/exflags);
9830   if (ftError) return 0;
9831   ftError = FT_Get_Advance(font.font, glyph, FT_LOAD_NO_SCALE|/*FT_LOAD_NO_AUTOHINT|*/exflags, cast(FT_Fixed*)advance);
9832   if (ftError) return 0;
9833   ftGlyph = font.font.glyph;
9834   *lsb = cast(int)ftGlyph.metrics.horiBearingX;
9835   *x0 = ftGlyph.bitmap_left;
9836   *x1 = *x0+ftGlyph.bitmap.width;
9837   *y0 = -ftGlyph.bitmap_top;
9838   *y1 = *y0+ftGlyph.bitmap.rows;
9839   return 1;
9840 }
9841 
9842 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc {
9843   FT_GlyphSlot ftGlyph = font.font.glyph;
9844   //FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap
9845   //version(nanovg_ignore_mono) enum RenderAA = true;
9846   //else version(nanovg_ft_mono) enum RenderAA = false;
9847   //else enum RenderAA = true;
9848   if (font.mono) {
9849     auto src = ftGlyph.bitmap.buffer;
9850     auto dst = output;
9851     auto spt = ftGlyph.bitmap.pitch;
9852     if (spt < 0) spt = -spt;
9853     foreach (int y; 0..ftGlyph.bitmap.rows) {
9854       ubyte count = 0, b = 0;
9855       auto s = src;
9856       auto d = dst;
9857       foreach (int x; 0..ftGlyph.bitmap.width) {
9858         if (count-- == 0) { count = 7; b = *s++; } else b <<= 1;
9859         *d++ = (b&0x80 ? 255 : 0);
9860       }
9861       src += spt;
9862       dst += outStride;
9863     }
9864   } else {
9865     auto src = ftGlyph.bitmap.buffer;
9866     auto dst = output;
9867     auto spt = ftGlyph.bitmap.pitch;
9868     if (spt < 0) spt = -spt;
9869     foreach (int y; 0..ftGlyph.bitmap.rows) {
9870       import core.stdc.string : memcpy;
9871       //dst[0..ftGlyph.bitmap.width] = src[0..ftGlyph.bitmap.width];
9872       memcpy(dst, src, ftGlyph.bitmap.width);
9873       src += spt;
9874       dst += outStride;
9875     }
9876   }
9877 }
9878 
9879 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc {
9880   FT_Vector ftKerning;
9881   version(none) {
9882     // fitted kerning
9883     FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning);
9884     //{ import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d\n", glyph1, glyph2, ftKerning.x, ftKerning.y); }
9885     return cast(int)ftKerning.x; // round up and convert to integer
9886   } else {
9887     // unfitted kerning
9888     //FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNFITTED, &ftKerning);
9889     if (glyph1 <= 0 || glyph2 <= 0 || (font.font.face_flags&FT_FACE_FLAG_KERNING) == 0) return 0;
9890     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;
9891     if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning)) return 0;
9892     version(none) {
9893       if (ftKerning.x) {
9894         //{ import core.stdc.stdio : printf; printf("has kerning: %u\n", cast(uint)(font.font.face_flags&FT_FACE_FLAG_KERNING)); }
9895         { import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d (size=%g)\n", glyph1, glyph2, ftKerning.x, ftKerning.y, cast(double)size); }
9896       }
9897     }
9898     version(none) {
9899       FT_Vector kk;
9900       if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNSCALED, &kk)) assert(0, "wtf?!");
9901       auto kadvfrac = FT_MulFix(kk.x, font.font.size.metrics.x_scale); // 1/64 of pixel
9902       //return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
9903       //assert(ftKerning.x == kadvfrac);
9904       if (ftKerning.x || kadvfrac) {
9905         { 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); }
9906       }
9907       //return cast(int)(kadvfrac+(kadvfrac < 0 ? -31 : 32)>>6); // round up and convert to integer
9908       return kadvfrac/64.0f;
9909     }
9910     //return cast(int)(ftKerning.x+(ftKerning.x < 0 ? -31 : 32)>>6); // round up and convert to integer
9911     return ftKerning.x/64.0f;
9912   }
9913 }
9914 
9915 extern(C) nothrow @trusted @nogc {
9916   static struct OutlinerData {
9917     @disable this (this);
9918     void opAssign() (const scope auto ref OutlinerData a) { static assert(0, "no copies!"); }
9919     NVGContext vg;
9920     NVGPathOutline.DataStore* ol;
9921     FT_BBox outlineBBox;
9922   nothrow @trusted @nogc:
9923     static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; }
9924     static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
9925   }
9926 
9927   int fons__nvg__moveto_cb (const(FT_Vector)* to, void* user) {
9928     auto odata = cast(OutlinerData*)user;
9929     if (odata.vg !is null) odata.vg.moveTo(odata.transx(to.x), odata.transy(to.y));
9930     if (odata.ol !is null) {
9931       odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo);
9932       odata.ol.putArgs(odata.transx(to.x));
9933       odata.ol.putArgs(odata.transy(to.y));
9934     }
9935     return 0;
9936   }
9937 
9938   int fons__nvg__lineto_cb (const(FT_Vector)* to, void* user) {
9939     auto odata = cast(OutlinerData*)user;
9940     if (odata.vg !is null) odata.vg.lineTo(odata.transx(to.x), odata.transy(to.y));
9941     if (odata.ol !is null) {
9942       odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo);
9943       odata.ol.putArgs(odata.transx(to.x));
9944       odata.ol.putArgs(odata.transy(to.y));
9945     }
9946     return 0;
9947   }
9948 
9949   int fons__nvg__quadto_cb (const(FT_Vector)* c1, const(FT_Vector)* to, void* user) {
9950     auto odata = cast(OutlinerData*)user;
9951     if (odata.vg !is null) odata.vg.quadTo(odata.transx(c1.x), odata.transy(c1.y), odata.transx(to.x), odata.transy(to.y));
9952     if (odata.ol !is null) {
9953       odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo);
9954       odata.ol.putArgs(odata.transx(c1.x));
9955       odata.ol.putArgs(odata.transy(c1.y));
9956       odata.ol.putArgs(odata.transx(to.x));
9957       odata.ol.putArgs(odata.transy(to.y));
9958     }
9959     return 0;
9960   }
9961 
9962   int fons__nvg__cubicto_cb (const(FT_Vector)* c1, const(FT_Vector)* c2, const(FT_Vector)* to, void* user) {
9963     auto odata = cast(OutlinerData*)user;
9964     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));
9965     if (odata.ol !is null) {
9966       odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo);
9967       odata.ol.putArgs(odata.transx(c1.x));
9968       odata.ol.putArgs(odata.transy(c1.y));
9969       odata.ol.putArgs(odata.transx(c2.x));
9970       odata.ol.putArgs(odata.transy(c2.y));
9971       odata.ol.putArgs(odata.transx(to.x));
9972       odata.ol.putArgs(odata.transy(to.y));
9973     }
9974     return 0;
9975   }
9976 }
9977 
9978 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc {
9979   if (bounds.length > 4) bounds = bounds.ptr[0..4];
9980 
9981   FT_Outline_Funcs funcs;
9982   funcs.move_to = &fons__nvg__moveto_cb;
9983   funcs.line_to = &fons__nvg__lineto_cb;
9984   funcs.conic_to = &fons__nvg__quadto_cb;
9985   funcs.cubic_to = &fons__nvg__cubicto_cb;
9986 
9987   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
9988   if (err) { bounds[] = 0; return false; }
9989   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; }
9990 
9991   FT_Outline outline = font.font.glyph.outline;
9992 
9993   OutlinerData odata;
9994   odata.vg = vg;
9995   FT_Outline_Get_CBox(&outline, &odata.outlineBBox);
9996 
9997   err = FT_Outline_Decompose(&outline, &funcs, &odata);
9998   if (err) { bounds[] = 0; return false; }
9999   if (bounds.length > 0) bounds.ptr[0] = odata.outlineBBox.xMin;
10000   if (bounds.length > 1) bounds.ptr[1] = -odata.outlineBBox.yMax;
10001   if (bounds.length > 2) bounds.ptr[2] = odata.outlineBBox.xMax;
10002   if (bounds.length > 3) bounds.ptr[3] = -odata.outlineBBox.yMin;
10003   return true;
10004 }
10005 
10006 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
10007   FT_Outline_Funcs funcs;
10008   funcs.move_to = &fons__nvg__moveto_cb;
10009   funcs.line_to = &fons__nvg__lineto_cb;
10010   funcs.conic_to = &fons__nvg__quadto_cb;
10011   funcs.cubic_to = &fons__nvg__cubicto_cb;
10012 
10013   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
10014   if (err) return false;
10015   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) return false;
10016 
10017   FT_Outline outline = font.font.glyph.outline;
10018 
10019   OutlinerData odata;
10020   odata.ol = ol;
10021   FT_Outline_Get_CBox(&outline, &odata.outlineBBox);
10022 
10023   err = FT_Outline_Decompose(&outline, &funcs, &odata);
10024   if (err) return false;
10025   ol.bounds.ptr[0] = odata.outlineBBox.xMin;
10026   ol.bounds.ptr[1] = -odata.outlineBBox.yMax;
10027   ol.bounds.ptr[2] = odata.outlineBBox.xMax;
10028   ol.bounds.ptr[3] = -odata.outlineBBox.yMin;
10029   return true;
10030 }
10031 
10032 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc {
10033   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10034 
10035   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
10036   if (err) return false;
10037   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; }
10038 
10039   FT_Outline outline = font.font.glyph.outline;
10040   FT_BBox outlineBBox;
10041   FT_Outline_Get_CBox(&outline, &outlineBBox);
10042   if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin;
10043   if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax;
10044   if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax;
10045   if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin;
10046   return true;
10047 }
10048 
10049 
10050 } else {
10051 // ////////////////////////////////////////////////////////////////////////// //
10052 // sorry
10053 import std.traits : isFunctionPointer, isDelegate;
10054 private auto assumeNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) {
10055   import std.traits;
10056   enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
10057   return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
10058 }
10059 
10060 private auto forceNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) {
10061   try {
10062     return assumeNoThrowNoGC(t)();
10063   } catch (Exception e) {
10064     assert(0, "OOPS!");
10065   }
10066 }
10067 
10068 struct FONSttFontImpl {
10069   stbtt_fontinfo font;
10070   bool mono; // no aa?
10071 }
10072 
10073 int fons__tt_init (FONSContext context) nothrow @trusted @nogc {
10074   return 1;
10075 }
10076 
10077 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc {
10078   font.mono = v;
10079 }
10080 
10081 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc {
10082   return font.mono;
10083 }
10084 
10085 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc {
10086   int stbError;
10087   font.font.userdata = context;
10088   forceNoThrowNoGC({ stbError = stbtt_InitFont(&font.font, data, 0); });
10089   return stbError;
10090 }
10091 
10092 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc {
10093   forceNoThrowNoGC({ stbtt_GetFontVMetrics(&font.font, ascent, descent, lineGap); });
10094 }
10095 
10096 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc {
10097   float res = void;
10098   forceNoThrowNoGC({ res = stbtt_ScaleForPixelHeight(&font.font, size); });
10099   return res;
10100 }
10101 
10102 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc {
10103   int res;
10104   forceNoThrowNoGC({ res = stbtt_FindGlyphIndex(&font.font, codepoint); });
10105   return res;
10106 }
10107 
10108 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 {
10109   forceNoThrowNoGC({ stbtt_GetGlyphHMetrics(&font.font, glyph, advance, lsb); });
10110   forceNoThrowNoGC({ stbtt_GetGlyphBitmapBox(&font.font, glyph, scale, scale, x0, y0, x1, y1); });
10111   return 1;
10112 }
10113 
10114 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc {
10115   forceNoThrowNoGC({ stbtt_MakeGlyphBitmap(&font.font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); });
10116 }
10117 
10118 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc {
10119   // FUnits -> pixels: pointSize * resolution / (72 points per inch * units_per_em)
10120   // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#converting
10121   float res = void;
10122   forceNoThrowNoGC({
10123     res = stbtt_GetGlyphKernAdvance(&font.font, glyph1, glyph2);
10124     res *= stbtt_ScaleForPixelHeight(&font.font, size);
10125   });
10126   /*
10127   if (res != 0) {
10128     { 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)); }
10129   }
10130   */
10131   //k8: dunno if this is right; i guess it isn't but...
10132   return res;
10133 }
10134 
10135 // old arsd.ttf sux! ;-)
10136 static if (is(typeof(STBTT_vcubic))) {
10137 
10138 static struct OutlinerData {
10139   @disable this (this);
10140   void opAssign() (const scope auto ref OutlinerData a) { static assert(0, "no copies!"); }
10141   NVGPathOutline.DataStore* ol;
10142 nothrow @trusted @nogc:
10143   static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; }
10144   static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
10145 }
10146 
10147 
10148 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc {
10149   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10150 
10151   bool okflag = false;
10152 
10153   forceNoThrowNoGC({
10154     int x0, y0, x1, y1;
10155     if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10156       bounds[] = 0;
10157       return;
10158     }
10159 
10160     if (bounds.length > 0) bounds.ptr[0] = x0;
10161     if (bounds.length > 1) bounds.ptr[1] = -y1;
10162     if (bounds.length > 2) bounds.ptr[2] = x1;
10163     if (bounds.length > 3) bounds.ptr[3] = -y0;
10164 
10165     static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
10166 
10167     stbtt_vertex* verts = null;
10168     scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); }
10169     int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts);
10170     if (vcount < 1) return;
10171 
10172     foreach (const ref vt; verts[0..vcount]) {
10173       switch (vt.type) {
10174         case STBTT_vmove: vg.moveTo(vt.x, transy(vt.y)); break;
10175         case STBTT_vline: vg.lineTo(vt.x, transy(vt.y)); break;
10176         case STBTT_vcurve: vg.quadTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy)); break;
10177         case STBTT_vcubic: vg.bezierTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy), vt.cx1, transy(vt.cy1)); break;
10178         default:
10179       }
10180     }
10181 
10182     okflag = true;
10183   });
10184 
10185   return okflag;
10186 }
10187 
10188 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
10189   bool okflag = false;
10190 
10191   forceNoThrowNoGC({
10192     int x0, y0, x1, y1;
10193 
10194     if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10195       ol.bounds[] = 0;
10196       return;
10197     }
10198 
10199     ol.bounds.ptr[0] = x0;
10200     ol.bounds.ptr[1] = -y1;
10201     ol.bounds.ptr[2] = x1;
10202     ol.bounds.ptr[3] = -y0;
10203 
10204     stbtt_vertex* verts = null;
10205     scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); }
10206     int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts);
10207     if (vcount < 1) return;
10208 
10209     OutlinerData odata;
10210     odata.ol = ol;
10211 
10212     foreach (const ref vt; verts[0..vcount]) {
10213       switch (vt.type) {
10214         case STBTT_vmove:
10215           odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo);
10216           odata.ol.putArgs(odata.transx(vt.x));
10217           odata.ol.putArgs(odata.transy(vt.y));
10218           break;
10219         case STBTT_vline:
10220           odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo);
10221           odata.ol.putArgs(odata.transx(vt.x));
10222           odata.ol.putArgs(odata.transy(vt.y));
10223           break;
10224         case STBTT_vcurve:
10225           odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo);
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           break;
10231         case STBTT_vcubic:
10232           odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo);
10233           odata.ol.putArgs(odata.transx(vt.x));
10234           odata.ol.putArgs(odata.transy(vt.y));
10235           odata.ol.putArgs(odata.transx(vt.cx));
10236           odata.ol.putArgs(odata.transy(vt.cy));
10237           odata.ol.putArgs(odata.transx(vt.cx1));
10238           odata.ol.putArgs(odata.transy(vt.cy1));
10239           break;
10240         default:
10241       }
10242     }
10243 
10244     okflag = true;
10245   });
10246 
10247   return okflag;
10248 }
10249 
10250 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc {
10251   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10252 
10253   bool okflag = false;
10254 
10255   forceNoThrowNoGC({
10256     int x0, y0, x1, y1;
10257     if (stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10258       if (bounds.length > 0) bounds.ptr[0] = x0;
10259       if (bounds.length > 1) bounds.ptr[1] = -y1;
10260       if (bounds.length > 2) bounds.ptr[2] = x1;
10261       if (bounds.length > 3) bounds.ptr[3] = -y0;
10262       okflag = true;
10263     } else {
10264       bounds[] = 0;
10265     }
10266   });
10267 
10268   return okflag;
10269 }
10270 
10271 } // check for old stb_ttf
10272 
10273 
10274 } // version
10275 
10276 
10277 // ////////////////////////////////////////////////////////////////////////// //
10278 private:
10279 enum FONS_SCRATCH_BUF_SIZE = 64000;
10280 enum FONS_HASH_LUT_SIZE = 256;
10281 enum FONS_INIT_FONTS = 4;
10282 enum FONS_INIT_GLYPHS = 256;
10283 enum FONS_INIT_ATLAS_NODES = 256;
10284 enum FONS_VERTEX_COUNT = 1024;
10285 enum FONS_MAX_STATES = 20;
10286 enum FONS_MAX_FALLBACKS = 20;
10287 
10288 
10289 struct FONSglyph {
10290   uint codepoint;
10291   int index;
10292   int next;
10293   short size, blur;
10294   short x0, y0, x1, y1;
10295   short xadv, xoff, yoff;
10296 }
10297 
10298 // refcounted
10299 struct FONSfontData {
10300   ubyte* data;
10301   int dataSize;
10302   bool freeData;
10303   int rc;
10304 
10305   @disable this (this); // no copies
10306   void opAssign() (const scope auto ref FONSfontData a) { static assert(0, "no copies!"); }
10307 }
10308 
10309 // won't set rc to 1
10310 FONSfontData* fons__createFontData (ubyte* adata, int asize, bool afree) nothrow @trusted @nogc {
10311   import core.stdc.stdlib : malloc;
10312   assert(adata !is null);
10313   assert(asize > 0);
10314   auto res = cast(FONSfontData*)malloc(FONSfontData.sizeof);
10315   if (res is null) assert(0, "FONS: out of memory");
10316   res.data = adata;
10317   res.dataSize = asize;
10318   res.freeData = afree;
10319   res.rc = 0;
10320   return res;
10321 }
10322 
10323 void incref (FONSfontData* fd) pure nothrow @trusted @nogc {
10324   pragma(inline, true);
10325   if (fd !is null) ++fd.rc;
10326 }
10327 
10328 void decref (ref FONSfontData* fd) nothrow @trusted @nogc {
10329   if (fd !is null) {
10330     if (--fd.rc == 0) {
10331       import core.stdc.stdlib : free;
10332       if (fd.freeData && fd.data !is null) {
10333         free(fd.data);
10334         fd.data = null;
10335       }
10336       free(fd);
10337       fd = null;
10338     }
10339   }
10340 }
10341 
10342 // as creating and destroying fonts is a rare operation, malloc some data
10343 struct FONSfont {
10344   FONSttFontImpl font;
10345   char* name; // malloced, strz, always lowercase
10346   uint namelen;
10347   uint namehash;
10348   char* path; // malloced, strz
10349   FONSfontData* fdata;
10350   float ascender;
10351   float descender;
10352   float lineh;
10353   FONSglyph* glyphs;
10354   int cglyphs;
10355   int nglyphs;
10356   int[FONS_HASH_LUT_SIZE] lut;
10357   int[FONS_MAX_FALLBACKS] fallbacks;
10358   int nfallbacks;
10359 
10360   @disable this (this);
10361   void opAssign() (const scope auto ref FONSfont a) { static assert(0, "no copies"); }
10362 
10363   static uint djbhash (const(void)[] s) pure nothrow @safe @nogc {
10364     uint hash = 5381;
10365     foreach (ubyte b; cast(const(ubyte)[])s) {
10366       if (b >= 'A' && b <= 'Z') b += 32; // poor man's tolower
10367       hash = ((hash<<5)+hash)+b;
10368     }
10369     return hash;
10370   }
10371 
10372   // except glyphs
10373   void freeMemory () nothrow @trusted @nogc {
10374     import core.stdc.stdlib : free;
10375     if (name !is null) { free(name); name = null; }
10376     namelen = namehash = 0;
10377     if (path !is null) { free(path); path = null; }
10378     fdata.decref();
10379   }
10380 
10381   // this also calcs name hash
10382   void setName (const(char)[] aname) nothrow @trusted @nogc {
10383     //{ import core.stdc.stdio; printf("setname: [%.*s]\n", cast(uint)aname.length, aname.ptr); }
10384     import core.stdc.stdlib : realloc;
10385     if (aname.length > int.max/32) assert(0, "FONS: invalid font name");
10386     namelen = cast(uint)aname.length;
10387     name = cast(char*)realloc(name, namelen+1);
10388     if (name is null) assert(0, "FONS: out of memory");
10389     if (aname.length) name[0..aname.length] = aname[];
10390     name[namelen] = 0;
10391     // lowercase it
10392     foreach (ref char ch; name[0..namelen]) if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
10393     namehash = djbhash(name[0..namelen]);
10394     //{ import core.stdc.stdio; printf("  [%s] [%.*s] [0x%08x]\n", name, namelen, name, namehash); }
10395   }
10396 
10397   void setPath (const(char)[] apath) nothrow @trusted @nogc {
10398     import core.stdc.stdlib : realloc;
10399     if (apath.length > int.max/32) assert(0, "FONS: invalid font path");
10400     path = cast(char*)realloc(path, apath.length+1);
10401     if (path is null) assert(0, "FONS: out of memory");
10402     if (apath.length) path[0..apath.length] = apath[];
10403     path[apath.length] = 0;
10404   }
10405 
10406   // this won't check hash
10407   bool nameEqu (const(char)[] aname) const pure nothrow @trusted @nogc {
10408     //{ import core.stdc.stdio; printf("nameEqu: aname=[%.*s]; namelen=%u; aslen=%u\n", cast(uint)aname.length, aname.ptr, namelen, cast(uint)aname.length); }
10409     if (namelen != aname.length) return false;
10410     const(char)* ns = name;
10411     // name part
10412     foreach (char ch; aname) {
10413       if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
10414       if (ch != *ns++) return false;
10415     }
10416     // done (length was checked earlier)
10417     return true;
10418   }
10419 
10420   void clear () nothrow @trusted @nogc {
10421     import core.stdc.stdlib : free;
10422     import core.stdc.string : memset;
10423     if (glyphs !is null) free(glyphs);
10424     freeMemory();
10425     memset(&this, 0, this.sizeof);
10426   }
10427 
10428   FONSglyph* allocGlyph () nothrow @trusted @nogc {
10429     if (nglyphs+1 > cglyphs) {
10430       import core.stdc.stdlib : realloc;
10431       cglyphs = (cglyphs == 0 ? 8 : cglyphs*2);
10432       glyphs = cast(FONSglyph*)realloc(glyphs, FONSglyph.sizeof*cglyphs);
10433       if (glyphs is null) assert(0, "FontStash: out of memory");
10434     }
10435     ++nglyphs;
10436     return &glyphs[nglyphs-1];
10437   }
10438 }
10439 
10440 void kill (ref FONSfont* font) nothrow @trusted @nogc {
10441   if (font !is null) {
10442     import core.stdc.stdlib : free;
10443     font.clear();
10444     free(font);
10445     font = null;
10446   }
10447 }
10448 
10449 
10450 // ////////////////////////////////////////////////////////////////////////// //
10451 struct FONSstate {
10452   int font;
10453   NVGTextAlign talign;
10454   float size = 0;
10455   float blur = 0;
10456   float spacing = 0;
10457 }
10458 
10459 
10460 // ////////////////////////////////////////////////////////////////////////// //
10461 // atlas based on Skyline Bin Packer by Jukka Jylänki
10462 alias FONSAtlas = FONSatlasInternal*;
10463 
10464 struct FONSatlasInternal {
10465   static struct Node {
10466     short x, y, width;
10467   }
10468 
10469   int width, height;
10470   Node* nodes;
10471   int nnodes;
10472   int cnodes;
10473 
10474   @disable this (this);
10475   void opAssign() (const scope auto ref FONSatlasInternal a) { static assert(0, "no copies"); }
10476 
10477 nothrow @trusted @nogc:
10478   static FONSAtlas create (int w, int h, int nnodes) {
10479     import core.stdc.stdlib : malloc;
10480     import core.stdc.string : memset;
10481 
10482     FONSAtlas atlas = cast(FONSAtlas)malloc(FONSatlasInternal.sizeof);
10483     if (atlas is null) assert(0, "FontStash: out of memory");
10484     memset(atlas, 0, FONSatlasInternal.sizeof);
10485 
10486     atlas.width = w;
10487     atlas.height = h;
10488 
10489     // allocate space for skyline nodes
10490     atlas.nodes = cast(Node*)malloc(Node.sizeof*nnodes);
10491     if (atlas.nodes is null) assert(0, "FontStash: out of memory");
10492     memset(atlas.nodes, 0, Node.sizeof*nnodes);
10493     atlas.nnodes = 0;
10494     atlas.cnodes = nnodes;
10495 
10496     // init root node
10497     atlas.nodes[0].x = 0;
10498     atlas.nodes[0].y = 0;
10499     atlas.nodes[0].width = cast(short)w;
10500     ++atlas.nnodes;
10501 
10502     return atlas;
10503   }
10504 
10505   void clear () {
10506     import core.stdc.stdlib : free;
10507     import core.stdc.string : memset;
10508 
10509     if (nodes !is null) free(nodes);
10510     memset(&this, 0, this.sizeof);
10511   }
10512 
10513   void insertNode (int idx, int x, int y, int w) {
10514     if (nnodes+1 > cnodes) {
10515       import core.stdc.stdlib : realloc;
10516       cnodes = (cnodes == 0 ? 8 : cnodes*2);
10517       nodes = cast(Node*)realloc(nodes, Node.sizeof*cnodes);
10518       if (nodes is null) assert(0, "FontStash: out of memory");
10519     }
10520     for (int i = nnodes; i > idx; --i) nodes[i] = nodes[i-1];
10521     nodes[idx].x = cast(short)x;
10522     nodes[idx].y = cast(short)y;
10523     nodes[idx].width = cast(short)w;
10524     ++nnodes;
10525   }
10526 
10527   void removeNode (int idx) {
10528     if (nnodes == 0) return;
10529     foreach (immutable int i; idx+1..nnodes) nodes[i-1] = nodes[i];
10530     --nnodes;
10531   }
10532 
10533   // insert node for empty space
10534   void expand (int w, int h) {
10535     if (w > width) insertNode(nnodes, width, 0, w-width);
10536     width = w;
10537     height = h;
10538   }
10539 
10540   void reset (int w, int h) {
10541     width = w;
10542     height = h;
10543     nnodes = 0;
10544     // init root node
10545     nodes[0].x = 0;
10546     nodes[0].y = 0;
10547     nodes[0].width = cast(short)w;
10548     ++nnodes;
10549   }
10550 
10551   void addSkylineLevel (int idx, int x, int y, int w, int h) {
10552     insertNode(idx, x, y+h, w);
10553 
10554     // delete skyline segments that fall under the shadow of the new segment
10555     for (int i = idx+1; i < nnodes; ++i) {
10556       if (nodes[i].x < nodes[i-1].x+nodes[i-1].width) {
10557         int shrink = nodes[i-1].x+nodes[i-1].width-nodes[i].x;
10558         nodes[i].x += cast(short)shrink;
10559         nodes[i].width -= cast(short)shrink;
10560         if (nodes[i].width <= 0) {
10561           removeNode(i);
10562           --i;
10563         } else {
10564           break;
10565         }
10566       } else {
10567         break;
10568       }
10569     }
10570 
10571     // Merge same height skyline segments that are next to each other
10572     for (int i = 0; i < nnodes-1; ++i) {
10573       if (nodes[i].y == nodes[i+1].y) {
10574         nodes[i].width += nodes[i+1].width;
10575         removeNode(i+1);
10576         --i;
10577       }
10578     }
10579   }
10580 
10581   // checks if there is enough space at the location of skyline span 'i',
10582   // and return the max height of all skyline spans under that at that location,
10583   // (think tetris block being dropped at that position); or -1 if no space found
10584   int rectFits (int i, int w, int h) {
10585     int x = nodes[i].x;
10586     int y = nodes[i].y;
10587     if (x+w > width) return -1;
10588     int spaceLeft = w;
10589     while (spaceLeft > 0) {
10590       if (i == nnodes) return -1;
10591       y = nvg__max(y, nodes[i].y);
10592       if (y+h > height) return -1;
10593       spaceLeft -= nodes[i].width;
10594       ++i;
10595     }
10596     return y;
10597   }
10598 
10599   bool addRect (int rw, int rh, int* rx, int* ry) {
10600     int besth = height, bestw = width, besti = -1;
10601     int bestx = -1, besty = -1;
10602 
10603     // Bottom left fit heuristic.
10604     for (int i = 0; i < nnodes; ++i) {
10605       int y = rectFits(i, rw, rh);
10606       if (y != -1) {
10607         if (y+rh < besth || (y+rh == besth && nodes[i].width < bestw)) {
10608           besti = i;
10609           bestw = nodes[i].width;
10610           besth = y+rh;
10611           bestx = nodes[i].x;
10612           besty = y;
10613         }
10614       }
10615     }
10616 
10617     if (besti == -1) return false;
10618 
10619     // perform the actual packing
10620     addSkylineLevel(besti, bestx, besty, rw, rh);
10621 
10622     *rx = bestx;
10623     *ry = besty;
10624 
10625     return true;
10626   }
10627 }
10628 
10629 void kill (ref FONSAtlas atlas) nothrow @trusted @nogc {
10630   if (atlas !is null) {
10631     import core.stdc.stdlib : free;
10632     atlas.clear();
10633     free(atlas);
10634     atlas = null;
10635   }
10636 }
10637 
10638 
10639 // ////////////////////////////////////////////////////////////////////////// //
10640 /// FontStash context (internal definition). Don't use it derectly, it was made public only to generate documentation.
10641 /// Group: font_stash
10642 public struct FONScontextInternal {
10643 private:
10644   FONSParams params;
10645   float itw, ith;
10646   ubyte* texData;
10647   int[4] dirtyRect;
10648   FONSfont** fonts; // actually, a simple hash table; can't grow yet
10649   int cfonts; // allocated
10650   int nfonts; // used (so we can track hash table stats)
10651   int* hashidx; // [hsize] items; holds indicies in [fonts] array
10652   int hused, hsize;// used items and total items in [hashidx]
10653   FONSAtlas atlas;
10654   ubyte* scratch;
10655   int nscratch;
10656   FONSstate[FONS_MAX_STATES] states;
10657   int nstates;
10658 
10659   void delegate (FONSError error, int val) nothrow @trusted @nogc handleError;
10660 
10661   @disable this (this);
10662   void opAssign() (const scope auto ref FONScontextInternal ctx) { static assert(0, "FONS copying is not allowed"); }
10663 
10664 private:
10665   static bool strequci (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
10666     if (s0.length != s1.length) return false;
10667     const(char)* sp0 = s0.ptr;
10668     const(char)* sp1 = s1.ptr;
10669     foreach (immutable _; 0..s0.length) {
10670       char c0 = *sp0++;
10671       char c1 = *sp1++;
10672       if (c0 != c1) {
10673         if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man tolower
10674         if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man tolower
10675         if (c0 != c1) return false;
10676       }
10677     }
10678     return true;
10679   }
10680 
10681   inout(FONSstate)* getState () inout pure nothrow @trusted @nogc return {
10682     pragma(inline, true);
10683     return cast(inout)(&states[(nstates > 0 ? nstates-1 : 0)]);
10684   }
10685 
10686   // simple linear probing; returns [FONS_INVALID] if not found
10687   int findNameInHash (const(char)[] name) const pure nothrow @trusted @nogc {
10688     if (nfonts == 0) return FONS_INVALID;
10689     auto nhash = FONSfont.djbhash(name);
10690     //{ import core.stdc.stdio; printf("findinhash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10691     auto res = nhash%hsize;
10692     // hash will never be 100% full, so this loop is safe
10693     for (;;) {
10694       int idx = hashidx[res];
10695       if (idx == -1) break;
10696       auto font = fonts[idx];
10697       if (font is null) assert(0, "FONS internal error");
10698       if (font.namehash == nhash && font.nameEqu(name)) return idx;
10699       //{ import core.stdc.stdio; printf("findinhash chained: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10700       res = (res+1)%hsize;
10701     }
10702     return FONS_INVALID;
10703   }
10704 
10705   // should be called $(B before) freeing `fonts[fidx]`
10706   void removeIndexFromHash (int fidx) nothrow @trusted @nogc {
10707     if (fidx < 0 || fidx >= nfonts) assert(0, "FONS internal error");
10708     if (fonts[fidx] is null) assert(0, "FONS internal error");
10709     if (hused != nfonts) assert(0, "FONS internal error");
10710     auto nhash = fonts[fidx].namehash;
10711     auto res = nhash%hsize;
10712     // hash will never be 100% full, so this loop is safe
10713     for (;;) {
10714       int idx = hashidx[res];
10715       if (idx == -1) assert(0, "FONS INTERNAL ERROR");
10716       if (idx == fidx) {
10717         // i found her! copy rest here
10718         int nidx = (res+1)%hsize;
10719         for (;;) {
10720           if ((hashidx[res] = hashidx[nidx]) == -1) break; // so it will copy `-1` too
10721           res = nidx;
10722           nidx = (nidx+1)%hsize;
10723         }
10724         return;
10725       }
10726       res = (res+1)%hsize;
10727     }
10728   }
10729 
10730   // add font with the given index to hash
10731   // prerequisite: font should not exists in hash
10732   void addIndexToHash (int idx) nothrow @trusted @nogc {
10733     if (idx < 0 || idx >= nfonts) assert(0, "FONS internal error");
10734     if (fonts[idx] is null) assert(0, "FONS internal error");
10735     import core.stdc.stdlib : realloc;
10736     auto nhash = fonts[idx].namehash;
10737     //{ import core.stdc.stdio; printf("addtohash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10738     // allocate new hash table if there was none
10739     if (hsize == 0) {
10740       enum InitSize = 256;
10741       auto newlist = cast(int*)realloc(null, InitSize*hashidx[0].sizeof);
10742       if (newlist is null) assert(0, "FONS: out of memory");
10743       newlist[0..InitSize] = -1;
10744       hsize = InitSize;
10745       hused = 0;
10746       hashidx = newlist;
10747     }
10748     int res = cast(int)(nhash%hsize);
10749     // need to rehash? we want our hash table 50% full at max
10750     if (hashidx[res] != -1 && hused >= hsize/2) {
10751       uint nsz = hsize*2;
10752       if (nsz > 1024*1024) assert(0, "FONS: out of memory for fonts");
10753       auto newlist = cast(int*)realloc(fonts, nsz*hashidx[0].sizeof);
10754       if (newlist is null) assert(0, "FONS: out of memory");
10755       newlist[0..nsz] = -1;
10756       hused = 0;
10757       // rehash
10758       foreach (immutable fidx, FONSfont* ff; fonts[0..nfonts]) {
10759         if (ff is null) continue;
10760         // find slot for this font (guaranteed to have one)
10761         uint newslot = ff.namehash%nsz;
10762         while (newlist[newslot] != -1) newslot = (newslot+1)%nsz;
10763         newlist[newslot] = cast(int)fidx;
10764         ++hused;
10765       }
10766       hsize = nsz;
10767       hashidx = newlist;
10768       // we added everything, including [idx], so nothing more to do here
10769     } else {
10770       // find slot (guaranteed to have one)
10771       while (hashidx[res] != -1) res = (res+1)%hsize;
10772       // i found her!
10773       hashidx[res] = idx;
10774       ++hused;
10775     }
10776   }
10777 
10778   void addWhiteRect (int w, int h) nothrow @trusted @nogc {
10779     int gx, gy;
10780     ubyte* dst;
10781 
10782     if (!atlas.addRect(w, h, &gx, &gy)) return;
10783 
10784     // Rasterize
10785     dst = &texData[gx+gy*params.width];
10786     foreach (int y; 0..h) {
10787       foreach (int x; 0..w) {
10788         dst[x] = 0xff;
10789       }
10790       dst += params.width;
10791     }
10792 
10793     dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], gx);
10794     dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], gy);
10795     dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], gx+w);
10796     dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], gy+h);
10797   }
10798 
10799   // returns fid, not hash slot
10800   int allocFontAt (int atidx) nothrow @trusted @nogc {
10801     if (atidx >= 0 && atidx >= nfonts) assert(0, "internal NanoVega fontstash error");
10802 
10803     if (atidx < 0) {
10804       if (nfonts >= cfonts) {
10805         import core.stdc.stdlib : realloc;
10806         import core.stdc.string : memset;
10807         assert(nfonts == cfonts);
10808         int newsz = cfonts+64;
10809         if (newsz > 65535) assert(0, "FONS: too many fonts");
10810         auto newlist = cast(FONSfont**)realloc(fonts, newsz*(FONSfont*).sizeof);
10811         if (newlist is null) assert(0, "FONS: out of memory");
10812         memset(newlist+cfonts, 0, (newsz-cfonts)*(FONSfont*).sizeof);
10813         fonts = newlist;
10814         cfonts = newsz;
10815       }
10816       assert(nfonts < cfonts);
10817     }
10818 
10819     FONSfont* font = cast(FONSfont*)malloc(FONSfont.sizeof);
10820     if (font is null) assert(0, "FONS: out of memory");
10821     memset(font, 0, FONSfont.sizeof);
10822 
10823     font.glyphs = cast(FONSglyph*)malloc(FONSglyph.sizeof*FONS_INIT_GLYPHS);
10824     if (font.glyphs is null) assert(0, "FONS: out of memory");
10825     font.cglyphs = FONS_INIT_GLYPHS;
10826     font.nglyphs = 0;
10827 
10828     if (atidx < 0) {
10829       fonts[nfonts] = font;
10830       return nfonts++;
10831     } else {
10832       fonts[atidx] = font;
10833       return atidx;
10834     }
10835   }
10836 
10837   // 0: ooops
10838   int findGlyphForCP (FONSfont *font, dchar dch, FONSfont** renderfont) nothrow @trusted @nogc {
10839     if (renderfont !is null) *renderfont = font;
10840     if (font is null || font.fdata is null) return 0;
10841     auto g = fons__tt_getGlyphIndex(&font.font, cast(uint)dch);
10842     // try to find the glyph in fallback fonts
10843     if (g == 0) {
10844       foreach (immutable i; 0..font.nfallbacks) {
10845         FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]];
10846         if (fallbackFont !is null) {
10847           int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, cast(uint)dch);
10848           if (fallbackIndex != 0) {
10849             if (renderfont !is null) *renderfont = fallbackFont;
10850             return g;
10851           }
10852         }
10853       }
10854       // no char, try to find replacement one
10855       if (dch != 0xFFFD) {
10856         g = fons__tt_getGlyphIndex(&font.font, 0xFFFD);
10857         if (g == 0) {
10858           foreach (immutable i; 0..font.nfallbacks) {
10859             FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]];
10860             if (fallbackFont !is null) {
10861               int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, 0xFFFD);
10862               if (fallbackIndex != 0) {
10863                 if (renderfont !is null) *renderfont = fallbackFont;
10864                 return g;
10865               }
10866             }
10867           }
10868         }
10869       }
10870     }
10871     return g;
10872   }
10873 
10874   void clear () nothrow @trusted @nogc {
10875     import core.stdc.stdlib : free;
10876 
10877     if (params.renderDelete !is null) params.renderDelete(params.userPtr);
10878     foreach (immutable int i; 0..nfonts) fonts[i].kill();
10879 
10880     if (atlas !is null) atlas.kill();
10881     if (fonts !is null) free(fonts);
10882     if (texData !is null) free(texData);
10883     if (scratch !is null) free(scratch);
10884     if (hashidx !is null) free(hashidx);
10885   }
10886 
10887   // add font from another fontstash
10888   int addCookedFont (FONSfont* font) nothrow @trusted @nogc {
10889     if (font is null || font.fdata is null) return FONS_INVALID;
10890     font.fdata.incref();
10891     auto res = addFontWithData(font.name[0..font.namelen], font.fdata, !font.font.mono);
10892     if (res == FONS_INVALID) font.fdata.decref(); // oops
10893     return res;
10894   }
10895 
10896   // fdata refcount must be already increased; it won't be changed
10897   int addFontWithData (const(char)[] name, FONSfontData* fdata, bool defAA) nothrow @trusted @nogc {
10898     int i, ascent, descent, fh, lineGap;
10899 
10900     if (name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID;
10901     if (name.length > 32767) return FONS_INVALID;
10902     if (fdata is null) return FONS_INVALID;
10903 
10904     // find a font with the given name
10905     int newidx;
10906     FONSfont* oldfont = null;
10907     int oldidx = findNameInHash(name);
10908     if (oldidx != FONS_INVALID) {
10909       // replacement font
10910       oldfont = fonts[oldidx];
10911       newidx = oldidx;
10912     } else {
10913       // new font, allocate new bucket
10914       newidx = -1;
10915     }
10916 
10917     newidx = allocFontAt(newidx);
10918     FONSfont* font = fonts[newidx];
10919     font.setName(name);
10920     font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; // init hash lookup
10921     font.fdata = fdata; // set the font data (don't change reference count)
10922     fons__tt_setMono(&this, &font.font, !defAA);
10923 
10924     // init font
10925     nscratch = 0;
10926     if (!fons__tt_loadFont(&this, &font.font, fdata.data, fdata.dataSize)) {
10927       // we promised to not free data on error, so just clear the data store (it will be freed by the caller)
10928       font.fdata = null;
10929       font.kill();
10930       if (oldidx != FONS_INVALID) {
10931         assert(oldidx == newidx);
10932         fonts[oldidx] = oldfont;
10933       } else {
10934         assert(newidx == nfonts-1);
10935         fonts[newidx] = null;
10936         --nfonts;
10937       }
10938       return FONS_INVALID;
10939     } else {
10940       // free old font data, if any
10941       if (oldfont !is null) oldfont.kill();
10942     }
10943 
10944     // add font to name hash
10945     if (oldidx == FONS_INVALID) addIndexToHash(newidx);
10946 
10947     // store normalized line height
10948     // the real line height is got by multiplying the lineh by font size
10949     fons__tt_getFontVMetrics(&font.font, &ascent, &descent, &lineGap);
10950     fh = ascent-descent;
10951     font.ascender = cast(float)ascent/cast(float)fh;
10952     font.descender = cast(float)descent/cast(float)fh;
10953     font.lineh = cast(float)(fh+lineGap)/cast(float)fh;
10954 
10955     //{ import core.stdc.stdio; printf("created font [%.*s] (idx=%d)...\n", cast(uint)name.length, name.ptr, idx); }
10956     return newidx;
10957   }
10958 
10959   // isize: size*10
10960   float getVertAlign (FONSfont* font, NVGTextAlign talign, short isize) pure nothrow @trusted @nogc {
10961     if (params.isZeroTopLeft) {
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     } else {
10969       final switch (talign.vertical) {
10970         case NVGTextAlign.V.Top: return -font.ascender*cast(float)isize/10.0f;
10971         case NVGTextAlign.V.Middle: return -(font.ascender+font.descender)/2.0f*cast(float)isize/10.0f;
10972         case NVGTextAlign.V.Baseline: return 0.0f;
10973         case NVGTextAlign.V.Bottom: return -font.descender*cast(float)isize/10.0f;
10974       }
10975     }
10976     assert(0);
10977   }
10978 
10979 public:
10980   /** Create new FontStash context. It can be destroyed with `fs.kill()` later.
10981    *
10982    * Note that if you don't plan to rasterize glyphs (i.e. you will use created
10983    * FontStash only to measure text), you can simply pass `FONSParams.init`).
10984    */
10985   static FONSContext create() (const scope auto ref FONSParams params) nothrow @trusted @nogc {
10986     import core.stdc.string : memcpy;
10987 
10988     FONSContext stash = null;
10989 
10990     // allocate memory for the font stash
10991     stash = cast(FONSContext)malloc(FONScontextInternal.sizeof);
10992     if (stash is null) goto error;
10993     memset(stash, 0, FONScontextInternal.sizeof);
10994 
10995     memcpy(&stash.params, &params, params.sizeof);
10996     if (stash.params.width < 1) stash.params.width = 32;
10997     if (stash.params.height < 1) stash.params.height = 32;
10998 
10999     // allocate scratch buffer
11000     stash.scratch = cast(ubyte*)malloc(FONS_SCRATCH_BUF_SIZE);
11001     if (stash.scratch is null) goto error;
11002 
11003     // initialize implementation library
11004     if (!fons__tt_init(stash)) goto error;
11005 
11006     if (stash.params.renderCreate !is null) {
11007       if (!stash.params.renderCreate(stash.params.userPtr, stash.params.width, stash.params.height)) goto error;
11008     }
11009 
11010     stash.atlas = FONSAtlas.create(stash.params.width, stash.params.height, FONS_INIT_ATLAS_NODES);
11011     if (stash.atlas is null) goto error;
11012 
11013     // don't allocate space for fonts: hash manager will do that for us later
11014     //stash.cfonts = 0;
11015     //stash.nfonts = 0;
11016 
11017     // create texture for the cache
11018     stash.itw = 1.0f/stash.params.width;
11019     stash.ith = 1.0f/stash.params.height;
11020     stash.texData = cast(ubyte*)malloc(stash.params.width*stash.params.height);
11021     if (stash.texData is null) goto error;
11022     memset(stash.texData, 0, stash.params.width*stash.params.height);
11023 
11024     stash.dirtyRect.ptr[0] = stash.params.width;
11025     stash.dirtyRect.ptr[1] = stash.params.height;
11026     stash.dirtyRect.ptr[2] = 0;
11027     stash.dirtyRect.ptr[3] = 0;
11028 
11029     // add white rect at 0, 0 for debug drawing
11030     stash.addWhiteRect(2, 2);
11031 
11032     stash.pushState();
11033     stash.clearState();
11034 
11035     return stash;
11036 
11037   error:
11038     stash.kill();
11039     return null;
11040   }
11041 
11042 public:
11043   /// Add fallback font (FontStash will try to find missing glyph in all fallback fonts before giving up).
11044   bool addFallbackFont (int base, int fallback) nothrow @trusted @nogc {
11045     FONSfont* baseFont = fonts[base];
11046     if (baseFont !is null && baseFont.nfallbacks < FONS_MAX_FALLBACKS) {
11047       baseFont.fallbacks.ptr[baseFont.nfallbacks++] = fallback;
11048       return true;
11049     }
11050     return false;
11051   }
11052 
11053   @property void size (float size) nothrow @trusted @nogc { pragma(inline, true); getState.size = size; } /// Set current font size.
11054   @property float size () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.size; } /// Get current font size.
11055 
11056   @property void spacing (float spacing) nothrow @trusted @nogc { pragma(inline, true); getState.spacing = spacing; } /// Set current letter spacing.
11057   @property float spacing () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.spacing; } /// Get current letter spacing.
11058 
11059   @property void blur (float blur) nothrow @trusted @nogc { pragma(inline, true); getState.blur = blur; } /// Set current letter blur.
11060   @property float blur () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.blur; } /// Get current letter blur.
11061 
11062   @property void textAlign (NVGTextAlign talign) nothrow @trusted @nogc { pragma(inline, true); getState.talign = talign; } /// Set current text align.
11063   @property NVGTextAlign textAlign () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.talign; } /// Get current text align.
11064 
11065   @property void fontId (int font) nothrow @trusted @nogc { pragma(inline, true); getState.font = font; } /// Set current font id.
11066   @property int fontId () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.font; } /// Get current font id.
11067 
11068   @property void fontId (const(char)[] name) nothrow @trusted @nogc { pragma(inline, true); getState.font = getFontByName(name); } /// Set current font using its name.
11069 
11070   /// Check if FontStash has a font with the given name loaded.
11071   bool hasFont (const(char)[] name) const pure nothrow @trusted @nogc { pragma(inline, true); return (getFontByName(name) >= 0); }
11072 
11073   /// Get AA for the current font, or for the specified font.
11074   bool getFontAA (int font=-1) nothrow @trusted @nogc {
11075     FONSstate* state = getState;
11076     if (font < 0) font = state.font;
11077     if (font < 0 || font >= nfonts) return false;
11078     FONSfont* f = fonts[font];
11079     return (f !is null ? !f.font.mono : false);
11080   }
11081 
11082   /// Push current state. Returns `false` if state stack overflowed.
11083   bool pushState () nothrow @trusted @nogc {
11084     if (nstates >= FONS_MAX_STATES) {
11085       if (handleError !is null) handleError(FONSError.StatesOverflow, 0);
11086       return false;
11087     }
11088     if (nstates > 0) {
11089       import core.stdc.string : memcpy;
11090       memcpy(&states[nstates], &states[nstates-1], FONSstate.sizeof);
11091     }
11092     ++nstates;
11093     return true;
11094   }
11095 
11096   /// Pop current state. Returns `false` if state stack underflowed.
11097   bool popState () nothrow @trusted @nogc {
11098     if (nstates <= 1) {
11099       if (handleError !is null) handleError(FONSError.StatesUnderflow, 0);
11100       return false;
11101     }
11102     --nstates;
11103     return true;
11104   }
11105 
11106   /// Clear current state (i.e. set it to some sane defaults).
11107   void clearState () nothrow @trusted @nogc {
11108     FONSstate* state = getState;
11109     state.size = 12.0f;
11110     state.font = 0;
11111     state.blur = 0;
11112     state.spacing = 0;
11113     state.talign.reset;
11114   }
11115 
11116   private enum NoAlias = ":noaa";
11117 
11118   /** Add font to FontStash.
11119    *
11120    * Load scalable font from disk, and add it to FontStash. If you will try to load a font
11121    * with same name and path several times, FontStash will load it only once. Also, you can
11122    * load new disk font for any existing logical font.
11123    *
11124    * Params:
11125    *   name = logical font name, that will be used to select this font later.
11126    *   path = path to disk file with your font.
11127    *   defAA = should FontStash use antialiased font rasterizer?
11128    *
11129    * Returns:
11130    *   font id or [FONS_INVALID].
11131    */
11132   int addFont (const(char)[] name, const(char)[] path, bool defAA=false) nothrow @trusted {
11133     if (path.length == 0 || name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID;
11134     if (path.length > 32768) return FONS_INVALID; // arbitrary limit
11135 
11136     // if font path ends with ":noaa", turn off antialiasing
11137     if (path.length >= NoAlias.length && strequci(path[$-NoAlias.length..$], NoAlias)) {
11138       path = path[0..$-NoAlias.length];
11139       if (path.length == 0) return FONS_INVALID;
11140       defAA = false;
11141     }
11142 
11143     // if font name ends with ":noaa", turn off antialiasing
11144     if (name.length > NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) {
11145       name = name[0..$-NoAlias.length];
11146       defAA = false;
11147     }
11148 
11149     // find a font with the given name
11150     int fidx = findNameInHash(name);
11151     //{ import core.stdc.stdio; printf("loading font '%.*s' [%s] (fidx=%d)...\n", cast(uint)path.length, path.ptr, fontnamebuf.ptr, fidx); }
11152 
11153     int loadFontFile (const(char)[] path) {
11154       // check if existing font (if any) has the same path
11155       if (fidx >= 0) {
11156         import core.stdc.string : strlen;
11157         auto plen = (fonts[fidx].path !is null ? strlen(fonts[fidx].path) : 0);
11158         version(Posix) {
11159           //{ 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); }
11160           if (plen == path.length && fonts[fidx].path[0..plen] == path) {
11161             //{ import core.stdc.stdio; printf("*** font [%.*s] already loaded from [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)plen, path.ptr); }
11162             // i found her!
11163             return fidx;
11164           }
11165         } else {
11166           if (plen == path.length && strequci(fonts[fidx].path[0..plen], path)) {
11167             // i found her!
11168             return fidx;
11169           }
11170         }
11171       }
11172       version(Windows) {
11173         // special shitdows check: this will reject fontconfig font names (but still allow things like "c:myfont")
11174         foreach (immutable char ch; path[(path.length >= 2 && path[1] == ':' ? 2 : 0)..$]) if (ch == ':') return FONS_INVALID;
11175       }
11176       // either no such font, or different path
11177       //{ import core.stdc.stdio; printf("trying font [%.*s] from file [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)path.length, path.ptr); }
11178       int xres = FONS_INVALID;
11179       try {
11180         import core.stdc.stdlib : free, malloc;
11181         static if (NanoVegaHasIVVFS) {
11182           auto fl = VFile(path);
11183           auto dataSize = fl.size;
11184           if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID;
11185           ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize);
11186           if (data is null) assert(0, "out of memory in NanoVega fontstash");
11187           scope(failure) free(data); // oops
11188           fl.rawReadExact(data[0..cast(uint)dataSize]);
11189           fl.close();
11190         } else {
11191           import core.stdc.stdio : FILE, fopen, fclose, fread, ftell, fseek;
11192           import std.internal.cstring : tempCString;
11193           auto fl = fopen(path.tempCString, "rb");
11194           if (fl is null) return FONS_INVALID;
11195           scope(exit) fclose(fl);
11196           if (fseek(fl, 0, 2/*SEEK_END*/) != 0) return FONS_INVALID;
11197           auto dataSize = ftell(fl);
11198           if (fseek(fl, 0, 0/*SEEK_SET*/) != 0) return FONS_INVALID;
11199           if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID;
11200           ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize);
11201           if (data is null) assert(0, "out of memory in NanoVega fontstash");
11202           scope(failure) free(data); // oops
11203           ubyte* dptr = data;
11204           auto left = cast(uint)dataSize;
11205           while (left > 0) {
11206             auto rd = fread(dptr, 1, left, fl);
11207             if (rd == 0) { free(data); return FONS_INVALID; } // unexpected EOF or reading error, it doesn't matter
11208             dptr += rd;
11209             left -= rd;
11210           }
11211         }
11212         scope(failure) free(data); // oops
11213         // create font data
11214         FONSfontData* fdata = fons__createFontData(data, cast(int)dataSize, true); // free data
11215         fdata.incref();
11216         xres = addFontWithData(name, fdata, defAA);
11217         if (xres == FONS_INVALID) {
11218           fdata.decref(); // this will free [data] and [fdata]
11219         } else {
11220           // remember path
11221           fonts[xres].setPath(path);
11222         }
11223       } catch (Exception e) {
11224         // oops; sorry
11225       }
11226       return xres;
11227     }
11228 
11229     // first try direct path
11230     auto res = loadFontFile(path);
11231     // if loading failed, try fontconfig (if fontconfig is available)
11232     static if (NanoVegaHasFontConfig) {
11233       if (res == FONS_INVALID && fontconfigAvailable) {
11234         // idiotic fontconfig NEVER fails; let's skip it if `path` looks like a path
11235         bool ok = true;
11236         if (path.length > 4 && (path[$-4..$] == ".ttf" || path[$-4..$] == ".ttc")) ok = false;
11237         if (ok) { foreach (immutable char ch; path) if (ch == '/') { ok = false; break; } }
11238         if (ok) {
11239           import std.internal.cstring : tempCString;
11240           FcPattern* pat = FcNameParse(path.tempCString);
11241           if (pat !is null) {
11242             scope(exit) FcPatternDestroy(pat);
11243             if (FcConfigSubstitute(null, pat, FcMatchPattern)) {
11244               FcDefaultSubstitute(pat);
11245               // find the font
11246               FcResult result;
11247               FcPattern* font = FcFontMatch(null, pat, &result);
11248               if (font !is null) {
11249                 scope(exit) FcPatternDestroy(font);
11250                 char* file = null;
11251                 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
11252                   if (file !is null && file[0]) {
11253                     import core.stdc.string : strlen;
11254                     res = loadFontFile(file[0..strlen(file)]);
11255                   }
11256                 }
11257               }
11258             }
11259           }
11260         }
11261       }
11262     }
11263     return res;
11264   }
11265 
11266   /** Add font to FontStash, using data from memory.
11267    *
11268    * And already loaded font to FontStash. You can replace existing logical fonts.
11269    * But note that you can't remove logical font by passing "empty" data.
11270    *
11271    * $(WARNING If [FONS_INVALID] returned, `data` won't be freed even if `freeData` is `true`.)
11272    *
11273    * Params:
11274    *   name = logical font name, that will be used to select this font later.
11275    *   data = font data.
11276    *   dataSize = font data size.
11277    *   freeData = should FontStash take ownership of the font data?
11278    *   defAA = should FontStash use antialiased font rasterizer?
11279    *
11280    * Returns:
11281    *   font id or [FONS_INVALID].
11282    */
11283   int addFontMem (const(char)[] name, ubyte* data, int dataSize, bool freeData, bool defAA=false) nothrow @trusted @nogc {
11284     if (data is null || dataSize < 16) return FONS_INVALID;
11285     FONSfontData* fdata = fons__createFontData(data, dataSize, freeData);
11286     fdata.incref();
11287     auto res = addFontWithData(name, fdata, defAA);
11288     if (res == FONS_INVALID) {
11289       // we promised to not free data on error
11290       fdata.freeData = false;
11291       fdata.decref(); // this will free [fdata]
11292     }
11293     return res;
11294   }
11295 
11296   /** Add fonts from another FontStash.
11297    *
11298    * This is more effective (and faster) than reloading fonts, because internally font data
11299    * is reference counted.
11300    */
11301   void addFontsFrom (FONSContext source) nothrow @trusted @nogc {
11302     if (source is null) return;
11303     foreach (FONSfont* font; source.fonts[0..source.nfonts]) {
11304       if (font !is null) {
11305         auto newidx = addCookedFont(font);
11306         FONSfont* newfont = fonts[newidx];
11307         assert(newfont !is null);
11308         assert(newfont.path is null);
11309         // copy path
11310         if (font.path !is null && font.path[0]) {
11311           import core.stdc.stdlib : malloc;
11312           import core.stdc.string : strcpy, strlen;
11313           newfont.path = cast(char*)malloc(strlen(font.path)+1);
11314           if (newfont.path is null) assert(0, "FONS: out of memory");
11315           strcpy(newfont.path, font.path);
11316         }
11317       }
11318     }
11319   }
11320 
11321   /// Returns logical font name corresponding to the given font id, or `null`.
11322   /// $(WARNING Copy returned name, as name buffer can be invalidated by next FontStash API call!)
11323   const(char)[] getNameById (int idx) const pure nothrow @trusted @nogc {
11324     if (idx < 0 || idx >= nfonts || fonts[idx] is null) return null;
11325     return fonts[idx].name[0..fonts[idx].namelen];
11326   }
11327 
11328   /// Returns font id corresponding to the given logical font name, or [FONS_INVALID].
11329   int getFontByName (const(char)[] name) const pure nothrow @trusted @nogc {
11330     //{ import core.stdc.stdio; printf("fonsGetFontByName: [%.*s]\n", cast(uint)name.length, name.ptr); }
11331     // remove ":noaa" suffix
11332     if (name.length >= NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) {
11333       name = name[0..$-NoAlias.length];
11334     }
11335     if (name.length == 0) return FONS_INVALID;
11336     return findNameInHash(name);
11337   }
11338 
11339   /** Measures the specified text string. Parameter bounds should be a float[4],
11340    * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax]
11341    * Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
11342    */
11343   float getTextBounds(T) (float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc if (isAnyCharType!T) {
11344     FONSstate* state = getState;
11345     uint codepoint;
11346     uint utf8state = 0;
11347     FONSQuad q;
11348     FONSglyph* glyph = null;
11349     int prevGlyphIndex = -1;
11350     short isize = cast(short)(state.size*10.0f);
11351     short iblur = cast(short)state.blur;
11352     FONSfont* font;
11353 
11354     if (state.font < 0 || state.font >= nfonts) return 0;
11355     font = fonts[state.font];
11356     if (font is null || font.fdata is null) return 0;
11357 
11358     float scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
11359 
11360     // Align vertically.
11361     y += getVertAlign(font, state.talign, isize);
11362 
11363     float minx = x, maxx = x;
11364     float miny = y, maxy = y;
11365     float startx = x;
11366 
11367     foreach (T ch; str) {
11368       static if (T.sizeof == 1) {
11369         //if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;
11370         mixin(DecUtfMixin!("utf8state", "codepoint", "(cast(ubyte)ch)"));
11371         if (utf8state) continue;
11372       } else {
11373         static if (T.sizeof == 4) {
11374           if (ch > dchar.max) ch = 0xFFFD;
11375         }
11376         codepoint = cast(uint)ch;
11377       }
11378       glyph = getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional);
11379       if (glyph !is null) {
11380         getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q);
11381         if (q.x0 < minx) minx = q.x0;
11382         if (q.x1 > maxx) maxx = q.x1;
11383         if (params.isZeroTopLeft) {
11384           if (q.y0 < miny) miny = q.y0;
11385           if (q.y1 > maxy) maxy = q.y1;
11386         } else {
11387           if (q.y1 < miny) miny = q.y1;
11388           if (q.y0 > maxy) maxy = q.y0;
11389         }
11390         prevGlyphIndex = glyph.index;
11391       } else {
11392         //{ import core.stdc.stdio; printf("NO GLYPH FOR 0x%04x\n", cast(uint)codepoint); }
11393         prevGlyphIndex = -1;
11394       }
11395     }
11396 
11397     float advance = x-startx;
11398     //{ import core.stdc.stdio; printf("***: x=%g; startx=%g; advance=%g\n", cast(double)x, cast(double)startx, cast(double)advance); }
11399 
11400     // Align horizontally
11401     if (state.talign.left) {
11402       // empty
11403     } else if (state.talign.right) {
11404       minx -= advance;
11405       maxx -= advance;
11406     } else if (state.talign.center) {
11407       minx -= advance*0.5f;
11408       maxx -= advance*0.5f;
11409     }
11410 
11411     if (bounds.length) {
11412       if (bounds.length > 0) bounds.ptr[0] = minx;
11413       if (bounds.length > 1) bounds.ptr[1] = miny;
11414       if (bounds.length > 2) bounds.ptr[2] = maxx;
11415       if (bounds.length > 3) bounds.ptr[3] = maxy;
11416     }
11417 
11418     return advance;
11419   }
11420 
11421   /// Returns various font metrics. Any argument can be `null` if you aren't interested in its value.
11422   void getVertMetrics (float* ascender, float* descender, float* lineh) nothrow @trusted @nogc {
11423     FONSstate* state = getState;
11424     if (state.font < 0 || state.font >= nfonts) {
11425       if (ascender !is null) *ascender = 0;
11426       if (descender !is null) *descender = 0;
11427       if (lineh !is null) *lineh = 0;
11428     } else {
11429       FONSfont* font = fonts[state.font];
11430       if (font is null || font.fdata is null) {
11431         if (ascender !is null) *ascender = 0;
11432         if (descender !is null) *descender = 0;
11433         if (lineh !is null) *lineh = 0;
11434       } else {
11435         short isize = cast(short)(state.size*10.0f);
11436         if (ascender !is null) *ascender = font.ascender*isize/10.0f;
11437         if (descender !is null) *descender = font.descender*isize/10.0f;
11438         if (lineh !is null) *lineh = font.lineh*isize/10.0f;
11439       }
11440     }
11441   }
11442 
11443   /// Returns line bounds. Any argument can be `null` if you aren't interested in its value.
11444   void getLineBounds (float y, float* minyp, float* maxyp) nothrow @trusted @nogc {
11445     FONSfont* font;
11446     FONSstate* state = getState;
11447     short isize;
11448 
11449     if (minyp !is null) *minyp = 0;
11450     if (maxyp !is null) *maxyp = 0;
11451 
11452     if (state.font < 0 || state.font >= nfonts) return;
11453     font = fonts[state.font];
11454     isize = cast(short)(state.size*10.0f);
11455     if (font is null || font.fdata is null) return;
11456 
11457     y += getVertAlign(font, state.talign, isize);
11458 
11459     if (params.isZeroTopLeft) {
11460       immutable float miny = y-font.ascender*cast(float)isize/10.0f;
11461       immutable float maxy = miny+font.lineh*isize/10.0f;
11462       if (minyp !is null) *minyp = miny;
11463       if (maxyp !is null) *maxyp = maxy;
11464     } else {
11465       immutable float maxy = y+font.descender*cast(float)isize/10.0f;
11466       immutable float miny = maxy-font.lineh*isize/10.0f;
11467       if (minyp !is null) *minyp = miny;
11468       if (maxyp !is null) *maxyp = maxy;
11469     }
11470   }
11471 
11472   /// Returns font line height.
11473   float fontHeight () nothrow @trusted @nogc {
11474     float res = void;
11475     getVertMetrics(null, null, &res);
11476     return res;
11477   }
11478 
11479   /// Returns font ascender (positive).
11480   float fontAscender () nothrow @trusted @nogc {
11481     float res = void;
11482     getVertMetrics(&res, null, null);
11483     return res;
11484   }
11485 
11486   /// Returns font descender (negative).
11487   float fontDescender () nothrow @trusted @nogc {
11488     float res = void;
11489     getVertMetrics(null, &res, null);
11490     return res;
11491   }
11492 
11493   //TODO: document this
11494   const(ubyte)* getTextureData (int* width, int* height) nothrow @trusted @nogc {
11495     if (width !is null) *width = params.width;
11496     if (height !is null) *height = params.height;
11497     return texData;
11498   }
11499 
11500   //TODO: document this
11501   bool validateTexture (int* dirty) nothrow @trusted @nogc {
11502     if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) {
11503       dirty[0] = dirtyRect.ptr[0];
11504       dirty[1] = dirtyRect.ptr[1];
11505       dirty[2] = dirtyRect.ptr[2];
11506       dirty[3] = dirtyRect.ptr[3];
11507       // reset dirty rect
11508       dirtyRect.ptr[0] = params.width;
11509       dirtyRect.ptr[1] = params.height;
11510       dirtyRect.ptr[2] = 0;
11511       dirtyRect.ptr[3] = 0;
11512       return true;
11513     }
11514     return false;
11515   }
11516 
11517   //TODO: document this
11518   void errorCallback (void delegate (FONSError error, int val) nothrow @trusted @nogc callback) nothrow @trusted @nogc {
11519     handleError = callback;
11520   }
11521 
11522   //TODO: document this
11523   void getAtlasSize (int* width, int* height) const pure nothrow @trusted @nogc {
11524     if (width !is null) *width = params.width;
11525     if (height !is null) *height = params.height;
11526   }
11527 
11528   //TODO: document this
11529   bool expandAtlas (int width, int height) nothrow @trusted @nogc {
11530     import core.stdc.stdlib : free;
11531     import core.stdc.string : memcpy, memset;
11532 
11533     int maxy = 0;
11534     ubyte* data = null;
11535 
11536     width = nvg__max(width, params.width);
11537     height = nvg__max(height, params.height);
11538 
11539     if (width == params.width && height == params.height) return true;
11540 
11541     // Flush pending glyphs.
11542     flush();
11543 
11544     // Create new texture
11545     if (params.renderResize !is null) {
11546       if (params.renderResize(params.userPtr, width, height) == 0) return false;
11547     }
11548     // Copy old texture data over.
11549     data = cast(ubyte*)malloc(width*height);
11550     if (data is null) return 0;
11551     foreach (immutable int i; 0..params.height) {
11552       ubyte* dst = &data[i*width];
11553       ubyte* src = &texData[i*params.width];
11554       memcpy(dst, src, params.width);
11555       if (width > params.width) memset(dst+params.width, 0, width-params.width);
11556     }
11557     if (height > params.height) memset(&data[params.height*width], 0, (height-params.height)*width);
11558 
11559     free(texData);
11560     texData = data;
11561 
11562     // Increase atlas size
11563     atlas.expand(width, height);
11564 
11565     // Add existing data as dirty.
11566     foreach (immutable int i; 0..atlas.nnodes) maxy = nvg__max(maxy, atlas.nodes[i].y);
11567     dirtyRect.ptr[0] = 0;
11568     dirtyRect.ptr[1] = 0;
11569     dirtyRect.ptr[2] = params.width;
11570     dirtyRect.ptr[3] = maxy;
11571 
11572     params.width = width;
11573     params.height = height;
11574     itw = 1.0f/params.width;
11575     ith = 1.0f/params.height;
11576 
11577     return true;
11578   }
11579 
11580   //TODO: document this
11581   bool resetAtlas (int width, int height) nothrow @trusted @nogc {
11582     import core.stdc.stdlib : realloc;
11583     import core.stdc.string : memcpy, memset;
11584 
11585     // flush pending glyphs
11586     flush();
11587 
11588     // create new texture
11589     if (params.renderResize !is null) {
11590       if (params.renderResize(params.userPtr, width, height) == 0) return false;
11591     }
11592 
11593     // reset atlas
11594     atlas.reset(width, height);
11595 
11596     // clear texture data
11597     texData = cast(ubyte*)realloc(texData, width*height);
11598     if (texData is null) assert(0, "FONS: out of memory");
11599     memset(texData, 0, width*height);
11600 
11601     // reset dirty rect
11602     dirtyRect.ptr[0] = width;
11603     dirtyRect.ptr[1] = height;
11604     dirtyRect.ptr[2] = 0;
11605     dirtyRect.ptr[3] = 0;
11606 
11607     // Reset cached glyphs
11608     foreach (FONSfont* font; fonts[0..nfonts]) {
11609       if (font !is null) {
11610         font.nglyphs = 0;
11611         font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1;
11612       }
11613     }
11614 
11615     params.width = width;
11616     params.height = height;
11617     itw = 1.0f/params.width;
11618     ith = 1.0f/params.height;
11619 
11620     // Add white rect at 0, 0 for debug drawing.
11621     addWhiteRect(2, 2);
11622 
11623     return true;
11624   }
11625 
11626   //TODO: document this
11627   bool getPathBounds (dchar dch, float[] bounds) nothrow @trusted @nogc {
11628     if (bounds.length > 4) bounds = bounds.ptr[0..4];
11629     static if (is(typeof(&fons__nvg__bounds))) {
11630       FONSstate* state = getState;
11631       if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; }
11632       FONSfont* font;
11633       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11634       if (g == 0) { bounds[] = 0; return false; }
11635       assert(font !is null);
11636       return fons__nvg__bounds(&font.font, g, bounds);
11637     } else {
11638       bounds[] = 0;
11639       return false;
11640     }
11641   }
11642 
11643   //TODO: document this
11644   bool toPath() (NVGContext vg, dchar dch, float[] bounds=null) nothrow @trusted @nogc {
11645     if (bounds.length > 4) bounds = bounds.ptr[0..4];
11646     static if (is(typeof(&fons__nvg__toPath))) {
11647       if (vg is null) { bounds[] = 0; return false; }
11648       FONSstate* state = getState;
11649       if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; }
11650       FONSfont* font;
11651       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11652       if (g == 0) { bounds[] = 0; return false; }
11653       assert(font !is null);
11654       return fons__nvg__toPath(vg, &font.font, g, bounds);
11655     } else {
11656       bounds[] = 0;
11657       return false;
11658     }
11659   }
11660 
11661   //TODO: document this
11662   bool toOutline (dchar dch, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
11663     if (ol is null) return false;
11664     static if (is(typeof(&fons__nvg__toOutline))) {
11665       FONSstate* state = getState;
11666       if (state.font < 0 || state.font >= nfonts) return false;
11667       FONSfont* font;
11668       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11669       if (g == 0) return false;
11670       assert(font !is null);
11671       return fons__nvg__toOutline(&font.font, g, ol);
11672     } else {
11673       return false;
11674     }
11675   }
11676 
11677   //TODO: document this
11678   FONSglyph* getGlyph (FONSfont* font, uint codepoint, short isize, short iblur, FONSBitmapFlag bitmapOption) nothrow @trusted @nogc {
11679     static uint fons__hashint() (uint a) pure nothrow @safe @nogc {
11680       pragma(inline, true);
11681       a += ~(a<<15);
11682       a ^=  (a>>10);
11683       a +=  (a<<3);
11684       a ^=  (a>>6);
11685       a += ~(a<<11);
11686       a ^=  (a>>16);
11687       return a;
11688     }
11689 
11690     // based on Exponential blur, Jani Huhtanen, 2006
11691     enum APREC = 16;
11692     enum ZPREC = 7;
11693 
11694     static void fons__blurCols (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc {
11695       foreach (immutable int y; 0..h) {
11696         int z = 0; // force zero border
11697         foreach (int x; 1..w) {
11698           z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC;
11699           dst[x] = cast(ubyte)(z>>ZPREC);
11700         }
11701         dst[w-1] = 0; // force zero border
11702         z = 0;
11703         for (int x = w-2; x >= 0; --x) {
11704           z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC;
11705           dst[x] = cast(ubyte)(z>>ZPREC);
11706         }
11707         dst[0] = 0; // force zero border
11708         dst += dstStride;
11709       }
11710     }
11711 
11712     static void fons__blurRows (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc {
11713       foreach (immutable int x; 0..w) {
11714         int z = 0; // force zero border
11715         for (int y = dstStride; y < h*dstStride; y += dstStride) {
11716           z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC;
11717           dst[y] = cast(ubyte)(z>>ZPREC);
11718         }
11719         dst[(h-1)*dstStride] = 0; // force zero border
11720         z = 0;
11721         for (int y = (h-2)*dstStride; y >= 0; y -= dstStride) {
11722           z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC;
11723           dst[y] = cast(ubyte)(z>>ZPREC);
11724         }
11725         dst[0] = 0; // force zero border
11726         ++dst;
11727       }
11728     }
11729 
11730     static void fons__blur (ubyte* dst, int w, int h, int dstStride, int blur) nothrow @trusted @nogc {
11731       import std.math : expf = exp;
11732       if (blur < 1) return;
11733       // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity)
11734       immutable float sigma = cast(float)blur*0.57735f; // 1/sqrt(3)
11735       int alpha = cast(int)((1<<APREC)*(1.0f-expf(-2.3f/(sigma+1.0f))));
11736       fons__blurRows(dst, w, h, dstStride, alpha);
11737       fons__blurCols(dst, w, h, dstStride, alpha);
11738       fons__blurRows(dst, w, h, dstStride, alpha);
11739       fons__blurCols(dst, w, h, dstStride, alpha);
11740       //fons__blurrows(dst, w, h, dstStride, alpha);
11741       //fons__blurcols(dst, w, h, dstStride, alpha);
11742     }
11743 
11744     int advance, lsb, x0, y0, x1, y1, gx, gy;
11745     FONSglyph* glyph = null;
11746     float size = isize/10.0f;
11747     FONSfont* renderFont = font;
11748 
11749     if (isize < 2) return null;
11750     if (iblur > 20) iblur = 20;
11751     int pad = iblur+2;
11752 
11753     // Reset allocator.
11754     nscratch = 0;
11755 
11756     // Find code point and size.
11757     uint h = fons__hashint(codepoint)&(FONS_HASH_LUT_SIZE-1);
11758     int i = font.lut.ptr[h];
11759     while (i != -1) {
11760       //if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) return &font.glyphs[i];
11761       if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) {
11762         glyph = &font.glyphs[i];
11763         // Negative coordinate indicates there is no bitmap data created.
11764         if (bitmapOption == FONSBitmapFlag.Optional || (glyph.x0 >= 0 && glyph.y0 >= 0)) return glyph;
11765         // At this point, glyph exists but the bitmap data is not yet created.
11766         break;
11767       }
11768       i = font.glyphs[i].next;
11769     }
11770 
11771     // Create a new glyph or rasterize bitmap data for a cached glyph.
11772     //scale = fons__tt_getPixelHeightScale(&font.font, size);
11773     int g = findGlyphForCP(font, cast(dchar)codepoint, &renderFont);
11774     // It is possible that we did not find a fallback glyph.
11775     // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph.
11776 
11777     float scale = fons__tt_getPixelHeightScale(&renderFont.font, size);
11778     fons__tt_buildGlyphBitmap(&renderFont.font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1);
11779     int gw = x1-x0+pad*2;
11780     int gh = y1-y0+pad*2;
11781 
11782     // Determines the spot to draw glyph in the atlas.
11783     if (bitmapOption == FONSBitmapFlag.Required) {
11784       // Find free spot for the rect in the atlas.
11785       bool added = atlas.addRect(gw, gh, &gx, &gy);
11786       if (!added && handleError !is null) {
11787         // Atlas is full, let the user to resize the atlas (or not), and try again.
11788         handleError(FONSError.AtlasFull, 0);
11789         added = atlas.addRect(gw, gh, &gx, &gy);
11790       }
11791       if (!added) return null;
11792     } else {
11793       // Negative coordinate indicates there is no bitmap data created.
11794       gx = -1;
11795       gy = -1;
11796     }
11797 
11798     // Init glyph.
11799     if (glyph is null) {
11800       glyph = font.allocGlyph();
11801       glyph.codepoint = codepoint;
11802       glyph.size = isize;
11803       glyph.blur = iblur;
11804       glyph.next = 0;
11805 
11806       // Insert char to hash lookup.
11807       glyph.next = font.lut.ptr[h];
11808       font.lut.ptr[h] = font.nglyphs-1;
11809     }
11810     glyph.index = g;
11811     glyph.x0 = cast(short)gx;
11812     glyph.y0 = cast(short)gy;
11813     glyph.x1 = cast(short)(glyph.x0+gw);
11814     glyph.y1 = cast(short)(glyph.y0+gh);
11815     glyph.xadv = cast(short)(scale*advance*10.0f);
11816     glyph.xoff = cast(short)(x0-pad);
11817     glyph.yoff = cast(short)(y0-pad);
11818 
11819     if (bitmapOption == FONSBitmapFlag.Optional) return glyph;
11820 
11821     // Rasterize
11822     ubyte* dst = &texData[(glyph.x0+pad)+(glyph.y0+pad)*params.width];
11823     fons__tt_renderGlyphBitmap(&font.font, dst, gw-pad*2, gh-pad*2, params.width, scale, scale, g);
11824 
11825     // Make sure there is one pixel empty border.
11826     dst = &texData[glyph.x0+glyph.y0*params.width];
11827     foreach (immutable int y; 0..gh) {
11828       dst[y*params.width] = 0;
11829       dst[gw-1+y*params.width] = 0;
11830     }
11831     foreach (immutable int x; 0..gw) {
11832       dst[x] = 0;
11833       dst[x+(gh-1)*params.width] = 0;
11834     }
11835 
11836     // Debug code to color the glyph background
11837     version(none) {
11838       foreach (immutable yy; 0..gh) {
11839         foreach (immutable xx; 0..gw) {
11840           int a = cast(int)dst[xx+yy*params.width]+42;
11841           if (a > 255) a = 255;
11842           dst[xx+yy*params.width] = cast(ubyte)a;
11843         }
11844       }
11845     }
11846 
11847     // Blur
11848     if (iblur > 0) {
11849       nscratch = 0;
11850       ubyte* bdst = &texData[glyph.x0+glyph.y0*params.width];
11851       fons__blur(bdst, gw, gh, params.width, iblur);
11852     }
11853 
11854     dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], glyph.x0);
11855     dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], glyph.y0);
11856     dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], glyph.x1);
11857     dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], glyph.y1);
11858 
11859     return glyph;
11860   }
11861 
11862   //TODO: document this
11863   void getQuad (FONSfont* font, int prevGlyphIndex, FONSglyph* glyph, float size, float scale, float spacing, float* x, float* y, FONSQuad* q) nothrow @trusted @nogc {
11864     if (prevGlyphIndex >= 0) {
11865       immutable float adv = fons__tt_getGlyphKernAdvance(&font.font, size, prevGlyphIndex, glyph.index)/**scale*/; //k8: do we really need scale here?
11866       //if (adv != 0) { import core.stdc.stdio; printf("adv=%g (scale=%g; spacing=%g)\n", cast(double)adv, cast(double)scale, cast(double)spacing); }
11867       *x += cast(int)(adv+spacing /*+0.5f*/); //k8: for me, it looks better this way (with non-aa fonts)
11868     }
11869 
11870     // Each glyph has 2px border to allow good interpolation,
11871     // one pixel to prevent leaking, and one to allow good interpolation for rendering.
11872     // Inset the texture region by one pixel for correct interpolation.
11873     immutable float xoff = cast(short)(glyph.xoff+1);
11874     immutable float yoff = cast(short)(glyph.yoff+1);
11875     immutable float x0 = cast(float)(glyph.x0+1);
11876     immutable float y0 = cast(float)(glyph.y0+1);
11877     immutable float x1 = cast(float)(glyph.x1-1);
11878     immutable float y1 = cast(float)(glyph.y1-1);
11879 
11880     if (params.isZeroTopLeft) {
11881       immutable float rx = cast(float)cast(int)(*x+xoff);
11882       immutable float ry = cast(float)cast(int)(*y+yoff);
11883 
11884       q.x0 = rx;
11885       q.y0 = ry;
11886       q.x1 = rx+x1-x0;
11887       q.y1 = ry+y1-y0;
11888 
11889       q.s0 = x0*itw;
11890       q.t0 = y0*ith;
11891       q.s1 = x1*itw;
11892       q.t1 = y1*ith;
11893     } else {
11894       immutable float rx = cast(float)cast(int)(*x+xoff);
11895       immutable float ry = cast(float)cast(int)(*y-yoff);
11896 
11897       q.x0 = rx;
11898       q.y0 = ry;
11899       q.x1 = rx+x1-x0;
11900       q.y1 = ry-y1+y0;
11901 
11902       q.s0 = x0*itw;
11903       q.t0 = y0*ith;
11904       q.s1 = x1*itw;
11905       q.t1 = y1*ith;
11906     }
11907 
11908     *x += cast(int)(glyph.xadv/10.0f+0.5f);
11909   }
11910 
11911   void flush () nothrow @trusted @nogc {
11912     // flush texture
11913     if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) {
11914       if (params.renderUpdate !is null) params.renderUpdate(params.userPtr, dirtyRect.ptr, texData);
11915       // reset dirty rect
11916       dirtyRect.ptr[0] = params.width;
11917       dirtyRect.ptr[1] = params.height;
11918       dirtyRect.ptr[2] = 0;
11919       dirtyRect.ptr[3] = 0;
11920     }
11921   }
11922 }
11923 
11924 /// Free all resources used by the `stash`, and `stash` itself.
11925 /// Group: font_stash
11926 public void kill (ref FONSContext stash) nothrow @trusted @nogc {
11927   import core.stdc.stdlib : free;
11928   if (stash is null) return;
11929   stash.clear();
11930   free(stash);
11931   stash = null;
11932 }
11933 
11934 
11935 // ////////////////////////////////////////////////////////////////////////// //
11936 void* fons__tmpalloc (usize size, void* up) nothrow @trusted @nogc {
11937   ubyte* ptr;
11938   FONSContext stash = cast(FONSContext)up;
11939   // 16-byte align the returned pointer
11940   size = (size+0xf)&~0xf;
11941   if (stash.nscratch+cast(int)size > FONS_SCRATCH_BUF_SIZE) {
11942     if (stash.handleError !is null) stash.handleError(FONSError.ScratchFull, stash.nscratch+cast(int)size);
11943     return null;
11944   }
11945   ptr = stash.scratch+stash.nscratch;
11946   stash.nscratch += cast(int)size;
11947   return ptr;
11948 }
11949 
11950 void fons__tmpfree (void* ptr, void* up) nothrow @trusted @nogc {
11951   // empty
11952 }
11953 
11954 
11955 // ////////////////////////////////////////////////////////////////////////// //
11956 // Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de>
11957 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
11958 
11959 enum FONS_UTF8_ACCEPT = 0;
11960 enum FONS_UTF8_REJECT = 12;
11961 
11962 static immutable ubyte[364] utf8d = [
11963   // The first part of the table maps bytes to character classes that
11964   // to reduce the size of the transition table and create bitmasks.
11965   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
11966   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
11967   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
11968   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,
11969   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,
11970   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,
11971   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,
11972   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,
11973 
11974   // The second part is a transition table that maps a combination
11975   // of a state of the automaton and a character class to a state.
11976   0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
11977   12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12,
11978   12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12,
11979   12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12,
11980   12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
11981 ];
11982 
11983 private enum DecUtfMixin(string state, string codep, string byte_) =
11984 `{
11985   uint type_ = utf8d.ptr[`~byte_~`];
11986   `~codep~` = (`~state~` != FONS_UTF8_ACCEPT ? (`~byte_~`&0x3fu)|(`~codep~`<<6) : (0xff>>type_)&`~byte_~`);
11987   if ((`~state~` = utf8d.ptr[256+`~state~`+type_]) == FONS_UTF8_REJECT) {
11988     `~state~` = FONS_UTF8_ACCEPT;
11989     `~codep~` = 0xFFFD;
11990   }
11991  }`;
11992 
11993 /*
11994 uint fons__decutf8 (uint* state, uint* codep, uint byte_) {
11995   pragma(inline, true);
11996   uint type = utf8d.ptr[byte_];
11997   *codep = (*state != FONS_UTF8_ACCEPT ? (byte_&0x3fu)|(*codep<<6) : (0xff>>type)&byte_);
11998   *state = utf8d.ptr[256 + *state+type];
11999   return *state;
12000 }
12001 */
12002 
12003 
12004 // ////////////////////////////////////////////////////////////////////////// //
12005 /// This iterator can be used to do text measurement.
12006 /// $(WARNING Don't add new fonts to stash while you are iterating, or you WILL get segfault!)
12007 /// Group: font_stash
12008 public struct FONSTextBoundsIterator {
12009 private:
12010   FONSContext stash;
12011   FONSstate state;
12012   uint codepoint = 0xFFFD;
12013   uint utf8state = 0;
12014   int prevGlyphIndex = -1;
12015   short isize, iblur;
12016   float scale = 0;
12017   FONSfont* font;
12018   float startx = 0, x = 0, y = 0;
12019   float minx = 0, miny = 0, maxx = 0, maxy = 0;
12020 
12021 private:
12022   void clear () nothrow @trusted @nogc {
12023     import core.stdc.string : memset;
12024     memset(&this, 0, this.sizeof);
12025     this.prevGlyphIndex = -1;
12026     this.codepoint = 0xFFFD;
12027   }
12028 
12029 public:
12030   /// Initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator.
12031   this (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { reset(astash, ax, ay); }
12032 
12033   /// (Re)initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator.
12034   void reset (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc {
12035     clear();
12036 
12037     if (astash is null || astash.nstates == 0) return;
12038 
12039     stash = astash;
12040     state = *stash.getState;
12041 
12042     if (state.font < 0 || state.font >= stash.nfonts) { clear(); return; }
12043     font = stash.fonts[state.font];
12044     if (font is null || font.fdata is null) { clear(); return; }
12045 
12046     x = ax;
12047     y = ay;
12048     isize = cast(short)(state.size*10.0f);
12049     iblur = cast(short)state.blur;
12050     scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
12051 
12052     // align vertically
12053     y += astash.getVertAlign(font, state.talign, isize);
12054 
12055     minx = maxx = x;
12056     miny = maxy = y;
12057     startx = x;
12058   }
12059 
12060   /// Can this iterator be used?
12061   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null); }
12062 
12063   /// Put some text into iterator, calculate new values.
12064   void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) {
12065     enum DoCodePointMixin = q{
12066       glyph = stash.getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional);
12067       if (glyph !is null) {
12068         stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q);
12069         if (q.x0 < minx) minx = q.x0;
12070         if (q.x1 > maxx) maxx = q.x1;
12071         if (stash.params.isZeroTopLeft) {
12072           if (q.y0 < miny) miny = q.y0;
12073           if (q.y1 > maxy) maxy = q.y1;
12074         } else {
12075           if (q.y1 < miny) miny = q.y1;
12076           if (q.y0 > maxy) maxy = q.y0;
12077         }
12078         prevGlyphIndex = glyph.index;
12079       } else {
12080         prevGlyphIndex = -1;
12081       }
12082     };
12083 
12084     if (stash is null || str.length == 0) return; // alas
12085 
12086     FONSQuad q;
12087     FONSglyph* glyph;
12088 
12089     static if (is(T == char)) {
12090       foreach (char ch; str) {
12091         mixin(DecUtfMixin!("utf8state", "codepoint", "cast(ubyte)ch"));
12092         if (utf8state) continue; // full char is not collected yet
12093         mixin(DoCodePointMixin);
12094       }
12095     } else {
12096       if (utf8state) {
12097         utf8state = 0;
12098         codepoint = 0xFFFD;
12099         mixin(DoCodePointMixin);
12100       }
12101       foreach (T dch; str) {
12102         static if (is(T == dchar)) {
12103           if (dch > dchar.max) dch = 0xFFFD;
12104         }
12105         codepoint = cast(uint)dch;
12106         mixin(DoCodePointMixin);
12107       }
12108     }
12109   }
12110 
12111   /// Returns current advance.
12112   @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null ? x-startx : 0); }
12113 
12114   /// Returns current text bounds.
12115   void getBounds (ref float[4] bounds) const pure nothrow @safe @nogc {
12116     if (stash is null) { bounds[] = 0; return; }
12117     float lminx = minx, lmaxx = maxx;
12118     // align horizontally
12119     if (state.talign.left) {
12120       // empty
12121     } else if (state.talign.right) {
12122       float ca = advance;
12123       lminx -= ca;
12124       lmaxx -= ca;
12125     } else if (state.talign.center) {
12126       float ca = advance*0.5f;
12127       lminx -= ca;
12128       lmaxx -= ca;
12129     }
12130     bounds[0] = lminx;
12131     bounds[1] = miny;
12132     bounds[2] = lmaxx;
12133     bounds[3] = maxy;
12134   }
12135 
12136   /// Returns current horizontal text bounds.
12137   void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc {
12138     if (stash !is null) {
12139       float lminx = minx, lmaxx = maxx;
12140       // align horizontally
12141       if (state.talign.left) {
12142         // empty
12143       } else if (state.talign.right) {
12144         float ca = advance;
12145         lminx -= ca;
12146         lmaxx -= ca;
12147       } else if (state.talign.center) {
12148         float ca = advance*0.5f;
12149         lminx -= ca;
12150         lmaxx -= ca;
12151       }
12152       xmin = lminx;
12153       xmax = lmaxx;
12154     } else {
12155       xmin = xmax = 0;
12156     }
12157   }
12158 
12159   /// Returns current vertical text bounds.
12160   void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc {
12161     pragma(inline, true);
12162     if (stash !is null) {
12163       ymin = miny;
12164       ymax = maxy;
12165     } else {
12166       ymin = ymax = 0;
12167     }
12168   }
12169 
12170   /// Returns font line height.
12171   float lineHeight () nothrow @trusted @nogc {
12172     pragma(inline, true);
12173     return (stash !is null ? stash.fonts[state.font].lineh*cast(short)(state.size*10.0f)/10.0f : 0);
12174   }
12175 
12176   /// Returns font ascender (positive).
12177   float ascender () nothrow @trusted @nogc {
12178     pragma(inline, true);
12179     return (stash !is null ? stash.fonts[state.font].ascender*cast(short)(state.size*10.0f)/10.0f : 0);
12180   }
12181 
12182   /// Returns font descender (negative).
12183   float descender () nothrow @trusted @nogc {
12184     pragma(inline, true);
12185     return (stash !is null ? stash.fonts[state.font].descender*cast(short)(state.size*10.0f)/10.0f : 0);
12186   }
12187 }
12188 
12189 
12190 // ////////////////////////////////////////////////////////////////////////// //
12191 // backgl
12192 // ////////////////////////////////////////////////////////////////////////// //
12193 import core.stdc.stdlib : malloc, realloc, free;
12194 import core.stdc.string : memcpy, memset;
12195 
12196 static if (__VERSION__ < 2076) {
12197   private auto DGNoThrowNoGC(T) (scope T t) /*if (isFunctionPointer!T || isDelegate!T)*/ {
12198     import std.traits;
12199     enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
12200     return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
12201   }
12202 }
12203 
12204 
12205 //import arsd.simpledisplay;
12206 version(nanovg_bindbc_opengl_bindings) {
12207   import bindbc.opengl;
12208 } else version(nanovg_builtin_opengl_bindings) {
12209   import arsd.simpledisplay;
12210 
12211 	/++
12212 		A SimpleWindow subclass that encapsulates some nanovega defaults. You just set a `redrawNVGScene` delegate and, optionally, your normal event handlers for simpledisplay, and the rest is set up for you.
12213 
12214 		History:
12215 			Added January 22, 2021 (version 9.2 release)
12216 	+/
12217 	public class NVGWindow : SimpleWindow {
12218 		NVGContext nvg;
12219 
12220 		/++
12221 
12222 		+/
12223 		this(int width, int height, string title) {
12224 			setOpenGLContextVersion(3, 0);
12225 			super(width, height, title, OpenGlOptions.yes, Resizability.allowResizing);
12226 
12227 			this.onClosing = delegate() {
12228 				nvg.kill();
12229 			};
12230 
12231 			this.visibleForTheFirstTime = delegate() {
12232 				this.setAsCurrentOpenGlContext();
12233 				nvg = nvgCreateContext();
12234 				if(nvg is null) throw new Exception("cannot initialize NanoVega");
12235 			};
12236 
12237 			this.redrawOpenGlScene = delegate() {
12238 				if(redrawNVGScene is null)
12239 					return;
12240 				glViewport(0, 0, this.width, this.height);
12241 				if(clearOnEachFrame) {
12242 					glClearColor(0, 0, 0, 0);
12243 					glClear(glNVGClearFlags);
12244 				}
12245 
12246 				nvg.beginFrame(this.width, this.height);
12247 				scope(exit) nvg.endFrame();
12248 
12249 				redrawNVGScene(nvg);
12250 			};
12251 
12252 			this.setEventHandlers(
12253 				&redrawOpenGlSceneNow,
12254 				(KeyEvent ke) {
12255 					if(ke.key == Key.Escape || ke.key == Key.Q)
12256 						this.close();
12257 				}
12258 			);
12259 		}
12260 
12261 		/++
12262 
12263 		+/
12264 		bool clearOnEachFrame = true;
12265 
12266 		/++
12267 
12268 		+/
12269 		void delegate(NVGContext nvg) redrawNVGScene;
12270 
12271 		/++
12272 
12273 		+/
12274 		void redrawNVGSceneNow() {
12275 			redrawOpenGlSceneNow();
12276 		}
12277 	}
12278 
12279 } else {
12280   import iv.glbinds;
12281 }
12282 
12283 private:
12284 // sdpy is missing that yet
12285 static if (!is(typeof(GL_STENCIL_BUFFER_BIT))) enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
12286 
12287 
12288 
12289 version(bindbc){
12290   private extern(System) nothrow @nogc:
12291   // this definition doesn't exist in regular OpenGL (?)
12292   enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
12293   private void nanovgInitOpenGL () {
12294     // i'm not aware of calling the load multiple times having negative side effects, so i don't do an initialization check
12295     GLSupport support = loadOpenGL();
12296     if (support == GLSupport.noLibrary)
12297       assert(0, "OpenGL initialization failed: shared library failed to load");
12298     else if (support == GLSupport.badLibrary)
12299       assert(0, "OpenGL initialization failed: a context-independent symbol failed to load");
12300     else if (support == GLSupport.noContext)
12301       assert(0, "OpenGL initialization failed: a context needs to be created prior to initialization");
12302   }
12303 } else { // OpenGL API missing from simpledisplay
12304     private void nanovgInitOpenGL () @nogc nothrow {
12305       __gshared bool initialized = false;
12306       if (initialized) return;
12307 
12308       try
12309         gl3.loadDynamicLibrary();
12310       catch(Exception)
12311       	assert(0, "GL 3 failed to load");
12312 
12313       initialized = true;
12314   }
12315 }
12316 
12317 
12318 
12319 /// Context creation flags.
12320 /// Group: context_management
12321 public enum NVGContextFlag : int {
12322   /// Nothing special, i.e. empty flag.
12323   None = 0,
12324   /// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA).
12325   Antialias = 1U<<0,
12326   /** Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little
12327     * slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. */
12328   StencilStrokes = 1U<<1,
12329   /// Flag indicating that additional debug checks are done.
12330   Debug = 1U<<2,
12331   /// Filter (antialias) fonts
12332   FontAA = 1U<<7,
12333   /// Don't filter (antialias) fonts
12334   FontNoAA = 1U<<8,
12335   /// You can use this as a substitute for default flags, for cases like this: `nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);`.
12336   Default = 1U<<31,
12337 }
12338 
12339 public enum NANOVG_GL_USE_STATE_FILTER = true;
12340 
12341 /// Returns flags for glClear().
12342 /// Group: context_management
12343 public uint glNVGClearFlags () pure nothrow @safe @nogc {
12344   pragma(inline, true);
12345   return (GL_COLOR_BUFFER_BIT|/*GL_DEPTH_BUFFER_BIT|*/GL_STENCIL_BUFFER_BIT);
12346 }
12347 
12348 
12349 // ////////////////////////////////////////////////////////////////////////// //
12350 private:
12351 
12352 version = nanovega_shared_stencil;
12353 //version = nanovega_debug_clipping;
12354 
12355 enum GLNVGuniformLoc {
12356   ViewSize,
12357   Tex,
12358   Frag,
12359   TMat,
12360   TTr,
12361   ClipTex,
12362 }
12363 
12364 alias GLNVGshaderType = int;
12365 enum /*GLNVGshaderType*/ {
12366   NSVG_SHADER_FILLCOLOR,
12367   NSVG_SHADER_FILLGRAD,
12368   NSVG_SHADER_FILLIMG,
12369   NSVG_SHADER_SIMPLE, // also used for clipfill
12370   NSVG_SHADER_IMG,
12371 }
12372 
12373 struct GLNVGshader {
12374   GLuint prog;
12375   GLuint frag;
12376   GLuint vert;
12377   GLint[GLNVGuniformLoc.max+1] loc;
12378 }
12379 
12380 struct GLNVGtexture {
12381   int id;
12382   GLuint tex;
12383   int width, height;
12384   NVGtexture type;
12385   int flags;
12386   shared int rc; // this can be 0 with tex != 0 -- postponed deletion
12387   int nextfree;
12388 }
12389 
12390 struct GLNVGblend {
12391   bool simple;
12392   GLenum srcRGB;
12393   GLenum dstRGB;
12394   GLenum srcAlpha;
12395   GLenum dstAlpha;
12396 }
12397 
12398 alias GLNVGcallType = int;
12399 enum /*GLNVGcallType*/ {
12400   GLNVG_NONE = 0,
12401   GLNVG_FILL,
12402   GLNVG_CONVEXFILL,
12403   GLNVG_STROKE,
12404   GLNVG_TRIANGLES,
12405   GLNVG_AFFINE, // change affine transformation matrix
12406   GLNVG_PUSHCLIP,
12407   GLNVG_POPCLIP,
12408   GLNVG_RESETCLIP,
12409   GLNVG_CLIP_DDUMP_ON,
12410   GLNVG_CLIP_DDUMP_OFF,
12411 }
12412 
12413 struct GLNVGcall {
12414   int type;
12415   int evenOdd; // for fill
12416   int image;
12417   int pathOffset;
12418   int pathCount;
12419   int triangleOffset;
12420   int triangleCount;
12421   int uniformOffset;
12422   NVGMatrix affine;
12423   GLNVGblend blendFunc;
12424   NVGClipMode clipmode;
12425 }
12426 
12427 struct GLNVGpath {
12428   int fillOffset;
12429   int fillCount;
12430   int strokeOffset;
12431   int strokeCount;
12432 }
12433 
12434 align(1) struct GLNVGfragUniforms {
12435 align(1):
12436   enum UNIFORM_ARRAY_SIZE = 13;
12437   // note: after modifying layout or size of uniform array,
12438   // don't forget to also update the fragment shader source!
12439   align(1) union {
12440   align(1):
12441     align(1) struct {
12442     align(1):
12443       float[12] scissorMat; // matrices are actually 3 vec4s
12444       float[12] paintMat;
12445       NVGColor innerCol;
12446       NVGColor middleCol;
12447       NVGColor outerCol;
12448       float[2] scissorExt;
12449       float[2] scissorScale;
12450       float[2] extent;
12451       float radius;
12452       float feather;
12453       float strokeMult;
12454       float strokeThr;
12455       float texType;
12456       float type;
12457       float doclip;
12458       float midp; // for gradients
12459       float unused2, unused3;
12460     }
12461     float[4][UNIFORM_ARRAY_SIZE] uniformArray;
12462   }
12463 }
12464 
12465 enum GLMaskState {
12466   DontMask = -1,
12467   Uninitialized = 0,
12468   Initialized = 1,
12469   JustCleared = 2,
12470 }
12471 
12472 import core.sync.mutex;
12473 __gshared Mutex GLNVGTextureLocker;
12474 shared static this() {
12475 	GLNVGTextureLocker = new Mutex();
12476 }
12477 
12478 struct GLNVGcontext {
12479   private import core.thread : ThreadID;
12480 
12481   GLNVGshader shader;
12482   GLNVGtexture* textures;
12483   float[2] view;
12484   int freetexid; // -1: none
12485   int ntextures;
12486   int ctextures;
12487   GLuint vertBuf;
12488   int fragSize;
12489   int flags;
12490   // FBOs for masks
12491   GLuint[NVG_MAX_STATES] fbo;
12492   GLuint[2][NVG_MAX_STATES] fboTex; // FBO textures: [0] is color, [1] is stencil
12493   int fboWidth, fboHeight;
12494   GLMaskState[NVG_MAX_STATES] maskStack;
12495   int msp; // mask stack pointer; starts from `0`; points to next free item; see below for logic description
12496   int lastClipFBO; // -666: cache invalidated; -1: don't mask
12497   int lastClipUniOfs;
12498   bool doClipUnion; // specal mode
12499   GLNVGshader shaderFillFBO;
12500   GLNVGshader shaderCopyFBO;
12501 
12502   bool inFrame; // will be `true` if we can perform OpenGL operations (used in texture deletion)
12503   shared bool mustCleanTextures; // will be `true` if we should delete some textures
12504   ThreadID mainTID;
12505   uint mainFBO;
12506 
12507   // Per frame buffers
12508   GLNVGcall* calls;
12509   int ccalls;
12510   int ncalls;
12511   GLNVGpath* paths;
12512   int cpaths;
12513   int npaths;
12514   NVGVertex* verts;
12515   int cverts;
12516   int nverts;
12517   ubyte* uniforms;
12518   int cuniforms;
12519   int nuniforms;
12520   NVGMatrix lastAffine;
12521 
12522   // cached state
12523   static if (NANOVG_GL_USE_STATE_FILTER) {
12524     GLuint boundTexture;
12525     GLuint stencilMask;
12526     GLenum stencilFunc;
12527     GLint stencilFuncRef;
12528     GLuint stencilFuncMask;
12529     GLNVGblend blendFunc;
12530   }
12531 }
12532 
12533 int glnvg__maxi() (int a, int b) { pragma(inline, true); return (a > b ? a : b); }
12534 
12535 void glnvg__bindTexture (GLNVGcontext* gl, GLuint tex) nothrow @trusted @nogc {
12536   static if (NANOVG_GL_USE_STATE_FILTER) {
12537     if (gl.boundTexture != tex) {
12538       gl.boundTexture = tex;
12539       glBindTexture(GL_TEXTURE_2D, tex);
12540     }
12541   } else {
12542     glBindTexture(GL_TEXTURE_2D, tex);
12543   }
12544 }
12545 
12546 void glnvg__stencilMask (GLNVGcontext* gl, GLuint mask) nothrow @trusted @nogc {
12547   static if (NANOVG_GL_USE_STATE_FILTER) {
12548     if (gl.stencilMask != mask) {
12549       gl.stencilMask = mask;
12550       glStencilMask(mask);
12551     }
12552   } else {
12553     glStencilMask(mask);
12554   }
12555 }
12556 
12557 void glnvg__stencilFunc (GLNVGcontext* gl, GLenum func, GLint ref_, GLuint mask) nothrow @trusted @nogc {
12558   static if (NANOVG_GL_USE_STATE_FILTER) {
12559     if (gl.stencilFunc != func || gl.stencilFuncRef != ref_ || gl.stencilFuncMask != mask) {
12560       gl.stencilFunc = func;
12561       gl.stencilFuncRef = ref_;
12562       gl.stencilFuncMask = mask;
12563       glStencilFunc(func, ref_, mask);
12564     }
12565   } else {
12566     glStencilFunc(func, ref_, mask);
12567   }
12568 }
12569 
12570 // texture id is never zero
12571 // sets refcount to one
12572 GLNVGtexture* glnvg__allocTexture (GLNVGcontext* gl) nothrow @trusted @nogc {
12573   GLNVGtexture* tex = null;
12574 
12575   int tid = gl.freetexid;
12576   if (tid == -1) {
12577     if (gl.ntextures >= gl.ctextures) {
12578       assert(gl.ntextures == gl.ctextures);
12579       //pragma(msg, GLNVGtexture.sizeof*32);
12580       int ctextures = (gl.ctextures == 0 ? 32 : gl.ctextures+gl.ctextures/2); // 1.5x overallocate
12581       GLNVGtexture* textures = cast(GLNVGtexture*)realloc(gl.textures, GLNVGtexture.sizeof*ctextures);
12582       if (textures is null) assert(0, "NanoVega: out of memory for textures");
12583       memset(&textures[gl.ctextures], 0, (ctextures-gl.ctextures)*GLNVGtexture.sizeof);
12584       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated more textures (n=%d; c=%d; nc=%d)\n", gl.ntextures, gl.ctextures, ctextures); }}
12585       gl.textures = textures;
12586       gl.ctextures = ctextures;
12587     }
12588     assert(gl.ntextures+1 <= gl.ctextures);
12589     tid = gl.ntextures++;
12590     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("  got next free texture id %d, ntextures=%d\n", tid+1, gl.ntextures); }}
12591   } else {
12592     gl.freetexid = gl.textures[tid].nextfree;
12593   }
12594   assert(tid <= gl.ntextures);
12595 
12596   assert(gl.textures[tid].id == 0);
12597   tex = &gl.textures[tid];
12598   memset(tex, 0, (*tex).sizeof);
12599   tex.id = tid+1;
12600   tex.rc = 1;
12601   tex.nextfree = -1;
12602 
12603   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated texture with id %d (%d)\n", tex.id, tid+1); }}
12604 
12605   return tex;
12606 }
12607 
12608 GLNVGtexture* glnvg__findTexture (GLNVGcontext* gl, int id) nothrow @trusted @nogc {
12609   if (id <= 0 || id > gl.ntextures) return null;
12610   if (gl.textures[id-1].id == 0) return null; // free one
12611   assert(gl.textures[id-1].id == id);
12612   return &gl.textures[id-1];
12613 }
12614 
12615 bool glnvg__deleteTexture (GLNVGcontext* gl, ref int id) nothrow @trusted @nogc {
12616   if (id <= 0 || id > gl.ntextures) return false;
12617   auto tx = &gl.textures[id-1];
12618   if (tx.id == 0) { id = 0; return false; } // free one
12619   assert(tx.id == id);
12620   assert(tx.tex != 0);
12621   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("decrefing texture with id %d (%d)\n", tx.id, id); }}
12622   import core.atomic : atomicOp;
12623   if (atomicOp!"-="(tx.rc, 1) == 0) {
12624     import core.thread : ThreadID;
12625     ThreadID mytid;
12626     static if (__VERSION__ < 2076) {
12627       DGNoThrowNoGC(() {
12628         import core.thread; mytid = Thread.getThis.id;
12629       })();
12630     } else {
12631       try { import core.thread; mytid = Thread.getThis.id; } catch (Exception e) {}
12632     }
12633     if (gl.mainTID == mytid && gl.inFrame) {
12634       // can delete it right now
12635       if ((tx.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tx.tex);
12636       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** deleted texture with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }}
12637       memset(tx, 0, (*tx).sizeof);
12638       //{ import core.stdc.stdio; printf("deleting texture with id %d\n", id); }
12639       tx.nextfree = gl.freetexid;
12640       gl.freetexid = id-1;
12641     } else {
12642       // alas, we aren't doing frame business, so we should postpone deletion
12643       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** POSTPONED texture deletion with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }}
12644       version(aliced) {
12645 	{
12646         GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow;
12647           tx.id = 0; // mark it as dead
12648           gl.mustCleanTextures = true; // set "need cleanup" flag
12649 	}
12650       } else {
12651         try {
12652             GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow;
12653             tx.id = 0; // mark it as dead
12654             gl.mustCleanTextures = true; // set "need cleanup" flag
12655         } catch (Exception e) {}
12656       }
12657     }
12658   }
12659   id = 0;
12660   return true;
12661 }
12662 
12663 void glnvg__dumpShaderError (GLuint shader, const(char)* name, const(char)* type) nothrow @trusted @nogc {
12664   import core.stdc.stdio : fprintf, stderr;
12665   GLchar[512+1] str = 0;
12666   GLsizei len = 0;
12667   glGetShaderInfoLog(shader, 512, &len, str.ptr);
12668   if (len > 512) len = 512;
12669   str[len] = '\0';
12670   fprintf(stderr, "Shader %s/%s error:\n%s\n", name, type, str.ptr);
12671 }
12672 
12673 void glnvg__dumpProgramError (GLuint prog, const(char)* name) nothrow @trusted @nogc {
12674   import core.stdc.stdio : fprintf, stderr;
12675   GLchar[512+1] str = 0;
12676   GLsizei len = 0;
12677   glGetProgramInfoLog(prog, 512, &len, str.ptr);
12678   if (len > 512) len = 512;
12679   str[len] = '\0';
12680   fprintf(stderr, "Program %s error:\n%s\n", name, str.ptr);
12681 }
12682 
12683 void glnvg__resetError(bool force=false) (GLNVGcontext* gl) nothrow @trusted @nogc {
12684   static if (!force) {
12685     if ((gl.flags&NVGContextFlag.Debug) == 0) return;
12686   }
12687   glGetError();
12688 }
12689 
12690 void glnvg__checkError(bool force=false) (GLNVGcontext* gl, const(char)* str) nothrow @trusted @nogc {
12691   GLenum err;
12692   static if (!force) {
12693     if ((gl.flags&NVGContextFlag.Debug) == 0) return;
12694   }
12695   err = glGetError();
12696   if (err != GL_NO_ERROR) {
12697     import core.stdc.stdio : fprintf, stderr;
12698     fprintf(stderr, "Error %08x after %s\n", err, str);
12699     return;
12700   }
12701 }
12702 
12703 bool glnvg__createShader (GLNVGshader* shader, const(char)* name, const(char)* header, const(char)* opts, const(char)* vshader, const(char)* fshader) nothrow @trusted @nogc {
12704   GLint status;
12705   GLuint prog, vert, frag;
12706   const(char)*[3] str;
12707 
12708   memset(shader, 0, (*shader).sizeof);
12709 
12710   prog = glCreateProgram();
12711   vert = glCreateShader(GL_VERTEX_SHADER);
12712   frag = glCreateShader(GL_FRAGMENT_SHADER);
12713   str[0] = header;
12714   str[1] = (opts !is null ? opts : "");
12715   str[2] = vshader;
12716   glShaderSource(vert, 3, cast(const(char)**)str.ptr, null);
12717 
12718   glCompileShader(vert);
12719   glGetShaderiv(vert, GL_COMPILE_STATUS, &status);
12720   if (status != GL_TRUE) {
12721     glnvg__dumpShaderError(vert, name, "vert");
12722     return false;
12723   }
12724 
12725   str[0] = header;
12726   str[1] = (opts !is null ? opts : "");
12727   str[2] = fshader;
12728   glShaderSource(frag, 3, cast(const(char)**)str.ptr, null);
12729 
12730   glCompileShader(frag);
12731   glGetShaderiv(frag, GL_COMPILE_STATUS, &status);
12732   if (status != GL_TRUE) {
12733     glnvg__dumpShaderError(frag, name, "frag");
12734     return false;
12735   }
12736 
12737   glAttachShader(prog, vert);
12738   glAttachShader(prog, frag);
12739 
12740   glBindAttribLocation(prog, 0, "vertex");
12741   glBindAttribLocation(prog, 1, "tcoord");
12742 
12743   glLinkProgram(prog);
12744   glGetProgramiv(prog, GL_LINK_STATUS, &status);
12745   if (status != GL_TRUE) {
12746     glnvg__dumpProgramError(prog, name);
12747     return false;
12748   }
12749 
12750   shader.prog = prog;
12751   shader.vert = vert;
12752   shader.frag = frag;
12753 
12754   return true;
12755 }
12756 
12757 void glnvg__deleteShader (GLNVGshader* shader) nothrow @trusted @nogc {
12758   if (shader.prog != 0) glDeleteProgram(shader.prog);
12759   if (shader.vert != 0) glDeleteShader(shader.vert);
12760   if (shader.frag != 0) glDeleteShader(shader.frag);
12761 }
12762 
12763 void glnvg__getUniforms (GLNVGshader* shader) nothrow @trusted @nogc {
12764   shader.loc[GLNVGuniformLoc.ViewSize] = glGetUniformLocation(shader.prog, "viewSize");
12765   shader.loc[GLNVGuniformLoc.Tex] = glGetUniformLocation(shader.prog, "tex");
12766   shader.loc[GLNVGuniformLoc.Frag] = glGetUniformLocation(shader.prog, "frag");
12767   shader.loc[GLNVGuniformLoc.TMat] = glGetUniformLocation(shader.prog, "tmat");
12768   shader.loc[GLNVGuniformLoc.TTr] = glGetUniformLocation(shader.prog, "ttr");
12769   shader.loc[GLNVGuniformLoc.ClipTex] = glGetUniformLocation(shader.prog, "clipTex");
12770 }
12771 
12772 void glnvg__killFBOs (GLNVGcontext* gl) nothrow @trusted @nogc {
12773   foreach (immutable fidx, ref GLuint fbo; gl.fbo[]) {
12774     if (fbo != 0) {
12775       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
12776       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
12777       foreach (ref GLuint tid; gl.fboTex.ptr[fidx][]) if (tid != 0) { glDeleteTextures(1, &tid); tid = 0; }
12778       glDeleteFramebuffers(1, &fbo);
12779       fbo = 0;
12780     }
12781   }
12782   gl.fboWidth = gl.fboHeight = 0;
12783 }
12784 
12785 // returns `true` is new FBO was created
12786 // will not unbind buffer, if it was created
12787 bool glnvg__allocFBO (GLNVGcontext* gl, int fidx, bool doclear=true) nothrow @trusted @nogc {
12788   assert(fidx >= 0 && fidx < gl.fbo.length);
12789   assert(gl.fboWidth > 0);
12790   assert(gl.fboHeight > 0);
12791 
12792   if (gl.fbo.ptr[fidx] != 0) return false; // nothing to do, this FBO is already initialized
12793 
12794   glnvg__resetError(gl);
12795 
12796   // allocate FBO object
12797   GLuint fbo = 0;
12798   glGenFramebuffers(1, &fbo);
12799   if (fbo == 0) assert(0, "NanoVega: cannot create FBO");
12800   glnvg__checkError(gl, "glnvg__allocFBO: glGenFramebuffers");
12801   glBindFramebuffer(GL_FRAMEBUFFER, fbo);
12802   //scope(exit) glBindFramebuffer(GL_FRAMEBUFFER, 0);
12803 
12804   // attach 2D texture to this FBO
12805   GLuint tidColor = 0;
12806   glGenTextures(1, &tidColor);
12807   if (tidColor == 0) assert(0, "NanoVega: cannot create RGBA texture for FBO");
12808   glBindTexture(GL_TEXTURE_2D, tidColor);
12809   //scope(exit) glBindTexture(GL_TEXTURE_2D, 0);
12810   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
12811   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_S");
12812   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
12813   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_T");
12814   //FIXME: linear or nearest?
12815   //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
12816   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
12817   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MIN_FILTER");
12818   //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
12819   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
12820   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MAG_FILTER");
12821   // empty texture
12822   //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
12823   // create texture with only one color channel
12824   glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, gl.fboWidth, gl.fboHeight, 0, GL_RED, GL_UNSIGNED_BYTE, null);
12825   //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
12826   glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (color)");
12827   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tidColor, 0);
12828   glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (color)");
12829 
12830   // attach stencil texture to this FBO
12831   GLuint tidStencil = 0;
12832   version(nanovega_shared_stencil) {
12833     if (gl.fboTex.ptr[0].ptr[0] == 0) {
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     } else {
12838       tidStencil = gl.fboTex.ptr[0].ptr[0];
12839     }
12840     if (fidx != 0) gl.fboTex.ptr[fidx].ptr[1] = 0; // stencil texture is shared among FBOs
12841   } else {
12842     glGenTextures(1, &tidStencil);
12843     if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO");
12844     gl.fboTex.ptr[0].ptr[0] = tidStencil;
12845   }
12846   glBindTexture(GL_TEXTURE_2D, tidStencil);
12847   glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, gl.fboWidth, gl.fboHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, null);
12848   glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (stencil)");
12849   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tidStencil, 0);
12850   glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (stencil)");
12851 
12852   {
12853     GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
12854     if (status != GL_FRAMEBUFFER_COMPLETE) {
12855       version(all) {
12856         import core.stdc.stdio;
12857         if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) printf("fucked attachement\n");
12858         if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) printf("fucked dimensions\n");
12859         if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) printf("missing attachement\n");
12860         if (status == GL_FRAMEBUFFER_UNSUPPORTED) printf("unsupported\n");
12861       }
12862       assert(0, "NanoVega: framebuffer creation failed");
12863     }
12864   }
12865 
12866   // clear 'em all
12867   if (doclear) {
12868     glClearColor(0, 0, 0, 0);
12869     glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
12870   }
12871 
12872   // save texture ids
12873   gl.fbo.ptr[fidx] = fbo;
12874   gl.fboTex.ptr[fidx].ptr[0] = tidColor;
12875   version(nanovega_shared_stencil) {} else {
12876     gl.fboTex.ptr[fidx].ptr[1] = tidStencil;
12877   }
12878 
12879   static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture);
12880 
12881   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): created with index %d\n", gl.msp-1, fidx); }
12882 
12883   return true;
12884 }
12885 
12886 // will not unbind buffer
12887 void glnvg__clearFBO (GLNVGcontext* gl, int fidx) nothrow @trusted @nogc {
12888   assert(fidx >= 0 && fidx < gl.fbo.length);
12889   assert(gl.fboWidth > 0);
12890   assert(gl.fboHeight > 0);
12891   assert(gl.fbo.ptr[fidx] != 0);
12892   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[fidx]);
12893   glClearColor(0, 0, 0, 0);
12894   glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
12895   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cleared with index %d\n", gl.msp-1, fidx); }
12896 }
12897 
12898 // will not unbind buffer
12899 void glnvg__copyFBOToFrom (GLNVGcontext* gl, int didx, int sidx) nothrow @trusted @nogc {
12900   import core.stdc.string : memset;
12901   assert(didx >= 0 && didx < gl.fbo.length);
12902   assert(sidx >= 0 && sidx < gl.fbo.length);
12903   assert(gl.fboWidth > 0);
12904   assert(gl.fboHeight > 0);
12905   assert(gl.fbo.ptr[didx] != 0);
12906   assert(gl.fbo.ptr[sidx] != 0);
12907   if (didx == sidx) return;
12908 
12909   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copy FBO: %d -> %d\n", gl.msp-1, sidx, didx); }
12910 
12911   glUseProgram(gl.shaderCopyFBO.prog);
12912 
12913   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[didx]);
12914   glDisable(GL_CULL_FACE);
12915   glDisable(GL_BLEND);
12916   glDisable(GL_SCISSOR_TEST);
12917   glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[sidx].ptr[0]);
12918   // copy texture by drawing full quad
12919   enum x = 0;
12920   enum y = 0;
12921   immutable int w = gl.fboWidth;
12922   immutable int h = gl.fboHeight;
12923   immutable(NVGVertex[4]) vertices =
12924    [NVGVertex(x, y), // top-left
12925     NVGVertex(w, y), // top-right
12926     NVGVertex(w, h), // bottom-right
12927     NVGVertex(x, h)]; // bottom-left
12928 
12929   glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices);
12930   glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
12931 
12932   // restore state (but don't unbind FBO)
12933   static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture);
12934   glEnable(GL_CULL_FACE);
12935   glEnable(GL_BLEND);
12936   glUseProgram(gl.shader.prog);
12937 }
12938 
12939 void glnvg__resetFBOClipTextureCache (GLNVGcontext* gl) nothrow @trusted @nogc {
12940   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): texture cache invalidated (%d)\n", gl.msp-1, gl.lastClipFBO); }
12941   /*
12942   if (gl.lastClipFBO >= 0) {
12943     glActiveTexture(GL_TEXTURE1);
12944     glBindTexture(GL_TEXTURE_2D, 0);
12945     glActiveTexture(GL_TEXTURE0);
12946   }
12947   */
12948   gl.lastClipFBO = -666;
12949 }
12950 
12951 void glnvg__setFBOClipTexture (GLNVGcontext* gl, GLNVGfragUniforms* frag) nothrow @trusted @nogc {
12952   //assert(gl.msp > 0 && gl.msp <= gl.maskStack.length);
12953   if (gl.lastClipFBO != -666) {
12954     // cached
12955     version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cached (%d)\n", gl.msp-1, gl.lastClipFBO); }
12956     frag.doclip = (gl.lastClipFBO >= 0 ? 1 : 0);
12957     return;
12958   }
12959 
12960   // no cache
12961   int fboidx = -1;
12962   mainloop: foreach_reverse (immutable sp, GLMaskState mst; gl.maskStack.ptr[0..gl.msp]/*; reverse*/) {
12963     final switch (mst) {
12964       case GLMaskState.DontMask: fboidx = -1; break mainloop;
12965       case GLMaskState.Uninitialized: break;
12966       case GLMaskState.Initialized: fboidx = cast(int)sp; break mainloop;
12967       case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__setFBOClipTexture()` internal error");
12968     }
12969   }
12970 
12971   if (fboidx < 0) {
12972     // don't mask
12973     gl.lastClipFBO = -1;
12974     frag.doclip = 0;
12975   } else {
12976     // do masking
12977     assert(gl.fbo.ptr[fboidx] != 0);
12978     gl.lastClipFBO = fboidx;
12979     frag.doclip = 1;
12980   }
12981 
12982   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): new cache (new:%d)\n", gl.msp-1, gl.lastClipFBO); }
12983 
12984   if (gl.lastClipFBO >= 0) {
12985     assert(gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
12986     glActiveTexture(GL_TEXTURE1);
12987     glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
12988     glActiveTexture(GL_TEXTURE0);
12989   }
12990 }
12991 
12992 // returns index in `gl.fbo`, or -1 for "don't mask"
12993 int glnvg__generateFBOClipTexture (GLNVGcontext* gl) nothrow @trusted @nogc {
12994   assert(gl.msp > 0 && gl.msp <= gl.maskStack.length);
12995   // we need initialized FBO, even for "don't mask" case
12996   // for this, look back in stack, and either copy initialized FBO,
12997   // or stop at first uninitialized one, and clear it
12998   if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.Initialized) {
12999     // shortcut
13000     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); }
13001     glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[gl.msp-1]);
13002     return gl.msp-1;
13003   }
13004   foreach_reverse (immutable sp; 0..gl.msp/*; reverse*/) {
13005     final switch (gl.maskStack.ptr[sp]) {
13006       case GLMaskState.DontMask:
13007         // clear it
13008         version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture\n", gl.msp-1); }
13009         if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1);
13010         gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared;
13011         return gl.msp-1;
13012       case GLMaskState.Uninitialized: break; // do nothing
13013       case GLMaskState.Initialized:
13014         // i found her! copy to TOS
13015         version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copying texture from %d\n", gl.msp-1, cast(int)sp); }
13016         glnvg__allocFBO(gl, gl.msp-1, false);
13017         glnvg__copyFBOToFrom(gl, gl.msp-1, sp);
13018         gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized;
13019         return gl.msp-1;
13020       case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__generateFBOClipTexture()` internal error");
13021     }
13022   }
13023   // nothing was initialized, lol
13024   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture (first one)\n", gl.msp-1); }
13025   if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1);
13026   gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared;
13027   return gl.msp-1;
13028 }
13029 
13030 void glnvg__renderPushClip (void* uptr) nothrow @trusted @nogc {
13031   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13032   GLNVGcall* call = glnvg__allocCall(gl);
13033   if (call is null) return;
13034   call.type = GLNVG_PUSHCLIP;
13035 }
13036 
13037 void glnvg__renderPopClip (void* uptr) nothrow @trusted @nogc {
13038   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13039   GLNVGcall* call = glnvg__allocCall(gl);
13040   if (call is null) return;
13041   call.type = GLNVG_POPCLIP;
13042 }
13043 
13044 void glnvg__renderResetClip (void* uptr) nothrow @trusted @nogc {
13045   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13046   GLNVGcall* call = glnvg__allocCall(gl);
13047   if (call is null) return;
13048   call.type = GLNVG_RESETCLIP;
13049 }
13050 
13051 void glnvg__clipDebugDump (void* uptr, bool doit) nothrow @trusted @nogc {
13052   version(nanovega_debug_clipping) {
13053     GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13054     GLNVGcall* call = glnvg__allocCall(gl);
13055     call.type = (doit ? GLNVG_CLIP_DDUMP_ON : GLNVG_CLIP_DDUMP_OFF);
13056   }
13057 }
13058 
13059 bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc {
13060   import core.stdc.stdio : snprintf;
13061 
13062   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13063   enum align_ = 4;
13064 
13065   char[64] shaderHeader = void;
13066   //enum shaderHeader = "#define UNIFORM_ARRAY_SIZE 12\n";
13067   snprintf(shaderHeader.ptr, shaderHeader.length, "#define UNIFORM_ARRAY_SIZE %u\n", cast(uint)GLNVGfragUniforms.UNIFORM_ARRAY_SIZE);
13068 
13069   enum fillVertShader = q{
13070     uniform vec2 viewSize;
13071     attribute vec2 vertex;
13072     attribute vec2 tcoord;
13073     varying vec2 ftcoord;
13074     varying vec2 fpos;
13075     uniform vec4 tmat; /* abcd of affine matrix: xyzw */
13076     uniform vec2 ttr; /* tx and ty of affine matrix */
13077     void main (void) {
13078       /* affine transformation */
13079       float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x;
13080       float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y;
13081       ftcoord = tcoord;
13082       fpos = vec2(nx, ny);
13083       gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1);
13084     }
13085   };
13086 
13087   enum fillFragShader = `
13088     uniform vec4 frag[UNIFORM_ARRAY_SIZE];
13089     uniform sampler2D tex;
13090     uniform sampler2D clipTex;
13091     uniform vec2 viewSize;
13092     varying vec2 ftcoord;
13093     varying vec2 fpos;
13094     #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz)
13095     #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz)
13096     #define innerCol frag[6]
13097     #define middleCol frag[7]
13098     #define outerCol frag[7+1]
13099     #define scissorExt frag[8+1].xy
13100     #define scissorScale frag[8+1].zw
13101     #define extent frag[9+1].xy
13102     #define radius frag[9+1].z
13103     #define feather frag[9+1].w
13104     #define strokeMult frag[10+1].x
13105     #define strokeThr frag[10+1].y
13106     #define texType int(frag[10+1].z)
13107     #define type int(frag[10+1].w)
13108     #define doclip int(frag[11+1].x)
13109     #define midp frag[11+1].y
13110 
13111     float sdroundrect (in vec2 pt, in vec2 ext, in float rad) {
13112       vec2 ext2 = ext-vec2(rad, rad);
13113       vec2 d = abs(pt)-ext2;
13114       return min(max(d.x, d.y), 0.0)+length(max(d, 0.0))-rad;
13115     }
13116 
13117     // Scissoring
13118     float scissorMask (in vec2 p) {
13119       vec2 sc = (abs((scissorMat*vec3(p, 1.0)).xy)-scissorExt);
13120       sc = vec2(0.5, 0.5)-sc*scissorScale;
13121       return clamp(sc.x, 0.0, 1.0)*clamp(sc.y, 0.0, 1.0);
13122     }
13123 
13124     #ifdef EDGE_AA
13125     // Stroke - from [0..1] to clipped pyramid, where the slope is 1px.
13126     float strokeMask () {
13127       return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult)*min(1.0, ftcoord.y);
13128     }
13129     #endif
13130 
13131     void main (void) {
13132       // clipping
13133       if (doclip != 0) {
13134         /*vec4 clr = texelFetch(clipTex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);*/
13135         vec4 clr = texture2D(clipTex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y));
13136         if (clr.r == 0.0) discard;
13137       }
13138       float scissor = scissorMask(fpos);
13139       if (scissor <= 0.0) discard; //k8: is it really faster?
13140       #ifdef EDGE_AA
13141       float strokeAlpha = strokeMask();
13142       if (strokeAlpha < strokeThr) discard;
13143       #else
13144       float strokeAlpha = 1.0;
13145       #endif
13146       // rendering
13147       vec4 color;
13148       if (type == 0) { /* NSVG_SHADER_FILLCOLOR */
13149         color = innerCol;
13150         // Combine alpha
13151         color *= strokeAlpha*scissor;
13152       } else if (type == 1) { /* NSVG_SHADER_FILLGRAD */
13153         // Gradient
13154         // Calculate gradient color using box gradient
13155         vec2 pt = (paintMat*vec3(fpos, 1.0)).xy;
13156         float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0);
13157         if (midp <= 0.0) {
13158           color = mix(innerCol, outerCol, d);
13159         } else {
13160           float gdst = min(midp, 1.0);
13161           if (d < gdst) {
13162             color = mix(innerCol, middleCol, d/gdst);
13163           } else {
13164             color = mix(middleCol, outerCol, (d-gdst)/gdst);
13165           }
13166         }
13167         // Combine alpha
13168         color *= strokeAlpha*scissor;
13169       } else if (type == 2) { /* NSVG_SHADER_FILLIMG */
13170         // Image
13171         // Calculate color from texture
13172         vec2 pt = (paintMat*vec3(fpos, 1.0)).xy/extent;
13173         color = texture2D(tex, pt);
13174         if (texType == 1) color = vec4(color.xyz*color.w, color.w);
13175         if (texType == 2) color = vec4(color.x);
13176         // Apply color tint and alpha
13177         color *= innerCol;
13178         // Combine alpha
13179         color *= strokeAlpha*scissor;
13180       } else if (type == 3) { /* NSVG_SHADER_SIMPLE */
13181         // Stencil fill
13182         color = vec4(1, 1, 1, 1);
13183       } else if (type == 4) { /* NSVG_SHADER_IMG */
13184         // Textured tris
13185         color = texture2D(tex, ftcoord);
13186         if (texType == 1) color = vec4(color.xyz*color.w, color.w);
13187         if (texType == 2) color = vec4(color.x);
13188         color *= scissor;
13189         color *= innerCol; // Apply color tint
13190       }
13191       gl_FragColor = color;
13192     }
13193   `;
13194 
13195   enum clipVertShaderFill = q{
13196     uniform vec2 viewSize;
13197     attribute vec2 vertex;
13198     uniform vec4 tmat; /* abcd of affine matrix: xyzw */
13199     uniform vec2 ttr; /* tx and ty of affine matrix */
13200     void main (void) {
13201       /* affine transformation */
13202       float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x;
13203       float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y;
13204       gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1);
13205     }
13206   };
13207 
13208   enum clipFragShaderFill = q{
13209     uniform vec2 viewSize;
13210     void main (void) {
13211       gl_FragColor = vec4(1, 1, 1, 1);
13212     }
13213   };
13214 
13215   enum clipVertShaderCopy = q{
13216     uniform vec2 viewSize;
13217     attribute vec2 vertex;
13218     void main (void) {
13219       gl_Position = vec4(2.0*vertex.x/viewSize.x-1.0, 1.0-2.0*vertex.y/viewSize.y, 0, 1);
13220     }
13221   };
13222 
13223   enum clipFragShaderCopy = q{
13224     uniform sampler2D tex;
13225     uniform vec2 viewSize;
13226     void main (void) {
13227       //gl_FragColor = texelFetch(tex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);
13228       gl_FragColor = texture2D(tex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y));
13229     }
13230   };
13231 
13232   glnvg__checkError(gl, "init");
13233 
13234   string defines = (gl.flags&NVGContextFlag.Antialias ? "#define EDGE_AA 1\n" : null);
13235   if (!glnvg__createShader(&gl.shader, "shader", shaderHeader.ptr, defines.ptr, fillVertShader, fillFragShader)) return false;
13236   if (!glnvg__createShader(&gl.shaderFillFBO, "shaderFillFBO", shaderHeader.ptr, defines.ptr, clipVertShaderFill, clipFragShaderFill)) return false;
13237   if (!glnvg__createShader(&gl.shaderCopyFBO, "shaderCopyFBO", shaderHeader.ptr, defines.ptr, clipVertShaderCopy, clipFragShaderCopy)) return false;
13238 
13239   glnvg__checkError(gl, "uniform locations");
13240   glnvg__getUniforms(&gl.shader);
13241   glnvg__getUniforms(&gl.shaderFillFBO);
13242   glnvg__getUniforms(&gl.shaderCopyFBO);
13243 
13244   // Create dynamic vertex array
13245   glGenBuffers(1, &gl.vertBuf);
13246 
13247   gl.fragSize = GLNVGfragUniforms.sizeof+align_-GLNVGfragUniforms.sizeof%align_;
13248 
13249   glnvg__checkError(gl, "create done");
13250 
13251   glFinish();
13252 
13253   return true;
13254 }
13255 
13256 int glnvg__renderCreateTexture (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc {
13257   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13258   GLNVGtexture* tex = glnvg__allocTexture(gl);
13259 
13260   if (tex is null) return 0;
13261 
13262   glGenTextures(1, &tex.tex);
13263   tex.width = w;
13264   tex.height = h;
13265   tex.type = type;
13266   tex.flags = imageFlags;
13267   glnvg__bindTexture(gl, tex.tex);
13268 
13269   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("created texture with id %d; glid=%u\n", tex.id, tex.tex); }}
13270 
13271   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
13272   glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width);
13273   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13274   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13275 
13276 
13277 
13278   immutable ttype = (type == NVGtexture.RGBA ? GL_RGBA : GL_RED);
13279   glTexImage2D(GL_TEXTURE_2D, 0, ttype, w, h, 0, ttype, GL_UNSIGNED_BYTE, data);
13280   // GL 3.0 and later have support for a dedicated function for generating mipmaps
13281   // it needs to be called after the glTexImage2D call
13282   if (imageFlags & NVGImageFlag.GenerateMipmaps)
13283     glGenerateMipmap(GL_TEXTURE_2D);
13284 
13285   immutable tfmin =
13286     (imageFlags & NVGImageFlag.GenerateMipmaps ? GL_LINEAR_MIPMAP_LINEAR :
13287      imageFlags & NVGImageFlag.NoFiltering ? GL_NEAREST :
13288      GL_LINEAR);
13289   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.0);
13290   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tfmin);
13291   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST : GL_LINEAR));
13292 
13293   int flag;
13294   if (imageFlags&NVGImageFlag.RepeatX)
13295     flag = GL_REPEAT;
13296   else if (imageFlags&NVGImageFlag.ClampToBorderX)
13297     flag = GL_CLAMP_TO_BORDER;
13298   else
13299     flag = GL_CLAMP_TO_EDGE;
13300   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, flag);
13301 
13302 
13303   if (imageFlags&NVGImageFlag.RepeatY)
13304     flag = GL_REPEAT;
13305   else if (imageFlags&NVGImageFlag.ClampToBorderY)
13306     flag = GL_CLAMP_TO_BORDER;
13307   else
13308     flag = GL_CLAMP_TO_EDGE;
13309   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, flag);
13310 
13311   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
13312   glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
13313   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13314   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13315 
13316   glnvg__checkError(gl, "create tex");
13317   glnvg__bindTexture(gl, 0);
13318 
13319   return tex.id;
13320 }
13321 
13322 bool glnvg__renderDeleteTexture (void* uptr, int image) nothrow @trusted @nogc {
13323   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13324   return glnvg__deleteTexture(gl, image);
13325 }
13326 
13327 bool glnvg__renderTextureIncRef (void* uptr, int image) nothrow @trusted @nogc {
13328   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13329   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13330   if (tex is null) {
13331     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT incref texture with id %d\n", image); }}
13332     return false;
13333   }
13334   import core.atomic : atomicOp;
13335   atomicOp!"+="(tex.rc, 1);
13336   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("texture #%d: incref; newref=%d\n", image, tex.rc); }}
13337   return true;
13338 }
13339 
13340 bool glnvg__renderUpdateTexture (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc {
13341   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13342   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13343 
13344   if (tex is null) {
13345     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT update texture with id %d\n", image); }}
13346     return false;
13347   }
13348 
13349   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("updated texture with id %d; glid=%u\n", tex.id, image, tex.tex); }}
13350 
13351   glnvg__bindTexture(gl, tex.tex);
13352 
13353   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
13354   glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width);
13355   glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
13356   glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
13357 
13358   immutable ttype = (tex.type == NVGtexture.RGBA ? GL_RGBA : GL_RED);
13359   glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, ttype, GL_UNSIGNED_BYTE, data);
13360 
13361   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
13362   glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
13363   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13364   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13365 
13366   glnvg__bindTexture(gl, 0);
13367 
13368   return true;
13369 }
13370 
13371 bool glnvg__renderGetTextureSize (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc {
13372   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13373   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13374   if (tex is null) {
13375     if (w !is null) *w = 0;
13376     if (h !is null) *h = 0;
13377     return false;
13378   } else {
13379     if (w !is null) *w = tex.width;
13380     if (h !is null) *h = tex.height;
13381     return true;
13382   }
13383 }
13384 
13385 void glnvg__xformToMat3x4 (float[] m3, const(float)[] t) nothrow @trusted @nogc {
13386   assert(t.length >= 6);
13387   assert(m3.length >= 12);
13388   m3.ptr[0] = t.ptr[0];
13389   m3.ptr[1] = t.ptr[1];
13390   m3.ptr[2] = 0.0f;
13391   m3.ptr[3] = 0.0f;
13392   m3.ptr[4] = t.ptr[2];
13393   m3.ptr[5] = t.ptr[3];
13394   m3.ptr[6] = 0.0f;
13395   m3.ptr[7] = 0.0f;
13396   m3.ptr[8] = t.ptr[4];
13397   m3.ptr[9] = t.ptr[5];
13398   m3.ptr[10] = 1.0f;
13399   m3.ptr[11] = 0.0f;
13400 }
13401 
13402 NVGColor glnvg__premulColor() (const scope auto ref NVGColor c) nothrow @trusted @nogc {
13403   //pragma(inline, true);
13404   NVGColor res = void;
13405   res.r = c.r*c.a;
13406   res.g = c.g*c.a;
13407   res.b = c.b*c.a;
13408   res.a = c.a;
13409   return res;
13410 }
13411 
13412 bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* paint, NVGscissor* scissor, float width, float fringe, float strokeThr) nothrow @trusted @nogc {
13413   import core.stdc.math : sqrtf;
13414   GLNVGtexture* tex = null;
13415   NVGMatrix invxform = void;
13416 
13417   memset(frag, 0, (*frag).sizeof);
13418 
13419   frag.innerCol = glnvg__premulColor(paint.innerColor);
13420   frag.middleCol = glnvg__premulColor(paint.middleColor);
13421   frag.outerCol = glnvg__premulColor(paint.outerColor);
13422   frag.midp = paint.midp;
13423 
13424   if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) {
13425     memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof);
13426     frag.scissorExt.ptr[0] = 1.0f;
13427     frag.scissorExt.ptr[1] = 1.0f;
13428     frag.scissorScale.ptr[0] = 1.0f;
13429     frag.scissorScale.ptr[1] = 1.0f;
13430   } else {
13431     //nvgTransformInverse(invxform[], scissor.xform[]);
13432     invxform = scissor.xform.inverted;
13433     glnvg__xformToMat3x4(frag.scissorMat[], invxform.mat[]);
13434     frag.scissorExt.ptr[0] = scissor.extent.ptr[0];
13435     frag.scissorExt.ptr[1] = scissor.extent.ptr[1];
13436     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;
13437     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;
13438   }
13439 
13440   memcpy(frag.extent.ptr, paint.extent.ptr, frag.extent.sizeof);
13441   frag.strokeMult = (width*0.5f+fringe*0.5f)/fringe;
13442   frag.strokeThr = strokeThr;
13443 
13444   if (paint.image.valid) {
13445     tex = glnvg__findTexture(gl, paint.image.id);
13446     if (tex is null) return false;
13447     if ((tex.flags&NVGImageFlag.FlipY) != 0) {
13448       /*
13449       NVGMatrix flipped;
13450       nvgTransformScale(flipped[], 1.0f, -1.0f);
13451       nvgTransformMultiply(flipped[], paint.xform[]);
13452       nvgTransformInverse(invxform[], flipped[]);
13453       */
13454       /*
13455       NVGMatrix m1 = void, m2 = void;
13456       nvgTransformTranslate(m1[], 0.0f, frag.extent.ptr[1]*0.5f);
13457       nvgTransformMultiply(m1[], paint.xform[]);
13458       nvgTransformScale(m2[], 1.0f, -1.0f);
13459       nvgTransformMultiply(m2[], m1[]);
13460       nvgTransformTranslate(m1[], 0.0f, -frag.extent.ptr[1]*0.5f);
13461       nvgTransformMultiply(m1[], m2[]);
13462       nvgTransformInverse(invxform[], m1[]);
13463       */
13464       NVGMatrix m1 = NVGMatrix.Translated(0.0f, frag.extent.ptr[1]*0.5f);
13465       m1.mul(paint.xform);
13466       NVGMatrix m2 = NVGMatrix.Scaled(1.0f, -1.0f);
13467       m2.mul(m1);
13468       m1 = NVGMatrix.Translated(0.0f, -frag.extent.ptr[1]*0.5f);
13469       m1.mul(m2);
13470       invxform = m1.inverted;
13471     } else {
13472       //nvgTransformInverse(invxform[], paint.xform[]);
13473       invxform = paint.xform.inverted;
13474     }
13475     frag.type = NSVG_SHADER_FILLIMG;
13476 
13477     if (tex.type == NVGtexture.RGBA) {
13478       frag.texType = (tex.flags&NVGImageFlag.Premultiplied ? 0 : 1);
13479     } else {
13480       frag.texType = 2;
13481     }
13482     //printf("frag.texType = %d\n", frag.texType);
13483   } else {
13484     frag.type = (paint.simpleColor ? NSVG_SHADER_FILLCOLOR : NSVG_SHADER_FILLGRAD);
13485     frag.radius = paint.radius;
13486     frag.feather = paint.feather;
13487     //nvgTransformInverse(invxform[], paint.xform[]);
13488     invxform = paint.xform.inverted;
13489   }
13490 
13491   glnvg__xformToMat3x4(frag.paintMat[], invxform.mat[]);
13492 
13493   return true;
13494 }
13495 
13496 void glnvg__setUniforms (GLNVGcontext* gl, int uniformOffset, int image) nothrow @trusted @nogc {
13497   GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset);
13498   glnvg__setFBOClipTexture(gl, frag);
13499   glUniform4fv(gl.shader.loc[GLNVGuniformLoc.Frag], frag.UNIFORM_ARRAY_SIZE, &(frag.uniformArray.ptr[0].ptr[0]));
13500   glnvg__checkError(gl, "glnvg__setUniforms");
13501   if (image != 0) {
13502     GLNVGtexture* tex = glnvg__findTexture(gl, image);
13503     glnvg__bindTexture(gl, (tex !is null ? tex.tex : 0));
13504     glnvg__checkError(gl, "tex paint tex");
13505   } else {
13506     glnvg__bindTexture(gl, 0);
13507   }
13508 }
13509 
13510 void glnvg__finishClip (GLNVGcontext* gl, NVGClipMode clipmode) nothrow @trusted @nogc {
13511   assert(clipmode != NVGClipMode.None);
13512 
13513   // fill FBO, clear stencil buffer
13514   //TODO: optimize with bounds?
13515   version(all) {
13516     //glnvg__resetAffine(gl);
13517     //glUseProgram(gl.shaderFillFBO.prog);
13518     glDisable(GL_CULL_FACE);
13519     glDisable(GL_BLEND);
13520     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13521     glEnable(GL_STENCIL_TEST);
13522     if (gl.doClipUnion) {
13523       // for "and" we should clear everything that is NOT stencil-masked
13524       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13525       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13526     } else {
13527       glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x00, 0xff);
13528       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13529     }
13530 
13531     immutable(NVGVertex[4]) vertices =
13532      [NVGVertex(0, 0, 0, 0),
13533       NVGVertex(0, gl.fboHeight, 0, 0),
13534       NVGVertex(gl.fboWidth, gl.fboHeight, 0, 0),
13535       NVGVertex(gl.fboWidth, 0, 0, 0)];
13536 
13537     glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices);
13538     glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
13539 
13540     //glnvg__restoreAffine(gl);
13541   }
13542 
13543   glBindFramebuffer(GL_FRAMEBUFFER, gl.mainFBO);
13544   glDisable(GL_COLOR_LOGIC_OP);
13545   //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // done above
13546   glEnable(GL_BLEND);
13547   glDisable(GL_STENCIL_TEST);
13548   glEnable(GL_CULL_FACE);
13549   glUseProgram(gl.shader.prog);
13550 
13551   // set current FBO as used one
13552   assert(gl.msp > 0 && gl.fbo.ptr[gl.msp-1] > 0 && gl.fboTex.ptr[gl.msp-1].ptr[0] > 0);
13553   if (gl.lastClipFBO != gl.msp-1) {
13554     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); }
13555     gl.lastClipFBO = gl.msp-1;
13556     glActiveTexture(GL_TEXTURE1);
13557     glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
13558     glActiveTexture(GL_TEXTURE0);
13559   }
13560 }
13561 
13562 void glnvg__setClipUniforms (GLNVGcontext* gl, int uniformOffset, NVGClipMode clipmode) nothrow @trusted @nogc {
13563   assert(clipmode != NVGClipMode.None);
13564   GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset);
13565   // save uniform offset for `glnvg__finishClip()`
13566   gl.lastClipUniOfs = uniformOffset;
13567   // get FBO index, bind this FBO
13568   immutable int clipTexId = glnvg__generateFBOClipTexture(gl);
13569   assert(clipTexId >= 0);
13570   glUseProgram(gl.shaderFillFBO.prog);
13571   glnvg__checkError(gl, "use");
13572   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[clipTexId]);
13573   // set logic op for clip
13574   gl.doClipUnion = false;
13575   if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.JustCleared) {
13576     // it is cleared to zero, we can just draw a path
13577     glDisable(GL_COLOR_LOGIC_OP);
13578     gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized;
13579   } else {
13580     glEnable(GL_COLOR_LOGIC_OP);
13581     final switch (clipmode) {
13582       case NVGClipMode.None: assert(0, "wtf?!");
13583       case NVGClipMode.Union: glLogicOp(GL_CLEAR); gl.doClipUnion = true; break; // use `GL_CLEAR` to avoid adding another shader mode
13584       case NVGClipMode.Or: glLogicOp(GL_COPY); break; // GL_OR
13585       case NVGClipMode.Xor: glLogicOp(GL_XOR); break;
13586       case NVGClipMode.Sub: glLogicOp(GL_CLEAR); break;
13587       case NVGClipMode.Replace: glLogicOp(GL_COPY); break;
13588     }
13589   }
13590   // set affine matrix
13591   glUniform4fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TMat], 1, gl.lastAffine.mat.ptr);
13592   glnvg__checkError(gl, "affine 0");
13593   glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TTr], 1, gl.lastAffine.mat.ptr+4);
13594   glnvg__checkError(gl, "affine 1");
13595   // setup common OpenGL parameters
13596   glDisable(GL_BLEND);
13597   glDisable(GL_CULL_FACE);
13598   glEnable(GL_STENCIL_TEST);
13599   glnvg__stencilMask(gl, 0xff);
13600   glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13601   glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
13602   glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13603 }
13604 
13605 void glnvg__renderViewport (void* uptr, int width, int height) nothrow @trusted @nogc {
13606   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13607   gl.inFrame = true;
13608   gl.view.ptr[0] = cast(float)width;
13609   gl.view.ptr[1] = cast(float)height;
13610   // kill FBOs if we need to create new ones (flushing will recreate 'em if necessary)
13611   if (width != gl.fboWidth || height != gl.fboHeight) {
13612     glnvg__killFBOs(gl);
13613     gl.fboWidth = width;
13614     gl.fboHeight = height;
13615   }
13616   gl.msp = 1;
13617   gl.maskStack.ptr[0] = GLMaskState.DontMask;
13618   // texture cleanup
13619   import core.atomic : atomicLoad;
13620   if (atomicLoad(gl.mustCleanTextures)) {
13621     try {
13622       import core.thread : Thread;
13623       static if (__VERSION__ < 2076) {
13624         DGNoThrowNoGC(() {
13625           if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13626         })();
13627       } else {
13628         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13629       }
13630       {
13631         GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow;
13632         gl.mustCleanTextures = false;
13633         foreach (immutable tidx, ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) {
13634           // no need to use atomic ops here, as we're locked
13635           if (tex.rc == 0 && tex.tex != 0 && tex.id == 0) {
13636             version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** cleaned up texture with glid=%u\n", tex.tex); }}
13637             import core.stdc.string : memset;
13638             if ((tex.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tex.tex);
13639             memset(&tex, 0, tex.sizeof);
13640             tex.nextfree = gl.freetexid;
13641             gl.freetexid = cast(int)tidx;
13642           }
13643         }
13644       }
13645     } catch (Exception e) {}
13646   }
13647 }
13648 
13649 void glnvg__fill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13650   GLNVGpath* paths = &gl.paths[call.pathOffset];
13651   int npaths = call.pathCount;
13652 
13653   if (call.clipmode == NVGClipMode.None) {
13654     // Draw shapes
13655     glEnable(GL_STENCIL_TEST);
13656     glnvg__stencilMask(gl, 0xffU);
13657     glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xffU);
13658 
13659     glnvg__setUniforms(gl, call.uniformOffset, 0);
13660     glnvg__checkError(gl, "fill simple");
13661 
13662     glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13663     if (call.evenOdd) {
13664       //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT);
13665       //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT);
13666       glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
13667     } else {
13668       glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
13669       glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
13670     }
13671     glDisable(GL_CULL_FACE);
13672     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13673     glEnable(GL_CULL_FACE);
13674 
13675     // Draw anti-aliased pixels
13676     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13677     glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image);
13678     glnvg__checkError(gl, "fill fill");
13679 
13680     if (gl.flags&NVGContextFlag.Antialias) {
13681       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xffU);
13682       glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13683       // Draw fringes
13684       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13685     }
13686 
13687     // Draw fill
13688     glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xffU);
13689     glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13690     if (call.evenOdd) {
13691       glDisable(GL_CULL_FACE);
13692       glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount);
13693       //foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13694       glEnable(GL_CULL_FACE);
13695     } else {
13696       glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount);
13697     }
13698 
13699     glDisable(GL_STENCIL_TEST);
13700   } else {
13701     glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); // this activates our FBO
13702     glnvg__checkError(gl, "fillclip simple");
13703     glnvg__stencilFunc(gl, GL_ALWAYS, 0x00, 0xffU);
13704     if (call.evenOdd) {
13705       //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT);
13706       //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT);
13707       glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
13708     } else {
13709       glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
13710       glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
13711     }
13712     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13713     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13714   }
13715 }
13716 
13717 void glnvg__convexFill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13718   GLNVGpath* paths = &gl.paths[call.pathOffset];
13719   int npaths = call.pathCount;
13720 
13721   if (call.clipmode == NVGClipMode.None) {
13722     glnvg__setUniforms(gl, call.uniformOffset, call.image);
13723     glnvg__checkError(gl, "convex fill");
13724     if (call.evenOdd) glDisable(GL_CULL_FACE);
13725     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13726     if (gl.flags&NVGContextFlag.Antialias) {
13727       // Draw fringes
13728       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13729     }
13730     if (call.evenOdd) glEnable(GL_CULL_FACE);
13731   } else {
13732     glnvg__setClipUniforms(gl, call.uniformOffset, call.clipmode); // this activates our FBO
13733     glnvg__checkError(gl, "clip convex fill");
13734     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13735     if (gl.flags&NVGContextFlag.Antialias) {
13736       // Draw fringes
13737       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13738     }
13739     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13740   }
13741 }
13742 
13743 void glnvg__stroke (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13744   GLNVGpath* paths = &gl.paths[call.pathOffset];
13745   int npaths = call.pathCount;
13746 
13747   if (call.clipmode == NVGClipMode.None) {
13748     if (gl.flags&NVGContextFlag.StencilStrokes) {
13749       glEnable(GL_STENCIL_TEST);
13750       glnvg__stencilMask(gl, 0xff);
13751 
13752       // Fill the stroke base without overlap
13753       glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff);
13754       glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
13755       glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image);
13756       glnvg__checkError(gl, "stroke fill 0");
13757       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13758 
13759       // Draw anti-aliased pixels.
13760       glnvg__setUniforms(gl, call.uniformOffset, call.image);
13761       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13762       glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13763       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13764 
13765       // Clear stencil buffer.
13766       glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13767       glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff);
13768       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13769       glnvg__checkError(gl, "stroke fill 1");
13770       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13771       glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13772 
13773       glDisable(GL_STENCIL_TEST);
13774 
13775       //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f);
13776     } else {
13777       glnvg__setUniforms(gl, call.uniformOffset, call.image);
13778       glnvg__checkError(gl, "stroke fill");
13779       // Draw Strokes
13780       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13781     }
13782   } else {
13783     glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode);
13784     glnvg__checkError(gl, "stroke fill 0");
13785     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13786     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13787   }
13788 }
13789 
13790 void glnvg__triangles (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13791   if (call.clipmode == NVGClipMode.None) {
13792     glnvg__setUniforms(gl, call.uniformOffset, call.image);
13793     glnvg__checkError(gl, "triangles fill");
13794     glDrawArrays(GL_TRIANGLES, call.triangleOffset, call.triangleCount);
13795   } else {
13796     //TODO(?): use texture as mask?
13797   }
13798 }
13799 
13800 void glnvg__affine (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13801   glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, call.affine.mat.ptr);
13802   glnvg__checkError(gl, "affine");
13803   glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, call.affine.mat.ptr+4);
13804   glnvg__checkError(gl, "affine");
13805   //glnvg__setUniforms(gl, call.uniformOffset, call.image);
13806 }
13807 
13808 void glnvg__renderCancelInternal (GLNVGcontext* gl, bool clearTextures) nothrow @trusted @nogc {
13809   scope(exit) gl.inFrame = false;
13810   if (clearTextures && gl.inFrame) {
13811     try {
13812       import core.thread : Thread;
13813       static if (__VERSION__ < 2076) {
13814         DGNoThrowNoGC(() {
13815           if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13816         })();
13817       } else {
13818         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13819       }
13820     } catch (Exception e) {}
13821     foreach (ref GLNVGcall c; gl.calls[0..gl.ncalls]) if (c.image > 0) glnvg__deleteTexture(gl, c.image);
13822   }
13823   gl.nverts = 0;
13824   gl.npaths = 0;
13825   gl.ncalls = 0;
13826   gl.nuniforms = 0;
13827   gl.msp = 1;
13828   gl.maskStack.ptr[0] = GLMaskState.DontMask;
13829 }
13830 
13831 void glnvg__renderCancel (void* uptr) nothrow @trusted @nogc {
13832   glnvg__renderCancelInternal(cast(GLNVGcontext*)uptr, true);
13833 }
13834 
13835 GLenum glnvg_convertBlendFuncFactor (NVGBlendFactor factor) pure nothrow @trusted @nogc {
13836   if (factor == NVGBlendFactor.Zero) return GL_ZERO;
13837   if (factor == NVGBlendFactor.One) return GL_ONE;
13838   if (factor == NVGBlendFactor.SrcColor) return GL_SRC_COLOR;
13839   if (factor == NVGBlendFactor.OneMinusSrcColor) return GL_ONE_MINUS_SRC_COLOR;
13840   if (factor == NVGBlendFactor.DstColor) return GL_DST_COLOR;
13841   if (factor == NVGBlendFactor.OneMinusDstColor) return GL_ONE_MINUS_DST_COLOR;
13842   if (factor == NVGBlendFactor.SrcAlpha) return GL_SRC_ALPHA;
13843   if (factor == NVGBlendFactor.OneMinusSrcAlpha) return GL_ONE_MINUS_SRC_ALPHA;
13844   if (factor == NVGBlendFactor.DstAlpha) return GL_DST_ALPHA;
13845   if (factor == NVGBlendFactor.OneMinusDstAlpha) return GL_ONE_MINUS_DST_ALPHA;
13846   if (factor == NVGBlendFactor.SrcAlphaSaturate) return GL_SRC_ALPHA_SATURATE;
13847   return GL_INVALID_ENUM;
13848 }
13849 
13850 GLNVGblend glnvg__buildBlendFunc (NVGCompositeOperationState op) pure nothrow @trusted @nogc {
13851   GLNVGblend res;
13852   res.simple = op.simple;
13853   res.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB);
13854   res.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB);
13855   res.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha);
13856   res.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha);
13857   if (res.simple) {
13858     if (res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) {
13859       res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM;
13860     }
13861   } else {
13862     if (res.srcRGB == GL_INVALID_ENUM || res.dstRGB == GL_INVALID_ENUM || res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) {
13863       res.simple = true;
13864       res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM;
13865     }
13866   }
13867   return res;
13868 }
13869 
13870 void glnvg__blendCompositeOperation() (GLNVGcontext* gl, const scope auto ref GLNVGblend op) nothrow @trusted @nogc {
13871   //glBlendFuncSeparate(glnvg_convertBlendFuncFactor(op.srcRGB), glnvg_convertBlendFuncFactor(op.dstRGB), glnvg_convertBlendFuncFactor(op.srcAlpha), glnvg_convertBlendFuncFactor(op.dstAlpha));
13872   static if (NANOVG_GL_USE_STATE_FILTER) {
13873     if (gl.blendFunc.simple == op.simple) {
13874       if (op.simple) {
13875         if (gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return;
13876       } else {
13877         if (gl.blendFunc.srcRGB == op.srcRGB && gl.blendFunc.dstRGB == op.dstRGB && gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return;
13878       }
13879     }
13880     gl.blendFunc = op;
13881   }
13882   if (op.simple) {
13883     if (op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) {
13884       glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13885     } else {
13886       glBlendFunc(op.srcAlpha, op.dstAlpha);
13887     }
13888   } else {
13889     if (op.srcRGB == GL_INVALID_ENUM || op.dstRGB == GL_INVALID_ENUM || op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) {
13890       glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13891     } else {
13892       glBlendFuncSeparate(op.srcRGB, op.dstRGB, op.srcAlpha, op.dstAlpha);
13893     }
13894   }
13895 }
13896 
13897 void glnvg__renderSetAffine (void* uptr, const scope ref NVGMatrix mat) nothrow @trusted @nogc {
13898   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13899   GLNVGcall* call;
13900   // if last operation was GLNVG_AFFINE, simply replace the matrix
13901   if (gl.ncalls > 0 && gl.calls[gl.ncalls-1].type == GLNVG_AFFINE) {
13902     call = &gl.calls[gl.ncalls-1];
13903   } else {
13904     call = glnvg__allocCall(gl);
13905     if (call is null) return;
13906     call.type = GLNVG_AFFINE;
13907   }
13908   call.affine.mat.ptr[0..6] = mat.mat.ptr[0..6];
13909 }
13910 
13911 version(nanovega_debug_clipping) public __gshared bool nanovegaClipDebugDump = false;
13912 
13913 void glnvg__renderFlush (void* uptr) nothrow @trusted @nogc {
13914   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13915   if (!gl.inFrame) assert(0, "NanoVega: internal driver error");
13916   try {
13917     import core.thread : Thread;
13918     static if (__VERSION__ < 2076) {
13919       DGNoThrowNoGC(() {
13920         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13921       })();
13922     } else {
13923       if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13924     }
13925   } catch (Exception e) {}
13926   scope(exit) gl.inFrame = false;
13927 
13928   glnvg__resetError!true(gl);
13929   {
13930     int vv = 0;
13931     glGetIntegerv(GL_FRAMEBUFFER_BINDING, &vv);
13932     if (glGetError() || vv < 0) vv = 0;
13933     gl.mainFBO = cast(uint)vv;
13934   }
13935 
13936   enum ShaderType { None, Fill, Clip }
13937   auto lastShader = ShaderType.None;
13938   if (gl.ncalls > 0) {
13939     gl.msp = 1;
13940     gl.maskStack.ptr[0] = GLMaskState.DontMask;
13941 
13942     // Setup require GL state.
13943     glUseProgram(gl.shader.prog);
13944 
13945     glActiveTexture(GL_TEXTURE1);
13946     glBindTexture(GL_TEXTURE_2D, 0);
13947     glActiveTexture(GL_TEXTURE0);
13948     glnvg__resetFBOClipTextureCache(gl);
13949 
13950     //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13951     static if (NANOVG_GL_USE_STATE_FILTER) {
13952       gl.blendFunc.simple = true;
13953       gl.blendFunc.srcRGB = gl.blendFunc.dstRGB = gl.blendFunc.srcAlpha = gl.blendFunc.dstAlpha = GL_INVALID_ENUM;
13954     }
13955     glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // just in case
13956     glEnable(GL_CULL_FACE);
13957     glCullFace(GL_BACK);
13958     glFrontFace(GL_CCW);
13959     glEnable(GL_BLEND);
13960     glDisable(GL_DEPTH_TEST);
13961     glDisable(GL_SCISSOR_TEST);
13962     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13963     glStencilMask(0xffffffff);
13964     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13965     glStencilFunc(GL_ALWAYS, 0, 0xffffffff);
13966     glActiveTexture(GL_TEXTURE0);
13967     glBindTexture(GL_TEXTURE_2D, 0);
13968     static if (NANOVG_GL_USE_STATE_FILTER) {
13969       gl.boundTexture = 0;
13970       gl.stencilMask = 0xffffffff;
13971       gl.stencilFunc = GL_ALWAYS;
13972       gl.stencilFuncRef = 0;
13973       gl.stencilFuncMask = 0xffffffff;
13974     }
13975     glnvg__checkError(gl, "OpenGL setup");
13976 
13977     // Upload vertex data
13978     glBindBuffer(GL_ARRAY_BUFFER, gl.vertBuf);
13979     // ensure that there's space for at least 4 vertices in case we need to draw a quad (e. g. framebuffer copy)
13980     glBufferData(GL_ARRAY_BUFFER, (gl.nverts < 4 ? 4 : gl.nverts) * NVGVertex.sizeof, gl.verts, GL_STREAM_DRAW);
13981     glEnableVertexAttribArray(0);
13982     glEnableVertexAttribArray(1);
13983     glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)cast(usize)0);
13984     glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)(0+2*float.sizeof));
13985     glnvg__checkError(gl, "vertex data uploading");
13986 
13987     // Set view and texture just once per frame.
13988     glUniform1i(gl.shader.loc[GLNVGuniformLoc.Tex], 0);
13989     if (gl.shader.loc[GLNVGuniformLoc.ClipTex] != -1) {
13990       //{ import core.stdc.stdio; printf("%d\n", gl.shader.loc[GLNVGuniformLoc.ClipTex]); }
13991       glUniform1i(gl.shader.loc[GLNVGuniformLoc.ClipTex], 1);
13992     }
13993     if (gl.shader.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shader.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
13994     glnvg__checkError(gl, "render shader setup");
13995 
13996     // Reset affine transformations.
13997     glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, NVGMatrix.IdentityMat.ptr);
13998     glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, NVGMatrix.IdentityMat.ptr+4);
13999     glnvg__checkError(gl, "affine setup");
14000 
14001     // set clip shaders params
14002     // fill
14003     glUseProgram(gl.shaderFillFBO.prog);
14004     glnvg__checkError(gl, "clip shaders setup (fill 0)");
14005     if (gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
14006     glnvg__checkError(gl, "clip shaders setup (fill 1)");
14007     // copy
14008     glUseProgram(gl.shaderCopyFBO.prog);
14009     glnvg__checkError(gl, "clip shaders setup (copy 0)");
14010     if (gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
14011     glnvg__checkError(gl, "clip shaders setup (copy 1)");
14012     //glUniform1i(gl.shaderFillFBO.loc[GLNVGuniformLoc.Tex], 0);
14013     glUniform1i(gl.shaderCopyFBO.loc[GLNVGuniformLoc.Tex], 0);
14014     glnvg__checkError(gl, "clip shaders setup (copy 2)");
14015     // restore render shader
14016     glUseProgram(gl.shader.prog);
14017 
14018     //{ 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]); }
14019 
14020     gl.lastAffine.identity;
14021 
14022     foreach (int i; 0..gl.ncalls) {
14023       GLNVGcall* call = &gl.calls[i];
14024       switch (call.type) {
14025         case GLNVG_FILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__fill(gl, call); break;
14026         case GLNVG_CONVEXFILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__convexFill(gl, call); break;
14027         case GLNVG_STROKE: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__stroke(gl, call); break;
14028         case GLNVG_TRIANGLES: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__triangles(gl, call); break;
14029         case GLNVG_AFFINE: gl.lastAffine = call.affine; glnvg__affine(gl, call); break;
14030         // clip region management
14031         case GLNVG_PUSHCLIP:
14032           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]); }
14033           if (gl.msp >= gl.maskStack.length) assert(0, "NanoVega: mask stack overflow in OpenGL backend");
14034           if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.DontMask) {
14035             gl.maskStack.ptr[gl.msp++] = GLMaskState.DontMask;
14036           } else {
14037             gl.maskStack.ptr[gl.msp++] = GLMaskState.Uninitialized;
14038           }
14039           // no need to reset FBO cache here, as nothing was changed
14040           break;
14041         case GLNVG_POPCLIP:
14042           if (gl.msp <= 1) assert(0, "NanoVega: mask stack underflow in OpenGL backend");
14043           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]); }
14044           --gl.msp;
14045           assert(gl.msp > 0);
14046           //{ import core.stdc.stdio; printf("popped; new msp is %d; state is %d\n", gl.msp, gl.maskStack.ptr[gl.msp]); }
14047           // check popped item
14048           final switch (gl.maskStack.ptr[gl.msp]) {
14049             case GLMaskState.DontMask:
14050               // if last FBO was "don't mask", reset cache if current is not "don't mask"
14051               if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) {
14052                 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14053                 glnvg__resetFBOClipTextureCache(gl);
14054               }
14055               break;
14056             case GLMaskState.Uninitialized:
14057               // if last FBO texture was uninitialized, it means that nothing was changed,
14058               // so we can keep using cached FBO
14059               break;
14060             case GLMaskState.Initialized:
14061               // if last FBO was initialized, it means that something was definitely changed
14062               version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14063               glnvg__resetFBOClipTextureCache(gl);
14064               break;
14065             case GLMaskState.JustCleared: assert(0, "NanoVega: internal FBO stack error");
14066           }
14067           break;
14068         case GLNVG_RESETCLIP:
14069           // mark current mask as "don't mask"
14070           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]); }
14071           if (gl.msp > 0) {
14072             if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) {
14073               gl.maskStack.ptr[gl.msp-1] = GLMaskState.DontMask;
14074               version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14075               glnvg__resetFBOClipTextureCache(gl);
14076             }
14077           }
14078           break;
14079         case GLNVG_CLIP_DDUMP_ON:
14080           version(nanovega_debug_clipping) nanovegaClipDebugDump = true;
14081           break;
14082         case GLNVG_CLIP_DDUMP_OFF:
14083           version(nanovega_debug_clipping) nanovegaClipDebugDump = false;
14084           break;
14085         case GLNVG_NONE: break;
14086         default:
14087           {
14088             import core.stdc.stdio; stderr.fprintf("NanoVega FATAL: invalid command in OpenGL backend: %d\n", call.type);
14089           }
14090           assert(0, "NanoVega: invalid command in OpenGL backend (fatal internal error)");
14091       }
14092       // and free texture, why not
14093       glnvg__deleteTexture(gl, call.image);
14094     }
14095 
14096     glDisableVertexAttribArray(0);
14097     glDisableVertexAttribArray(1);
14098     glDisable(GL_CULL_FACE);
14099     glBindBuffer(GL_ARRAY_BUFFER, 0);
14100     glUseProgram(0);
14101     glnvg__bindTexture(gl, 0);
14102   }
14103 
14104   // this will do all necessary cleanup
14105   glnvg__renderCancelInternal(gl, false); // no need to clear textures
14106 }
14107 
14108 int glnvg__maxVertCount (const(NVGpath)* paths, int npaths) nothrow @trusted @nogc {
14109   int count = 0;
14110   foreach (int i; 0..npaths) {
14111     count += paths[i].nfill;
14112     count += paths[i].nstroke;
14113   }
14114   return count;
14115 }
14116 
14117 GLNVGcall* glnvg__allocCall (GLNVGcontext* gl) nothrow @trusted @nogc {
14118   GLNVGcall* ret = null;
14119   if (gl.ncalls+1 > gl.ccalls) {
14120     GLNVGcall* calls;
14121     int ccalls = glnvg__maxi(gl.ncalls+1, 128)+gl.ccalls/2; // 1.5x Overallocate
14122     calls = cast(GLNVGcall*)realloc(gl.calls, GLNVGcall.sizeof*ccalls);
14123     if (calls is null) return null;
14124     gl.calls = calls;
14125     gl.ccalls = ccalls;
14126   }
14127   ret = &gl.calls[gl.ncalls++];
14128   memset(ret, 0, GLNVGcall.sizeof);
14129   return ret;
14130 }
14131 
14132 int glnvg__allocPaths (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14133   int ret = 0;
14134   if (gl.npaths+n > gl.cpaths) {
14135     GLNVGpath* paths;
14136     int cpaths = glnvg__maxi(gl.npaths+n, 128)+gl.cpaths/2; // 1.5x Overallocate
14137     paths = cast(GLNVGpath*)realloc(gl.paths, GLNVGpath.sizeof*cpaths);
14138     if (paths is null) return -1;
14139     gl.paths = paths;
14140     gl.cpaths = cpaths;
14141   }
14142   ret = gl.npaths;
14143   gl.npaths += n;
14144   return ret;
14145 }
14146 
14147 int glnvg__allocVerts (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14148   int ret = 0;
14149   if (gl.nverts+n > gl.cverts) {
14150     NVGVertex* verts;
14151     int cverts = glnvg__maxi(gl.nverts+n, 4096)+gl.cverts/2; // 1.5x Overallocate
14152     verts = cast(NVGVertex*)realloc(gl.verts, NVGVertex.sizeof*cverts);
14153     if (verts is null) return -1;
14154     gl.verts = verts;
14155     gl.cverts = cverts;
14156   }
14157   ret = gl.nverts;
14158   gl.nverts += n;
14159   return ret;
14160 }
14161 
14162 int glnvg__allocFragUniforms (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14163   int ret = 0, structSize = gl.fragSize;
14164   if (gl.nuniforms+n > gl.cuniforms) {
14165     ubyte* uniforms;
14166     int cuniforms = glnvg__maxi(gl.nuniforms+n, 128)+gl.cuniforms/2; // 1.5x Overallocate
14167     uniforms = cast(ubyte*)realloc(gl.uniforms, structSize*cuniforms);
14168     if (uniforms is null) return -1;
14169     gl.uniforms = uniforms;
14170     gl.cuniforms = cuniforms;
14171   }
14172   ret = gl.nuniforms*structSize;
14173   gl.nuniforms += n;
14174   return ret;
14175 }
14176 
14177 GLNVGfragUniforms* nvg__fragUniformPtr (GLNVGcontext* gl, int i) nothrow @trusted @nogc {
14178   return cast(GLNVGfragUniforms*)&gl.uniforms[i];
14179 }
14180 
14181 void glnvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc {
14182   vtx.x = x;
14183   vtx.y = y;
14184   vtx.u = u;
14185   vtx.v = v;
14186 }
14187 
14188 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 {
14189   if (npaths < 1) return;
14190 
14191   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14192   GLNVGcall* call = glnvg__allocCall(gl);
14193   NVGVertex* quad;
14194   GLNVGfragUniforms* frag;
14195   int maxverts, offset;
14196 
14197   if (call is null) return;
14198 
14199   call.type = GLNVG_FILL;
14200   call.evenOdd = evenOdd;
14201   call.clipmode = clipmode;
14202   //if (clipmode != NVGClipMode.None) { import core.stdc.stdio; printf("CLIP!\n"); }
14203   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14204   call.triangleCount = 4;
14205   call.pathOffset = glnvg__allocPaths(gl, npaths);
14206   if (call.pathOffset == -1) goto error;
14207   call.pathCount = npaths;
14208   call.image = paint.image.id;
14209   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14210 
14211   if (npaths == 1 && paths[0].convex) {
14212     call.type = GLNVG_CONVEXFILL;
14213     call.triangleCount = 0; // Bounding box fill quad not needed for convex fill
14214   }
14215 
14216   // Allocate vertices for all the paths.
14217   maxverts = glnvg__maxVertCount(paths, npaths)+call.triangleCount;
14218   offset = glnvg__allocVerts(gl, maxverts);
14219   if (offset == -1) goto error;
14220 
14221   foreach (int i; 0..npaths) {
14222     GLNVGpath* copy = &gl.paths[call.pathOffset+i];
14223     const(NVGpath)* path = &paths[i];
14224     memset(copy, 0, GLNVGpath.sizeof);
14225     if (path.nfill > 0) {
14226       copy.fillOffset = offset;
14227       copy.fillCount = path.nfill;
14228       memcpy(&gl.verts[offset], path.fill, NVGVertex.sizeof*path.nfill);
14229       offset += path.nfill;
14230     }
14231     if (path.nstroke > 0) {
14232       copy.strokeOffset = offset;
14233       copy.strokeCount = path.nstroke;
14234       memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke);
14235       offset += path.nstroke;
14236     }
14237   }
14238 
14239   // Setup uniforms for draw calls
14240   if (call.type == GLNVG_FILL) {
14241     import core.stdc.string : memcpy;
14242     // Quad
14243     call.triangleOffset = offset;
14244     quad = &gl.verts[call.triangleOffset];
14245     glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f);
14246     glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f);
14247     glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f);
14248     glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f);
14249     // Get uniform
14250     call.uniformOffset = glnvg__allocFragUniforms(gl, 2);
14251     if (call.uniformOffset == -1) goto error;
14252     // Simple shader for stencil
14253     frag = nvg__fragUniformPtr(gl, call.uniformOffset);
14254     memset(frag, 0, (*frag).sizeof);
14255     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f);
14256     memcpy(nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), frag, (*frag).sizeof);
14257     frag.strokeThr = -1.0f;
14258     frag.type = NSVG_SHADER_SIMPLE;
14259     // Fill shader
14260     //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, fringe, fringe, -1.0f);
14261   } else {
14262     call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14263     if (call.uniformOffset == -1) goto error;
14264     // Fill shader
14265     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f);
14266   }
14267 
14268   return;
14269 
14270 error:
14271   // We get here if call alloc was ok, but something else is not.
14272   // Roll back the last call to prevent drawing it.
14273   if (gl.ncalls > 0) --gl.ncalls;
14274 }
14275 
14276 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 {
14277   if (npaths < 1) return;
14278 
14279   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14280   GLNVGcall* call = glnvg__allocCall(gl);
14281   int maxverts, offset;
14282 
14283   if (call is null) return;
14284 
14285   call.type = GLNVG_STROKE;
14286   call.clipmode = clipmode;
14287   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14288   call.pathOffset = glnvg__allocPaths(gl, npaths);
14289   if (call.pathOffset == -1) goto error;
14290   call.pathCount = npaths;
14291   call.image = paint.image.id;
14292   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14293 
14294   // Allocate vertices for all the paths.
14295   maxverts = glnvg__maxVertCount(paths, npaths);
14296   offset = glnvg__allocVerts(gl, maxverts);
14297   if (offset == -1) goto error;
14298 
14299   foreach (int i; 0..npaths) {
14300     GLNVGpath* copy = &gl.paths[call.pathOffset+i];
14301     const(NVGpath)* path = &paths[i];
14302     memset(copy, 0, GLNVGpath.sizeof);
14303     if (path.nstroke) {
14304       copy.strokeOffset = offset;
14305       copy.strokeCount = path.nstroke;
14306       memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke);
14307       offset += path.nstroke;
14308     }
14309   }
14310 
14311   if (gl.flags&NVGContextFlag.StencilStrokes) {
14312     // Fill shader
14313     call.uniformOffset = glnvg__allocFragUniforms(gl, 2);
14314     if (call.uniformOffset == -1) goto error;
14315     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
14316     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f);
14317   } else {
14318     // Fill shader
14319     call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14320     if (call.uniformOffset == -1) goto error;
14321     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
14322   }
14323 
14324   return;
14325 
14326 error:
14327   // We get here if call alloc was ok, but something else is not.
14328   // Roll back the last call to prevent drawing it.
14329   if (gl.ncalls > 0) --gl.ncalls;
14330 }
14331 
14332 void glnvg__renderTriangles (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc {
14333   if (nverts < 1) return;
14334 
14335   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14336   GLNVGcall* call = glnvg__allocCall(gl);
14337   GLNVGfragUniforms* frag;
14338 
14339   if (call is null) return;
14340 
14341   call.type = GLNVG_TRIANGLES;
14342   call.clipmode = clipmode;
14343   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14344   call.image = paint.image.id;
14345   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14346 
14347   // Allocate vertices for all the paths.
14348   call.triangleOffset = glnvg__allocVerts(gl, nverts);
14349   if (call.triangleOffset == -1) goto error;
14350   call.triangleCount = nverts;
14351 
14352   memcpy(&gl.verts[call.triangleOffset], verts, NVGVertex.sizeof*nverts);
14353 
14354   // Fill shader
14355   call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14356   if (call.uniformOffset == -1) goto error;
14357   frag = nvg__fragUniformPtr(gl, call.uniformOffset);
14358   glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f);
14359   frag.type = NSVG_SHADER_IMG;
14360 
14361   return;
14362 
14363 error:
14364   // We get here if call alloc was ok, but something else is not.
14365   // Roll back the last call to prevent drawing it.
14366   if (gl.ncalls > 0) --gl.ncalls;
14367 }
14368 
14369 void glnvg__renderDelete (void* uptr) nothrow @trusted @nogc {
14370   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14371   if (gl is null) return;
14372 
14373   glnvg__killFBOs(gl);
14374   glnvg__deleteShader(&gl.shader);
14375   glnvg__deleteShader(&gl.shaderFillFBO);
14376   glnvg__deleteShader(&gl.shaderCopyFBO);
14377 
14378   if (gl.vertBuf != 0) glDeleteBuffers(1, &gl.vertBuf);
14379 
14380   foreach (ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) {
14381     if (tex.id != 0 && (tex.flags&NVGImageFlag.NoDelete) == 0) {
14382       assert(tex.tex != 0);
14383       glDeleteTextures(1, &tex.tex);
14384     }
14385   }
14386   free(gl.textures);
14387 
14388   free(gl.paths);
14389   free(gl.verts);
14390   free(gl.uniforms);
14391   free(gl.calls);
14392 
14393   free(gl);
14394 }
14395 
14396 
14397 /** Creates NanoVega contexts for OpenGL2+.
14398  *
14399  * Specify creation flags as additional arguments, like this:
14400  * `nvgCreateContext(NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes);`
14401  *
14402  * If you won't specify any flags, defaults will be used:
14403  * `[NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes]`.
14404  *
14405  * Group: context_management
14406  */
14407 public NVGContext nvgCreateContext (const(NVGContextFlag)[] flagList...) nothrow @trusted @nogc {
14408   version(aliced) {
14409     enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA;
14410   } else {
14411     enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes;
14412   }
14413   uint flags = 0;
14414   if (flagList.length != 0) {
14415     foreach (immutable flg; flagList) flags |= (flg != NVGContextFlag.Default ? flg : DefaultFlags);
14416   } else {
14417     flags = DefaultFlags;
14418   }
14419   NVGparams params = void;
14420   NVGContext ctx = null;
14421   version(nanovg_builtin_opengl_bindings) nanovgInitOpenGL(); // why not?
14422   version(nanovg_bindbc_opengl_bindings) nanovgInitOpenGL();
14423   GLNVGcontext* gl = cast(GLNVGcontext*)malloc(GLNVGcontext.sizeof);
14424   if (gl is null) goto error;
14425   memset(gl, 0, GLNVGcontext.sizeof);
14426 
14427   memset(&params, 0, params.sizeof);
14428   params.renderCreate = &glnvg__renderCreate;
14429   params.renderCreateTexture = &glnvg__renderCreateTexture;
14430   params.renderTextureIncRef = &glnvg__renderTextureIncRef;
14431   params.renderDeleteTexture = &glnvg__renderDeleteTexture;
14432   params.renderUpdateTexture = &glnvg__renderUpdateTexture;
14433   params.renderGetTextureSize = &glnvg__renderGetTextureSize;
14434   params.renderViewport = &glnvg__renderViewport;
14435   params.renderCancel = &glnvg__renderCancel;
14436   params.renderFlush = &glnvg__renderFlush;
14437   params.renderPushClip = &glnvg__renderPushClip;
14438   params.renderPopClip = &glnvg__renderPopClip;
14439   params.renderResetClip = &glnvg__renderResetClip;
14440   params.renderFill = &glnvg__renderFill;
14441   params.renderStroke = &glnvg__renderStroke;
14442   params.renderTriangles = &glnvg__renderTriangles;
14443   params.renderSetAffine = &glnvg__renderSetAffine;
14444   params.renderDelete = &glnvg__renderDelete;
14445   params.userPtr = gl;
14446   params.edgeAntiAlias = (flags&NVGContextFlag.Antialias ? true : false);
14447   if (flags&(NVGContextFlag.FontAA|NVGContextFlag.FontNoAA)) {
14448     params.fontAA = (flags&NVGContextFlag.FontNoAA ? NVG_INVERT_FONT_AA : !NVG_INVERT_FONT_AA);
14449   } else {
14450     params.fontAA = NVG_INVERT_FONT_AA;
14451   }
14452 
14453   gl.flags = flags;
14454   gl.freetexid = -1;
14455 
14456   ctx = createInternal(&params);
14457   if (ctx is null) goto error;
14458 
14459   static if (__VERSION__ < 2076) {
14460     DGNoThrowNoGC(() { import core.thread; gl.mainTID = Thread.getThis.id; })();
14461   } else {
14462     try { import core.thread; gl.mainTID = Thread.getThis.id; } catch (Exception e) {}
14463   }
14464 
14465   return ctx;
14466 
14467 error:
14468   // 'gl' is freed by nvgDeleteInternal.
14469   if (ctx !is null) ctx.deleteInternal();
14470   return null;
14471 }
14472 
14473 /// Using  OpenGL texture id creates GLNVGtexture and return its id.
14474 /// Group: images
14475 public int glCreateImageFromHandleGL2 (NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc {
14476   GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr;
14477   GLNVGtexture* tex = glnvg__allocTexture(gl);
14478 
14479   if (tex is null) return 0;
14480 
14481   tex.type = NVGtexture.RGBA;
14482   tex.tex = textureId;
14483   tex.flags = imageFlags;
14484   tex.width = w;
14485   tex.height = h;
14486 
14487   return tex.id;
14488 }
14489 
14490 /// Create NVGImage from OpenGL texture id.
14491 /// Group: images
14492 public NVGImage glCreateImageFromOpenGLTexture(NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc {
14493   auto id = glCreateImageFromHandleGL2(ctx, textureId, w, h, imageFlags);
14494 
14495   NVGImage res;
14496   if (id > 0) {
14497     res.id = id;
14498     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); }
14499     res.ctx = ctx;
14500     ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount
14501   }
14502   return res;
14503 }
14504 
14505 /// Returns OpenGL texture id for NanoVega image.
14506 /// Group: images
14507 public GLuint glImageHandleGL2 (NVGContext ctx, int image) nothrow @trusted @nogc {
14508   GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr;
14509   GLNVGtexture* tex = glnvg__findTexture(gl, image);
14510   return tex.tex;
14511 }
14512 
14513 
14514 // ////////////////////////////////////////////////////////////////////////// //
14515 private:
14516 
14517 static if (NanoVegaHasFontConfig) {
14518   version(nanovg_builtin_fontconfig_bindings) {
14519     pragma(lib, "fontconfig");
14520 
14521     private extern(C) nothrow @trusted @nogc {
14522       enum FC_FILE = "file"; /* String */
14523       alias FcBool = int;
14524       alias FcChar8 = char;
14525       struct FcConfig;
14526       struct FcPattern;
14527       alias FcMatchKind = int;
14528       enum : FcMatchKind {
14529         FcMatchPattern,
14530         FcMatchFont,
14531         FcMatchScan
14532       }
14533       alias FcResult = int;
14534       enum : FcResult {
14535         FcResultMatch,
14536         FcResultNoMatch,
14537         FcResultTypeMismatch,
14538         FcResultNoId,
14539         FcResultOutOfMemory
14540       }
14541       FcBool FcInit ();
14542       FcBool FcConfigSubstituteWithPat (FcConfig* config, FcPattern* p, FcPattern* p_pat, FcMatchKind kind);
14543       void FcDefaultSubstitute (FcPattern* pattern);
14544       FcBool FcConfigSubstitute (FcConfig* config, FcPattern* p, FcMatchKind kind);
14545       FcPattern* FcFontMatch (FcConfig* config, FcPattern* p, FcResult* result);
14546       FcPattern* FcNameParse (const(FcChar8)* name);
14547       void FcPatternDestroy (FcPattern* p);
14548       FcResult FcPatternGetString (const(FcPattern)* p, const(char)* object, int n, FcChar8** s);
14549     }
14550   }
14551 
14552   __gshared bool fontconfigAvailable = false;
14553   // initialize fontconfig
14554   shared static this () {
14555     if (FcInit()) {
14556       fontconfigAvailable = true;
14557     } else {
14558       import core.stdc.stdio : stderr, fprintf;
14559       stderr.fprintf("***NanoVega WARNING: cannot init fontconfig!\n");
14560     }
14561   }
14562 }
14563 
14564 
14565 // ////////////////////////////////////////////////////////////////////////// //
14566 public enum BaphometDims = 512.0f; // baphomet icon is 512x512 ([0..511])
14567 
14568 private static immutable ubyte[7641] baphometPath = [
14569   0x01,0x04,0x06,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x08,0xa0,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,
14570   0x00,0x80,0xff,0x43,0xa2,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x08,0x00,0x80,0xff,
14571   0x43,0x7a,0x89,0xe5,0x42,0xa0,0x1d,0xc6,0x43,0x00,0x00,0x00,0x00,0x30,0x89,0x7f,0x43,0x00,0x00,0x00,
14572   0x00,0x08,0x7a,0x89,0xe5,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7a,0x89,0xe5,0x42,0x00,0x00,
14573   0x00,0x00,0x30,0x89,0x7f,0x43,0x08,0x00,0x00,0x00,0x00,0xa2,0x1d,0xc6,0x43,0x7a,0x89,0xe5,0x42,0x00,
14574   0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x09,0x06,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd,
14575   0x43,0x08,0x16,0x68,0xb3,0x43,0x72,0x87,0xdd,0x43,0x71,0x87,0xdd,0x43,0x17,0x68,0xb3,0x43,0x71,0x87,
14576   0xdd,0x43,0x30,0x89,0x7f,0x43,0x08,0x71,0x87,0xdd,0x43,0xd2,0x2f,0x18,0x43,0x16,0x68,0xb3,0x43,0x35,
14577   0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x35,0xe2,0x87,0x42,0x08,0xd1,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,
14578   0x35,0xe2,0x87,0x42,0xd2,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x08,0x35,0xe2,0x87,
14579   0x42,0x17,0x68,0xb3,0x43,0xd1,0x2f,0x18,0x43,0x72,0x87,0xdd,0x43,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd,
14580   0x43,0x09,0x06,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x07,0xa4,0x3f,0x7f,0x43,0x0b,0x86,0xdc,0x43,
14581   0x07,0x6c,0xb9,0xb2,0x43,0xe8,0xd1,0xca,0x42,0x07,0x6e,0x4d,0xa0,0x42,0xa9,0x10,0x9c,0x43,0x07,0xb7,
14582   0x40,0xd7,0x43,0xa9,0x10,0x9c,0x43,0x07,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x09,0x06,0x98,0x42,
14583   0x74,0x43,0xb1,0x8d,0x68,0x43,0x08,0xd7,0x24,0x79,0x43,0xba,0x83,0x6e,0x43,0xa9,0x16,0x7c,0x43,0x56,
14584   0xa1,0x76,0x43,0x74,0x2a,0x7d,0x43,0x44,0x73,0x80,0x43,0x08,0x55,0xd1,0x7e,0x43,0xe3,0xea,0x76,0x43,
14585   0xbc,0x18,0x81,0x43,0x7f,0xa8,0x6e,0x43,0x8f,0x0a,0x84,0x43,0x02,0xfc,0x68,0x43,0x09,0x06,0x92,0x29,
14586   0x8d,0x43,0x73,0xc3,0x67,0x43,0x08,0xa4,0xd9,0x8e,0x43,0xf2,0xa6,0x7a,0x43,0x8f,0x22,0x88,0x43,0x75,
14587   0x2a,0x7d,0x43,0x42,0x7f,0x82,0x43,0x08,0xc8,0x88,0x43,0x09,0x06,0xc1,0x79,0x74,0x43,0x50,0x64,0x89,
14588   0x43,0x08,0x68,0x2d,0x72,0x43,0xee,0x21,0x81,0x43,0xcd,0x97,0x55,0x43,0xe6,0xf1,0x7b,0x43,0x91,0xec,
14589   0x5d,0x43,0xa8,0xc7,0x6a,0x43,0x09,0x06,0xfa,0xa5,0x52,0x43,0x60,0x97,0x7c,0x43,0x08,0x19,0xff,0x50,
14590   0x43,0xe9,0x6e,0x8a,0x43,0xb0,0xbd,0x70,0x43,0x4c,0x51,0x82,0x43,0x04,0xeb,0x69,0x43,0x66,0x0f,0x8e,
14591   0x43,0x09,0x06,0x17,0xbf,0x71,0x43,0x2c,0x58,0x94,0x43,0x08,0x1c,0x96,0x6e,0x43,0x61,0x68,0x99,0x43,
14592   0x2d,0x3a,0x6e,0x43,0xc8,0x81,0x9e,0x43,0xb7,0x9b,0x72,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0x30,0xdb,
14593   0x82,0x43,0xdb,0xe9,0x93,0x43,0x08,0x11,0x82,0x84,0x43,0x61,0x68,0x99,0x43,0xe8,0x4a,0x84,0x43,0x8e,
14594   0xa6,0x9e,0x43,0x42,0x7f,0x82,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0xc4,0x02,0x85,0x43,0xd1,0x0b,0x92,
14595   0x43,0x08,0xd6,0xb2,0x86,0x43,0x34,0x1e,0x92,0x43,0x4f,0x58,0x87,0x43,0xa4,0xf1,0x92,0x43,0x03,0xd9,
14596   0x87,0x43,0x7b,0xc6,0x94,0x43,0x09,0x06,0x87,0x3e,0x64,0x43,0x31,0x3b,0x93,0x43,0x08,0x3b,0xbf,0x64,
14597   0x43,0x6f,0xf9,0x91,0x43,0x96,0x0b,0x67,0x43,0xc5,0x4a,0x91,0x43,0xcf,0xfe,0x6a,0x43,0x31,0x2f,0x91,
14598   0x43,0x09,0x06,0x16,0x74,0xb5,0x43,0x08,0xec,0x8e,0x43,0x08,0x1b,0x4b,0xb2,0x43,0xee,0x5d,0x8b,0x43,
14599   0x48,0x4d,0xad,0x43,0x12,0xa6,0x8a,0x43,0xf3,0xd7,0xa7,0x43,0x74,0xb8,0x8a,0x43,0x08,0x8c,0xb2,0xa0,
14600   0x43,0xcd,0xf8,0x8a,0x43,0x68,0x46,0x9b,0x43,0x79,0x8f,0x87,0x43,0x49,0xc9,0x96,0x43,0xe9,0x3e,0x82,
14601   0x43,0x08,0x60,0x5c,0x97,0x43,0xa1,0xde,0x8b,0x43,0x4e,0xa0,0x93,0x43,0x31,0x3b,0x93,0x43,0x9f,0xea,
14602   0x8d,0x43,0x27,0x8d,0x99,0x43,0x08,0x07,0xe0,0x8c,0x43,0x06,0x34,0x9b,0x43,0x38,0xe9,0x8c,0x43,0x46,
14603   0x0a,0x9e,0x43,0x3d,0xcc,0x8b,0x43,0xb2,0x06,0xa2,0x43,0x08,0xf1,0x40,0x8a,0x43,0xb0,0x12,0xa4,0x43,
14604   0x39,0xd1,0x88,0x43,0x76,0x43,0xa6,0x43,0xfa,0x06,0x88,0x43,0xa4,0x75,0xa9,0x43,0x08,0x19,0x6c,0x88,
14605   0x43,0x9f,0x9e,0xac,0x43,0x66,0xeb,0x87,0x43,0x44,0x76,0xb0,0x43,0x6b,0xce,0x86,0x43,0x3b,0xbc,0xb4,
14606   0x43,0x08,0xa9,0x8c,0x85,0x43,0x06,0xd0,0xb5,0x43,0xfa,0xee,0x83,0x43,0x74,0xa3,0xb6,0x43,0x3d,0x90,
14607   0x81,0x43,0x31,0xf6,0xb6,0x43,0x08,0x9d,0x61,0x7d,0x43,0xee,0x48,0xb7,0x43,0x3b,0x1f,0x75,0x43,0xcf,
14608   0xe3,0xb6,0x43,0xee,0x6f,0x6d,0x43,0x68,0xe2,0xb5,0x43,0x08,0xd4,0xed,0x6b,0x43,0x87,0x2f,0xb2,0x43,
14609   0x0e,0xc9,0x6b,0x43,0xa7,0x7c,0xae,0x43,0x98,0xfa,0x67,0x43,0xab,0x53,0xab,0x43,0x08,0x25,0x2c,0x64,
14610   0x43,0x33,0xa2,0xa8,0x43,0x40,0x96,0x61,0x43,0xc3,0xc2,0xa5,0x43,0x64,0xde,0x60,0x43,0xfa,0xa2,0xa2,
14611   0x43,0x08,0xb0,0x5d,0x60,0x43,0x06,0x4c,0x9f,0x43,0x9a,0xca,0x5f,0x43,0x38,0x3d,0x9b,0x43,0x3b,0x8f,
14612   0x5c,0x43,0x85,0xb0,0x98,0x43,0x08,0x42,0x36,0x51,0x43,0x3d,0xf0,0x91,0x43,0xcd,0x4f,0x49,0x43,0xdb,
14613   0xb9,0x8b,0x43,0xe0,0xdb,0x44,0x43,0x42,0x8b,0x84,0x43,0x08,0x7e,0xc9,0x44,0x43,0x8a,0x57,0x8d,0x43,
14614   0xbc,0x6c,0x0f,0x43,0x23,0x62,0x8e,0x43,0xf5,0x17,0x07,0x43,0xc5,0x3e,0x8f,0x43,0x09,0x06,0xe0,0xea,
14615   0x76,0x43,0xab,0xef,0xc5,0x43,0x08,0x12,0x00,0x79,0x43,0xab,0xcb,0xbf,0x43,0x79,0xb9,0x6d,0x43,0x7e,
14616   0x8d,0xba,0x43,0xee,0x6f,0x6d,0x43,0x98,0xeb,0xb5,0x43,0x08,0xe0,0x02,0x7b,0x43,0x5f,0x1c,0xb8,0x43,
14617   0x85,0x2c,0x82,0x43,0xe9,0x65,0xb8,0x43,0xd6,0xb2,0x86,0x43,0xc6,0x05,0xb5,0x43,0x08,0x03,0xcd,0x85,
14618   0x43,0x5a,0x39,0xb9,0x43,0xe4,0x4f,0x81,0x43,0xdb,0xd4,0xbf,0x43,0xdf,0x6c,0x82,0x43,0xbc,0x93,0xc5,
14619   0x43,0x09,0x06,0xf0,0xd0,0x22,0x43,0x5d,0x19,0x08,0x43,0x08,0xbc,0xab,0x49,0x43,0x4a,0x35,0x29,0x43,
14620   0xcb,0xf7,0x65,0x43,0xce,0x37,0x45,0x43,0x0e,0x99,0x63,0x43,0x67,0xc6,0x5c,0x43,0x09,0x06,0x05,0x94,
14621   0xab,0x43,0xc2,0x13,0x04,0x43,0x08,0x9f,0x26,0x98,0x43,0x11,0x42,0x25,0x43,0x97,0x00,0x8a,0x43,0x32,
14622   0x32,0x41,0x43,0xf5,0x2f,0x8b,0x43,0xc7,0xc0,0x58,0x43,0x09,0x06,0x8f,0x85,0x48,0x43,0xe0,0xa8,0x8c,
14623   0x43,0x08,0x55,0xaa,0x48,0x43,0xe0,0xa8,0x8c,0x43,0x6b,0x3d,0x49,0x43,0xc1,0x43,0x8c,0x43,0x31,0x62,
14624   0x49,0x43,0xc1,0x43,0x8c,0x43,0x08,0x2f,0xe3,0x2f,0x43,0xad,0xe7,0x98,0x43,0xff,0x0d,0x0d,0x43,0xad,
14625   0xf3,0x9a,0x43,0xf0,0xaf,0xcc,0x42,0x74,0x00,0x97,0x43,0x08,0xbb,0xa2,0xf7,0x42,0x93,0x4d,0x93,0x43,
14626   0x5e,0x19,0x08,0x43,0x5a,0x2a,0x87,0x43,0x23,0x6e,0x10,0x43,0x42,0x97,0x86,0x43,0x08,0xca,0xe8,0x33,
14627   0x43,0x1b,0x3c,0x80,0x43,0x80,0xe8,0x4d,0x43,0xda,0xf4,0x70,0x43,0xae,0x0e,0x4f,0x43,0x2b,0x1b,0x65,
14628   0x43,0x08,0x66,0x96,0x54,0x43,0xa3,0xe1,0x3b,0x43,0x4e,0xc4,0x19,0x43,0xa0,0x1a,0x16,0x43,0x10,0xe2,
14629   0x14,0x43,0x26,0x14,0xe0,0x42,0x08,0x5c,0x91,0x1c,0x43,0xcb,0x27,0xee,0x42,0xa9,0x40,0x24,0x43,0x71,
14630   0x3b,0xfc,0x42,0xf3,0xef,0x2b,0x43,0x8b,0x27,0x05,0x43,0x08,0xe2,0x4b,0x2c,0x43,0x48,0x86,0x07,0x43,
14631   0x79,0x62,0x2f,0x43,0x05,0xe5,0x09,0x43,0x55,0x32,0x34,0x43,0xa0,0xd2,0x09,0x43,0x08,0x74,0xa3,0x36,
14632   0x43,0x3a,0xd1,0x08,0x43,0x7e,0x81,0x38,0x43,0x09,0xd4,0x0a,0x43,0x0d,0xba,0x39,0x43,0xa0,0xea,0x0d,
14633   0x43,0x08,0x6f,0xe4,0x3d,0x43,0x43,0xc7,0x0e,0x43,0xd6,0xe5,0x3e,0x43,0xc4,0x4a,0x11,0x43,0x55,0x7a,
14634   0x40,0x43,0x59,0x72,0x13,0x43,0x08,0x55,0x92,0x44,0x43,0xbf,0x73,0x14,0x43,0x23,0x95,0x46,0x43,0xa5,
14635   0x09,0x17,0x43,0xe0,0xf3,0x48,0x43,0xfe,0x55,0x19,0x43,0x08,0xcd,0x4f,0x49,0x43,0xaa,0x10,0x1c,0x43,
14636   0x61,0x77,0x4b,0x43,0xfe,0x6d,0x1d,0x43,0x80,0xe8,0x4d,0x43,0x2b,0x94,0x1e,0x43,0x08,0x58,0xc9,0x51,
14637   0x43,0x41,0x27,0x1f,0x43,0x9b,0x82,0x53,0x43,0x35,0x72,0x20,0x43,0x53,0xf2,0x54,0x43,0x88,0xcf,0x21,
14638   0x43,0x08,0x7b,0x29,0x55,0x43,0xe8,0x0a,0x25,0x43,0xb2,0x2d,0x58,0x43,0xef,0xe8,0x26,0x43,0x9b,0xb2,
14639   0x5b,0x43,0xd0,0x8f,0x28,0x43,0x08,0x5f,0xef,0x5f,0x43,0xeb,0x11,0x2a,0x43,0xfd,0xdc,0x5f,0x43,0x6e,
14640   0x95,0x2c,0x43,0x3b,0xa7,0x60,0x43,0x2b,0xf4,0x2e,0x43,0x08,0x06,0xbb,0x61,0x43,0xfd,0xe5,0x31,0x43,
14641   0xe7,0x61,0x63,0x43,0xef,0x30,0x33,0x43,0x53,0x52,0x65,0x43,0xa3,0xb1,0x33,0x43,0x08,0x12,0xa0,0x68,
14642   0x43,0x7f,0x69,0x34,0x43,0x40,0xc6,0x69,0x43,0x64,0xff,0x36,0x43,0x7e,0x90,0x6a,0x43,0x71,0xcc,0x39,
14643   0x43,0x08,0xbc,0x5a,0x6b,0x43,0x51,0x73,0x3b,0x43,0xc1,0x49,0x6c,0x43,0xa5,0xd0,0x3c,0x43,0xe0,0xba,
14644   0x6e,0x43,0xb8,0x74,0x3c,0x43,0x08,0x6b,0x1c,0x73,0x43,0x13,0xc1,0x3e,0x43,0x40,0xf6,0x71,0x43,0xce,
14645   0x1f,0x41,0x43,0x55,0x89,0x72,0x43,0x8d,0x7e,0x43,0x43,0x08,0x68,0x2d,0x72,0x43,0x89,0xae,0x4b,0x43,
14646   0xc1,0x79,0x74,0x43,0xcb,0x78,0x4c,0x43,0x55,0xa1,0x76,0x43,0x5b,0xb1,0x4d,0x43,0x08,0xa2,0x38,0x7a,
14647   0x43,0xd1,0x56,0x4e,0x43,0x85,0xb6,0x78,0x43,0xb1,0x15,0x54,0x43,0x83,0xc7,0x77,0x43,0x89,0x0e,0x5c,
14648   0x43,0x08,0xcf,0x46,0x77,0x43,0x0f,0x81,0x5f,0x43,0x1a,0xde,0x7a,0x43,0xce,0xc7,0x5d,0x43,0x42,0x73,
14649   0x80,0x43,0x99,0xc3,0x5a,0x43,0x08,0x85,0x2c,0x82,0x43,0xf6,0xe6,0x59,0x43,0x81,0x3d,0x81,0x43,0x16,
14650   0x10,0x50,0x43,0xd6,0x8e,0x80,0x43,0x5b,0x99,0x49,0x43,0x08,0xc4,0xea,0x80,0x43,0x22,0x95,0x46,0x43,
14651   0xfa,0xe2,0x81,0x43,0xda,0xec,0x43,0x43,0x78,0x77,0x83,0x43,0xe4,0xb2,0x41,0x43,0x08,0x8a,0x27,0x85,
14652   0x43,0x86,0x77,0x3e,0x43,0x0c,0x9f,0x85,0x43,0x07,0xf4,0x3b,0x43,0x8f,0x16,0x86,0x43,0xe6,0x82,0x39,
14653   0x43,0x08,0x85,0x44,0x86,0x43,0x37,0xd9,0x35,0x43,0x1e,0x4f,0x87,0x43,0xe1,0x7b,0x34,0x43,0xdf,0x90,
14654   0x88,0x43,0xb6,0x55,0x33,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0xe5,0x31,0x43,0xfa,0x12,0x8a,0x43,0xbf,
14655   0x03,0x2d,0x43,0x19,0x78,0x8a,0x43,0x45,0x5e,0x2c,0x43,0x08,0x03,0xf1,0x8b,0x43,0xac,0x47,0x29,0x43,
14656   0x2f,0x17,0x8d,0x43,0x45,0x46,0x28,0x43,0xc8,0x21,0x8e,0x43,0x30,0xb3,0x27,0x43,0x08,0xa9,0xc8,0x8f,
14657   0x43,0xef,0xe8,0x26,0x43,0xbf,0x5b,0x90,0x43,0x5b,0xc1,0x24,0x43,0x10,0xca,0x90,0x43,0xa0,0x62,0x22,
14658   0x43,0x08,0x26,0x5d,0x91,0x43,0xbb,0xcc,0x1f,0x43,0xf0,0x70,0x92,0x43,0x78,0x13,0x1e,0x43,0x77,0xd7,
14659   0x93,0x43,0x73,0x24,0x1d,0x43,0x08,0x65,0x3f,0x96,0x43,0xce,0x58,0x1b,0x43,0xbe,0x7f,0x96,0x43,0xbf,
14660   0x8b,0x18,0x43,0x60,0x5c,0x97,0x43,0xb6,0xad,0x16,0x43,0x08,0xba,0xa8,0x99,0x43,0x78,0xcb,0x11,0x43,
14661   0x49,0xe1,0x9a,0x43,0x78,0xcb,0x11,0x43,0x01,0x51,0x9c,0x43,0x73,0xdc,0x10,0x43,0x08,0x72,0x24,0x9d,
14662   0x43,0xd2,0xff,0x0f,0x43,0x1c,0xd3,0x9d,0x43,0x07,0xec,0x0e,0x43,0xeb,0xc9,0x9d,0x43,0xe8,0x7a,0x0c,
14663   0x43,0x08,0x60,0x80,0x9d,0x43,0xd7,0xbe,0x08,0x43,0x4d,0xe8,0x9f,0x43,0x86,0x50,0x08,0x43,0x25,0xbd,
14664   0xa1,0x43,0x5b,0x2a,0x07,0x43,0x08,0x99,0x7f,0xa3,0x43,0xc9,0xf1,0x05,0x43,0x48,0x1d,0xa5,0x43,0x86,
14665   0x38,0x04,0x43,0x6c,0x71,0xa6,0x43,0x18,0x59,0x01,0x43,0x08,0x32,0x96,0xa6,0x43,0x6e,0x64,0xff,0x42,
14666   0x48,0x29,0xa7,0x43,0xed,0xcf,0xfd,0x42,0x5f,0xbc,0xa7,0x43,0x71,0x3b,0xfc,0x42,0x08,0xf3,0xe3,0xa9,
14667   0x43,0xf7,0x7d,0xf7,0x42,0xd8,0x6d,0xaa,0x43,0x45,0xe5,0xf2,0x42,0x48,0x41,0xab,0x43,0xcb,0x27,0xee,
14668   0x42,0x08,0x24,0xf9,0xab,0x43,0x52,0x6a,0xe9,0x42,0xee,0x0c,0xad,0x43,0x4c,0x8c,0xe7,0x42,0x1b,0x33,
14669   0xae,0x43,0xcc,0xf7,0xe5,0x42,0x08,0xaa,0x6b,0xaf,0x43,0xe8,0x61,0xe3,0x42,0x90,0xf5,0xaf,0x43,0xc9,
14670   0xf0,0xe0,0x42,0xe0,0x63,0xb0,0x43,0xe5,0x5a,0xde,0x42,0x08,0xaa,0x83,0xb3,0x43,0x29,0x2d,0x09,0x43,
14671   0x6a,0xfe,0x8e,0x43,0xb8,0x74,0x3c,0x43,0xd5,0x06,0x95,0x43,0xe6,0x79,0x67,0x43,0x08,0x2f,0x53,0x97,
14672   0x43,0xe9,0xb0,0x74,0x43,0xa8,0x28,0xa0,0x43,0x43,0xfd,0x76,0x43,0x83,0x28,0xad,0x43,0x17,0x59,0x81,
14673   0x43,0x08,0x3d,0xe7,0xbf,0x43,0x4b,0x8d,0x8c,0x43,0xae,0x96,0xba,0x43,0x66,0x27,0x92,0x43,0x15,0xe0,
14674   0xc7,0x43,0x6f,0x11,0x96,0x43,0x08,0x7e,0x5d,0xb2,0x43,0xdb,0x01,0x98,0x43,0x9e,0x56,0xa0,0x43,0x80,
14675   0xc1,0x97,0x43,0x69,0x2e,0x97,0x43,0x31,0x17,0x8d,0x43,0x09,0x06,0xab,0xa7,0x39,0x43,0x67,0x0f,0x0e,
14676   0x43,0x08,0xdb,0xbc,0x3b,0x43,0xe8,0x92,0x10,0x43,0xb5,0x85,0x3b,0x43,0x97,0x3c,0x14,0x43,0xab,0xa7,
14677   0x39,0x43,0x0c,0x0b,0x18,0x43,0x09,0x06,0xca,0x30,0x40,0x43,0x30,0x3b,0x13,0x43,0x08,0x17,0xc8,0x43,
14678   0x43,0xa5,0x09,0x17,0x43,0x7e,0xc9,0x44,0x43,0x1a,0xd8,0x1a,0x43,0x9d,0x22,0x43,0x43,0x8d,0xa6,0x1e,
14679   0x43,0x09,0x06,0xc8,0x78,0x4c,0x43,0xed,0xc9,0x1d,0x43,0x08,0x0b,0x32,0x4e,0x43,0x22,0xce,0x20,0x43,
14680   0x23,0xc5,0x4e,0x43,0x58,0xd2,0x23,0x43,0x0b,0x32,0x4e,0x43,0x2b,0xc4,0x26,0x43,0x09,0x06,0xec,0x08,
14681   0x58,0x43,0xc7,0xb1,0x26,0x43,0x08,0x02,0x9c,0x58,0x43,0xef,0x00,0x2b,0x43,0xd9,0x64,0x58,0x43,0x02,
14682   0xbd,0x2e,0x43,0x10,0x51,0x57,0x43,0x37,0xc1,0x31,0x43,0x09,0x06,0xcb,0xdf,0x61,0x43,0x4a,0x65,0x31,
14683   0x43,0x08,0xbe,0x2a,0x63,0x43,0xbd,0x33,0x35,0x43,0x32,0xe1,0x62,0x43,0x56,0x4a,0x38,0x43,0xde,0x83,
14684   0x61,0x43,0x3c,0xe0,0x3a,0x43,0x09,0x06,0x1c,0x7e,0x6a,0x43,0x5b,0x39,0x39,0x43,0x08,0x31,0x11,0x6b,
14685   0x43,0x0c,0xd2,0x3d,0x43,0x1c,0x7e,0x6a,0x43,0x13,0xd9,0x42,0x43,0xd9,0xc4,0x68,0x43,0xcb,0x60,0x48,
14686   0x43,0x09,0x06,0xe5,0xc1,0x73,0x43,0x16,0xf8,0x4b,0x43,0x08,0xa6,0xf7,0x72,0x43,0xb1,0xfd,0x4f,0x43,
14687   0x3b,0x07,0x71,0x43,0x4a,0x14,0x53,0x43,0xa2,0xf0,0x6d,0x43,0x7c,0x29,0x55,0x43,0x09,0x06,0x00,0x8d,
14688   0xa6,0x43,0xef,0x21,0x01,0x43,0x08,0x52,0xfb,0xa6,0x43,0xce,0xc8,0x02,0x43,0xe6,0x16,0xa7,0x43,0x51,
14689   0x4c,0x05,0x43,0x3b,0x68,0xa6,0x43,0x4c,0x75,0x08,0x43,0x09,0x06,0xde,0x20,0xa1,0x43,0x86,0x50,0x08,
14690   0x43,0x08,0xd4,0x4e,0xa1,0x43,0xd3,0xe7,0x0b,0x43,0xb5,0xe9,0xa0,0x43,0x59,0x5a,0x0f,0x43,0xba,0xcc,
14691   0x9f,0x43,0x54,0x83,0x12,0x43,0x09,0x06,0x77,0xfb,0x99,0x43,0x6c,0x16,0x13,0x43,0x08,0xde,0xfc,0x9a,
14692   0x43,0x4a,0xbd,0x14,0x43,0x06,0x34,0x9b,0x43,0xfe,0x55,0x19,0x43,0x13,0xe9,0x99,0x43,0x41,0x27,0x1f,
14693   0x43,0x09,0x06,0x46,0xce,0x93,0x43,0x26,0xa5,0x1d,0x43,0x08,0xe7,0xaa,0x94,0x43,0xbb,0xcc,0x1f,0x43,
14694   0x18,0xb4,0x94,0x43,0xa8,0x40,0x24,0x43,0xe2,0xbb,0x93,0x43,0x21,0xfe,0x28,0x43,0x09,0x06,0xb1,0x8e,
14695   0x8d,0x43,0xa8,0x58,0x28,0x43,0x08,0x19,0x90,0x8e,0x43,0x54,0x13,0x2b,0x43,0xa4,0xd9,0x8e,0x43,0x84,
14696   0x40,0x31,0x43,0x46,0xaa,0x8d,0x43,0x29,0x24,0x37,0x43,0x09,0x06,0xd6,0xbe,0x88,0x43,0xef,0x30,0x33,
14697   0x43,0x08,0x0c,0xb7,0x89,0x43,0x0e,0xa2,0x35,0x43,0xc0,0x37,0x8a,0x43,0x7a,0xaa,0x3b,0x43,0xbb,0x48,
14698   0x89,0x43,0xbb,0x7b,0x41,0x43,0x09,0x06,0x3a,0xad,0x82,0x43,0xc4,0x59,0x43,0x43,0x08,0xd2,0xb7,0x83,
14699   0x43,0x2b,0x5b,0x44,0x43,0x35,0xd6,0x85,0x43,0x48,0xf5,0x49,0x43,0x42,0x97,0x86,0x43,0xc4,0xa1,0x4f,
14700   0x43,0x09,0x06,0x9c,0xb3,0x80,0x43,0x48,0x55,0x5a,0x43,0x08,0xff,0xc5,0x80,0x43,0x09,0x73,0x55,0x43,
14701   0x93,0xe1,0x80,0x43,0x0f,0x39,0x53,0x43,0xf1,0xbe,0x7e,0x43,0x18,0xe7,0x4c,0x43,0x09,0x06,0xe0,0x02,
14702   0x7b,0x43,0x92,0xec,0x5d,0x43,0x08,0x09,0x3a,0x7b,0x43,0xf0,0xf7,0x58,0x43,0x09,0x3a,0x7b,0x43,0xe6,
14703   0x31,0x5b,0x43,0xe0,0x02,0x7b,0x43,0xa8,0x4f,0x56,0x43,0x09,0x06,0x39,0x4f,0x7d,0x43,0x3e,0x8f,0x5c,
14704   0x43,0x08,0xe9,0xe0,0x7c,0x43,0x03,0x9c,0x58,0x43,0x1e,0x2b,0x81,0x43,0x7f,0x30,0x5a,0x43,0xff,0x73,
14705   0x7d,0x43,0xf6,0xb6,0x51,0x43,0x09,0x06,0x5c,0xb8,0x52,0x43,0x28,0x21,0x87,0x43,0x08,0xae,0x3e,0x57,
14706   0x43,0x12,0x9a,0x88,0x43,0x23,0xf5,0x56,0x43,0x04,0xf1,0x8b,0x43,0x25,0xfc,0x5b,0x43,0x85,0x74,0x8e,
14707   0x43,0x08,0x2f,0xf2,0x61,0x43,0x8e,0x52,0x90,0x43,0xd9,0xdc,0x6c,0x43,0x85,0x74,0x8e,0x43,0xc6,0x20,
14708   0x69,0x43,0x3d,0xd8,0x8d,0x43,0x08,0x6d,0x8c,0x5a,0x43,0xf5,0x3b,0x8d,0x43,0x3d,0x77,0x58,0x43,0xa1,
14709   0xc6,0x87,0x43,0xf8,0xed,0x5e,0x43,0x5e,0x0d,0x86,0x43,0x09,0x06,0xde,0xcc,0x92,0x43,0xf7,0x17,0x87,
14710   0x43,0x08,0xb6,0x89,0x90,0x43,0xae,0x87,0x88,0x43,0x4a,0xa5,0x90,0x43,0xa1,0xde,0x8b,0x43,0xf9,0x2a,
14711   0x8e,0x43,0x23,0x62,0x8e,0x43,0x08,0xf5,0x2f,0x8b,0x43,0x5c,0x49,0x90,0x43,0x35,0xd6,0x85,0x43,0x8e,
14712   0x46,0x8e,0x43,0x3d,0xb4,0x87,0x43,0x47,0xaa,0x8d,0x43,0x08,0x6a,0xfe,0x8e,0x43,0xff,0x0d,0x8d,0x43,
14713   0xbb,0x6c,0x8f,0x43,0xf7,0x17,0x87,0x43,0x5c,0x31,0x8c,0x43,0xb2,0x5e,0x85,0x43,0x09,0x06,0x60,0x38,
14714   0x91,0x43,0x69,0x5d,0x7a,0x43,0x08,0x34,0x1e,0x92,0x43,0x1e,0x5b,0x89,0x43,0x04,0x63,0x7e,0x43,0x5e,
14715   0x01,0x84,0x43,0x59,0x2a,0x87,0x43,0x0d,0xcf,0x8d,0x43,0x09,0x03,0x04,0x06,0x5a,0x18,0x63,0x43,0x82,
14716   0x79,0x8b,0x43,0x08,0x25,0x2c,0x64,0x43,0x82,0x79,0x8b,0x43,0x2a,0x1b,0x65,0x43,0x9d,0xef,0x8a,0x43,
14717   0x2a,0x1b,0x65,0x43,0xc1,0x37,0x8a,0x43,0x08,0x2a,0x1b,0x65,0x43,0x17,0x89,0x89,0x43,0x25,0x2c,0x64,
14718   0x43,0x31,0xff,0x88,0x43,0x5a,0x18,0x63,0x43,0x31,0xff,0x88,0x43,0x08,0xf3,0x16,0x62,0x43,0x31,0xff,
14719   0x88,0x43,0xee,0x27,0x61,0x43,0x17,0x89,0x89,0x43,0xee,0x27,0x61,0x43,0xc1,0x37,0x8a,0x43,0x08,0xee,
14720   0x27,0x61,0x43,0x9d,0xef,0x8a,0x43,0xf3,0x16,0x62,0x43,0x82,0x79,0x8b,0x43,0x5a,0x18,0x63,0x43,0x82,
14721   0x79,0x8b,0x43,0x09,0x06,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x08,0x34,0xee,0x89,0x43,0x82,0x79,
14722   0x8b,0x43,0x85,0x5c,0x8a,0x43,0x9d,0xef,0x8a,0x43,0x85,0x5c,0x8a,0x43,0xc1,0x37,0x8a,0x43,0x08,0x85,
14723   0x5c,0x8a,0x43,0x17,0x89,0x89,0x43,0x34,0xee,0x89,0x43,0x31,0xff,0x88,0x43,0x4f,0x64,0x89,0x43,0x31,
14724   0xff,0x88,0x43,0x08,0x9c,0xe3,0x88,0x43,0x31,0xff,0x88,0x43,0x19,0x6c,0x88,0x43,0x17,0x89,0x89,0x43,
14725   0x19,0x6c,0x88,0x43,0xc1,0x37,0x8a,0x43,0x08,0x19,0x6c,0x88,0x43,0x9d,0xef,0x8a,0x43,0x9c,0xe3,0x88,
14726   0x43,0x82,0x79,0x8b,0x43,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x09,0x02,0x04,0x06,0x19,0x60,0x86,
14727   0x43,0xec,0xed,0xa3,0x43,0x08,0x35,0xd6,0x85,0x43,0x76,0x43,0xa6,0x43,0x93,0xe1,0x80,0x43,0x57,0x02,
14728   0xac,0x43,0x61,0xd8,0x80,0x43,0x87,0x17,0xae,0x43,0x08,0xa5,0x85,0x80,0x43,0xc3,0xfe,0xaf,0x43,0xce,
14729   0xbc,0x80,0x43,0x83,0x40,0xb1,0x43,0xa5,0x91,0x82,0x43,0x79,0x6e,0xb1,0x43,0x08,0x23,0x26,0x84,0x43,
14730   0x40,0x93,0xb1,0x43,0x30,0xe7,0x84,0x43,0xbe,0x1b,0xb1,0x43,0x11,0x82,0x84,0x43,0xab,0x6b,0xaf,0x43,
14731   0x08,0xb7,0x41,0x84,0x43,0x3b,0x98,0xae,0x43,0xb7,0x41,0x84,0x43,0xc3,0xf2,0xad,0x43,0xa1,0xae,0x83,
14732   0x43,0x83,0x28,0xad,0x43,0x08,0xb2,0x52,0x83,0x43,0x80,0x39,0xac,0x43,0x81,0x49,0x83,0x43,0xf0,0x00,
14733   0xab,0x43,0xe4,0x67,0x85,0x43,0x76,0x4f,0xa8,0x43,0x08,0x9c,0xd7,0x86,0x43,0xd1,0x83,0xa6,0x43,0xec,
14734   0x45,0x87,0x43,0x01,0x75,0xa2,0x43,0x19,0x60,0x86,0x43,0xec,0xed,0xa3,0x43,0x09,0x06,0xd9,0xdc,0x6c,
14735   0x43,0x14,0x25,0xa4,0x43,0x08,0xa2,0xf0,0x6d,0x43,0x9f,0x7a,0xa6,0x43,0x47,0xec,0x77,0x43,0x80,0x39,
14736   0xac,0x43,0xa9,0xfe,0x77,0x43,0xb0,0x4e,0xae,0x43,0x08,0x23,0xa4,0x78,0x43,0xea,0x35,0xb0,0x43,0xd2,
14737   0x35,0x78,0x43,0xab,0x77,0xb1,0x43,0xc1,0x79,0x74,0x43,0xa2,0xa5,0xb1,0x43,0x08,0xc6,0x50,0x71,0x43,
14738   0x68,0xca,0xb1,0x43,0xab,0xce,0x6f,0x43,0xe7,0x52,0xb1,0x43,0xea,0x98,0x70,0x43,0xd4,0xa2,0xaf,0x43,
14739   0x08,0x9d,0x19,0x71,0x43,0x96,0xd8,0xae,0x43,0x9d,0x19,0x71,0x43,0xec,0x29,0xae,0x43,0xca,0x3f,0x72,
14740   0x43,0xab,0x5f,0xad,0x43,0x08,0xa6,0xf7,0x72,0x43,0xa7,0x70,0xac,0x43,0x09,0x0a,0x73,0x43,0x17,0x38,
14741   0xab,0x43,0x44,0xcd,0x6e,0x43,0x9f,0x86,0xa8,0x43,0x08,0xd4,0xed,0x6b,0x43,0xf8,0xba,0xa6,0x43,0x31,
14742   0x11,0x6b,0x43,0x2a,0xac,0xa2,0x43,0xd9,0xdc,0x6c,0x43,0x14,0x25,0xa4,0x43,0x09,0x01,0x05,0x06,0x66,
14743   0x5d,0x7a,0x43,0x74,0xeb,0xc2,0x43,0x08,0x09,0x22,0x77,0x43,0x50,0xbb,0xc7,0x43,0xe9,0xe0,0x7c,0x43,
14744   0xf5,0x86,0xc9,0x43,0x8f,0x94,0x7a,0x43,0xc5,0x95,0xcd,0x43,0x09,0x06,0x08,0x98,0x80,0x43,0x6b,0x19,
14745   0xc3,0x43,0x08,0xb7,0x35,0x82,0x43,0x79,0xf2,0xc7,0x43,0xf1,0xbe,0x7e,0x43,0x1e,0xbe,0xc9,0x43,0x73,
14746   0x7c,0x80,0x43,0xec,0xcc,0xcd,0x43,0x09,0x06,0x28,0xab,0x7d,0x43,0xae,0xde,0xc6,0x43,0x08,0x1e,0xcd,
14747   0x7b,0x43,0x8a,0xa2,0xc9,0x43,0x30,0x89,0x7f,0x43,0x5c,0x94,0xcc,0x43,0x28,0xab,0x7d,0x43,0x42,0x2a,
14748   0xcf,0x43,0x09,0x01,0x05,0x06,0x24,0x14,0xe0,0x42,0xf5,0x77,0x97,0x43,0x08,0xf7,0x1d,0xe7,0x42,0x74,
14749   0x00,0x97,0x43,0x4d,0x93,0xec,0x42,0xdb,0xf5,0x95,0x43,0x29,0x4b,0xed,0x42,0xcd,0x34,0x95,0x43,0x09,
14750   0x06,0x29,0x7b,0xf5,0x42,0x6f,0x1d,0x98,0x43,0x08,0xe4,0xf1,0xfb,0x42,0x61,0x5c,0x97,0x43,0xdb,0x7d,
14751   0x01,0x43,0xb2,0xbe,0x95,0x43,0x55,0x23,0x02,0x43,0xe7,0xaa,0x94,0x43,0x09,0x06,0x98,0xdc,0x03,0x43,
14752   0xbe,0x8b,0x98,0x43,0x08,0x66,0xdf,0x05,0x43,0x47,0xe6,0x97,0x43,0xae,0x87,0x08,0x43,0x98,0x48,0x96,
14753   0x43,0x61,0x08,0x09,0x43,0xd6,0x06,0x95,0x43,0x09,0x06,0x31,0x0b,0x0b,0x43,0x8e,0x82,0x98,0x43,0x08,
14754   0xdb,0xc5,0x0d,0x43,0x80,0xc1,0x97,0x43,0xd6,0xee,0x10,0x43,0xa9,0xec,0x95,0x43,0x79,0xcb,0x11,0x43,
14755   0x55,0x8f,0x94,0x43,0x09,0x06,0xd1,0x2f,0x18,0x43,0xdb,0x01,0x98,0x43,0x08,0xad,0xe7,0x18,0x43,0x38,
14756   0x25,0x97,0x43,0x8a,0x9f,0x19,0x43,0x80,0xb5,0x95,0x43,0xd6,0x1e,0x19,0x43,0xe0,0xd8,0x94,0x43,0x09,
14757   0x06,0x9a,0x5b,0x1d,0x43,0x58,0x8a,0x97,0x43,0x08,0x01,0x5d,0x1e,0x43,0xf1,0x88,0x96,0x43,0x2f,0x83,
14758   0x1f,0x43,0x19,0xb4,0x94,0x43,0x19,0xf0,0x1e,0x43,0x6f,0x05,0x94,0x43,0x09,0x06,0x0b,0x53,0x24,0x43,
14759   0xae,0xdb,0x96,0x43,0x08,0x25,0xd5,0x25,0x43,0x50,0xac,0x95,0x43,0x53,0xfb,0x26,0x43,0x8a,0x7b,0x93,
14760   0x43,0x76,0x43,0x26,0x43,0xb7,0x95,0x92,0x43,0x09,0x06,0x76,0x5b,0x2a,0x43,0x47,0xda,0x95,0x43,0x08,
14761   0xf3,0xef,0x2b,0x43,0x10,0xe2,0x94,0x43,0x6d,0x95,0x2c,0x43,0xae,0xc3,0x92,0x43,0x68,0xa6,0x2b,0x43,
14762   0x47,0xc2,0x91,0x43,0x09,0x06,0x36,0xc1,0x31,0x43,0x2c,0x58,0x94,0x43,0x08,0x8c,0x1e,0x33,0x43,0x31,
14763   0x3b,0x93,0x43,0x79,0x7a,0x33,0x43,0xff,0x25,0x91,0x43,0xd9,0x9d,0x32,0x43,0xc1,0x5b,0x90,0x43,0x09,
14764   0x06,0x25,0x35,0x36,0x43,0x31,0x3b,0x93,0x43,0x08,0x3f,0xb7,0x37,0x43,0xc1,0x67,0x92,0x43,0xe0,0x93,
14765   0x38,0x43,0xae,0xb7,0x90,0x43,0x7e,0x81,0x38,0x43,0x0d,0xdb,0x8f,0x43,0x09,0x06,0xb5,0x85,0x3b,0x43,
14766   0xe4,0xaf,0x91,0x43,0x08,0xcf,0x07,0x3d,0x43,0x9d,0x13,0x91,0x43,0xbc,0x63,0x3d,0x43,0x47,0xb6,0x8f,
14767   0x43,0xe5,0x9a,0x3d,0x43,0x74,0xd0,0x8e,0x43,0x09,0x06,0xae,0xc6,0x42,0x43,0xa4,0xd9,0x8e,0x43,0x08,
14768   0xca,0x48,0x44,0x43,0xfa,0x2a,0x8e,0x43,0xa2,0x11,0x44,0x43,0x9d,0xfb,0x8c,0x43,0x55,0x92,0x44,0x43,
14769   0x0d,0xc3,0x8b,0x43,0x09,0x06,0x39,0x10,0xc3,0x43,0x34,0x36,0x96,0x43,0x08,0x92,0x44,0xc1,0x43,0xe4,
14770   0xc7,0x95,0x43,0x6f,0xf0,0xbf,0x43,0x4b,0xbd,0x94,0x43,0x47,0xb9,0xbf,0x43,0x0b,0xf3,0x93,0x43,0x09,
14771   0x06,0x8f,0x49,0xbe,0x43,0xb7,0xad,0x96,0x43,0x08,0x11,0xb5,0xbc,0x43,0x77,0xe3,0x95,0x43,0x9c,0xf2,
14772   0xba,0x43,0xfa,0x4e,0x94,0x43,0xae,0x96,0xba,0x43,0x31,0x3b,0x93,0x43,0x09,0x06,0xdb,0xb0,0xb9,0x43,
14773   0x10,0xee,0x96,0x43,0x08,0x42,0xa6,0xb8,0x43,0xc8,0x51,0x96,0x43,0x50,0x5b,0xb7,0x43,0x19,0xb4,0x94,
14774   0x43,0xf7,0x1a,0xb7,0x43,0x58,0x72,0x93,0x43,0x09,0x06,0xf2,0x2b,0xb6,0x43,0x10,0xee,0x96,0x43,0x08,
14775   0x9d,0xce,0xb4,0x43,0x04,0x2d,0x96,0x43,0xed,0x30,0xb3,0x43,0x2c,0x58,0x94,0x43,0xce,0xcb,0xb2,0x43,
14776   0xd6,0xfa,0x92,0x43,0x09,0x06,0x5a,0x09,0xb1,0x43,0x19,0xc0,0x96,0x43,0x08,0x6c,0xad,0xb0,0x43,0x77,
14777   0xe3,0x95,0x43,0x7e,0x51,0xb0,0x43,0xc0,0x73,0x94,0x43,0xd8,0x91,0xb0,0x43,0x1e,0x97,0x93,0x43,0x09,
14778   0x06,0x48,0x4d,0xad,0x43,0xbe,0x7f,0x96,0x43,0x08,0x95,0xcc,0xac,0x43,0x58,0x7e,0x95,0x43,0x4d,0x30,
14779   0xac,0x43,0x80,0xa9,0x93,0x43,0xd8,0x79,0xac,0x43,0xd6,0xfa,0x92,0x43,0x09,0x06,0x90,0xd1,0xa9,0x43,
14780   0x14,0xd1,0x95,0x43,0x08,0x83,0x10,0xa9,0x43,0xb7,0xa1,0x94,0x43,0x3b,0x74,0xa8,0x43,0xf1,0x70,0x92,
14781   0x43,0x29,0xd0,0xa8,0x43,0x1e,0x8b,0x91,0x43,0x09,0x06,0x5a,0xcd,0xa6,0x43,0x8a,0x87,0x95,0x43,0x08,
14782   0x1c,0x03,0xa6,0x43,0x23,0x86,0x94,0x43,0x5f,0xb0,0xa5,0x43,0xc1,0x67,0x92,0x43,0xe1,0x27,0xa6,0x43,
14783   0x8a,0x6f,0x91,0x43,0x09,0x06,0xd4,0x5a,0xa3,0x43,0x2c,0x58,0x94,0x43,0x08,0x29,0xac,0xa2,0x43,0x31,
14784   0x3b,0x93,0x43,0x32,0x7e,0xa2,0x43,0xff,0x25,0x91,0x43,0x83,0xec,0xa2,0x43,0x8e,0x52,0x90,0x43,0x09,
14785   0x06,0xf8,0x96,0xa0,0x43,0x1e,0x97,0x93,0x43,0x08,0xeb,0xd5,0x9f,0x43,0x7b,0xba,0x92,0x43,0x99,0x67,
14786   0x9f,0x43,0x9d,0x13,0x91,0x43,0x99,0x67,0x9f,0x43,0xfa,0x36,0x90,0x43,0x09,0x06,0xeb,0xc9,0x9d,0x43,
14787   0xc8,0x39,0x92,0x43,0x08,0xde,0x08,0x9d,0x43,0xb2,0xa6,0x91,0x43,0xe6,0xda,0x9c,0x43,0x2c,0x40,0x90,
14788   0x43,0x52,0xbf,0x9c,0x43,0x5a,0x5a,0x8f,0x43,0x09,0x06,0x37,0x3d,0x9b,0x43,0x85,0x80,0x90,0x43,0x08,
14789   0x2a,0x7c,0x9a,0x43,0xdb,0xd1,0x8f,0x43,0xf0,0xa0,0x9a,0x43,0x7d,0xa2,0x8e,0x43,0x65,0x57,0x9a,0x43,
14790   0xee,0x69,0x8d,0x43,0x09,0x02,0x04,0x06,0x2a,0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x08,0x0d,0x8a,0x31,
14791   0x42,0x9f,0x0e,0x94,0x43,0xf3,0x1f,0x34,0x42,0x3d,0xfc,0x93,0x43,0x63,0xff,0x36,0x42,0xa9,0xe0,0x93,
14792   0x43,0x08,0xb5,0x34,0x5d,0x42,0x0b,0xf3,0x93,0x43,0x6d,0xa4,0x5e,0x42,0x03,0x39,0x98,0x43,0xe7,0x31,
14793   0x5b,0x42,0x93,0x89,0x9d,0x43,0x08,0x02,0x9c,0x58,0x42,0xd4,0x5a,0xa3,0x43,0x38,0x70,0x53,0x42,0x14,
14794   0x49,0xaa,0x43,0xf8,0xed,0x5e,0x42,0x83,0x28,0xad,0x43,0x08,0xea,0x68,0x68,0x42,0x20,0x22,0xaf,0x43,
14795   0x12,0xb8,0x6c,0x42,0xb5,0x49,0xb1,0x43,0x2a,0x4b,0x6d,0x42,0x0d,0x96,0xb3,0x43,0x07,0x2a,0x4b,0x6d,
14796   0x42,0xc6,0x05,0xb5,0x43,0x08,0x87,0x6e,0x6c,0x42,0x68,0xee,0xb7,0x43,0x1c,0x66,0x66,0x42,0x31,0x0e,
14797   0xbb,0x43,0x57,0x11,0x5e,0x42,0x8f,0x49,0xbe,0x43,0x08,0x66,0x96,0x54,0x42,0xb9,0x5c,0xb8,0x43,0x2c,
14798   0x2b,0x3c,0x42,0x68,0xd6,0xb3,0x43,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x07,0x2a,0xf4,0x2e,0x42,
14799   0x61,0xa4,0xa3,0x43,0x08,0x55,0x1a,0x30,0x42,0xf0,0xd0,0xa2,0x43,0xf8,0xf6,0x30,0x42,0xb2,0x06,0xa2,
14800   0x43,0x98,0xd3,0x31,0x42,0xd6,0x4e,0xa1,0x43,0x08,0x1c,0x6f,0x38,0x42,0x2a,0x94,0x9e,0x43,0xc1,0x22,
14801   0x36,0x42,0xf5,0x9b,0x9d,0x43,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x57,
14802   0xa2,0x9b,0x43,0x08,0xab,0x8f,0x35,0x42,0x8a,0xab,0x9b,0x43,0xe9,0x71,0x3a,0x42,0xb2,0xe2,0x9b,0x43,
14803   0xb7,0x74,0x3c,0x42,0x34,0x5a,0x9c,0x43,0x08,0x23,0x7d,0x42,0x42,0x0b,0x2f,0x9e,0x43,0xe5,0x9a,0x3d,
14804   0x42,0x38,0x6d,0xa3,0x43,0x36,0xd9,0x35,0x42,0xf3,0xd7,0xa7,0x43,0x08,0x12,0x61,0x2e,0x42,0xb0,0x42,
14805   0xac,0x43,0x63,0xff,0x36,0x42,0xdd,0x74,0xaf,0x43,0x1e,0xa6,0x45,0x42,0x44,0x82,0xb2,0x43,0x08,0x74,
14806   0x1b,0x4b,0x42,0x79,0x7a,0xb3,0x43,0x10,0x21,0x4f,0x42,0x2a,0x18,0xb5,0x43,0xdb,0x4c,0x54,0x42,0x91,
14807   0x19,0xb6,0x43,0x08,0xee,0x3f,0x65,0x42,0x5f,0x28,0xba,0x43,0xa7,0xaf,0x66,0x42,0xb9,0x50,0xb6,0x43,
14808   0x14,0x58,0x5c,0x42,0xca,0xdc,0xb1,0x43,0x08,0x2c,0x8b,0x4c,0x42,0x4e,0x30,0xac,0x43,0x19,0xcf,0x48,
14809   0x42,0x2a,0xd0,0xa8,0x43,0xbc,0xab,0x49,0x42,0xa9,0x4c,0xa6,0x43,0x08,0x61,0x5f,0x47,0x42,0xfa,0xa2,
14810   0xa2,0x43,0xa7,0xaf,0x66,0x42,0x85,0x98,0x94,0x43,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x07,0x2a,
14811   0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x09,0x06,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x08,0xdc,0xe3,
14812   0xf1,0x41,0xe9,0x9e,0x92,0x43,0xd2,0xe7,0x0b,0x42,0xd6,0x06,0x95,0x43,0x2a,0xf4,0x2e,0x42,0x04,0x21,
14813   0x94,0x43,0x07,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x08,0x87,0x17,0x2e,0x42,0xc3,0x62,0x95,0x43,
14814   0xe7,0x3a,0x2d,0x42,0xf5,0x6b,0x95,0x43,0x44,0x5e,0x2c,0x42,0xf5,0x6b,0x95,0x43,0x08,0xd1,0x47,0x1c,
14815   0x42,0x19,0xc0,0x96,0x43,0x66,0xdf,0x05,0x42,0x38,0x19,0x95,0x43,0x12,0x6a,0x00,0x42,0xb2,0xbe,0x95,
14816   0x43,0x08,0xbb,0x6b,0xea,0x41,0xd6,0x12,0x97,0x43,0x2d,0x82,0xfa,0x41,0x61,0x74,0x9b,0x43,0x7e,0x72,
14817   0x06,0x42,0x8a,0xab,0x9b,0x43,0x08,0xc8,0x39,0x12,0x42,0x4e,0xd0,0x9b,0x43,0x53,0xe3,0x22,0x42,0xc3,
14818   0x86,0x9b,0x43,0x2a,0xf4,0x2e,0x42,0x57,0xa2,0x9b,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,
14819   0x08,0x01,0xa5,0x2a,0x42,0xa4,0x2d,0x9d,0x43,0x96,0x9c,0x24,0x42,0x06,0x40,0x9d,0x43,0x8a,0xb7,0x1d,
14820   0x42,0x9a,0x5b,0x9d,0x43,0x08,0x6b,0x16,0x13,0x42,0xcd,0x64,0x9d,0x43,0x42,0xc7,0x0e,0x42,0x9a,0x5b,
14821   0x9d,0x43,0x23,0x26,0x04,0x42,0xcd,0x64,0x9d,0x43,0x08,0xe6,0x91,0xeb,0x41,0x38,0x49,0x9d,0x43,0x73,
14822   0x7b,0xdb,0x41,0xf5,0x83,0x99,0x43,0x7f,0x60,0xe2,0x41,0x0b,0x0b,0x98,0x43,0x08,0x7f,0x60,0xe2,0x41,
14823   0xec,0x99,0x95,0x43,0xe3,0x5a,0xde,0x41,0xbe,0x7f,0x96,0x43,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,
14824   0x07,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x09,0x06,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x08,
14825   0xd4,0x7e,0x29,0x42,0xab,0x6b,0xaf,0x43,0x4e,0x0c,0x26,0x42,0x44,0x6a,0xae,0x43,0x38,0x79,0x25,0x42,
14826   0xd4,0x96,0xad,0x43,0x08,0x25,0xbd,0x21,0x42,0xe2,0x4b,0xac,0x43,0x49,0x35,0x29,0x42,0x9a,0x97,0xa7,
14827   0x43,0x2a,0xf4,0x2e,0x42,0x61,0xa4,0xa3,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x09,0x06,
14828   0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x08,0x86,0x20,0x80,0x43,0x57,0x41,0xe6,0x43,0x7d,0x4e,0x80,
14829   0x43,0x25,0x38,0xe6,0x43,0xa5,0x85,0x80,0x43,0xf3,0x2e,0xe6,0x43,0x08,0x35,0xca,0x83,0x43,0xd4,0xc9,
14830   0xe5,0x43,0x9c,0xd7,0x86,0x43,0x44,0x91,0xe4,0x43,0xd5,0xca,0x8a,0x43,0x91,0x1c,0xe6,0x43,0x08,0x53,
14831   0x5f,0x8c,0x43,0xf8,0x1d,0xe7,0x43,0x2f,0x17,0x8d,0x43,0x4e,0x7b,0xe8,0x43,0x92,0x29,0x8d,0x43,0x2f,
14832   0x22,0xea,0x43,0x07,0x92,0x29,0x8d,0x43,0x44,0xb5,0xea,0x43,0x08,0xfe,0x0d,0x8d,0x43,0x2a,0x4b,0xed,
14833   0x43,0xe3,0x8b,0x8b,0x43,0x55,0x7d,0xf0,0x43,0xec,0x51,0x89,0x43,0x72,0x0b,0xf4,0x43,0x08,0xcd,0xd4,
14834   0x84,0x43,0x9d,0x55,0xfb,0x43,0xc9,0xe5,0x83,0x43,0x74,0x1e,0xfb,0x43,0x73,0x94,0x84,0x43,0x5a,0x90,
14835   0xf7,0x43,0x08,0xe8,0x62,0x88,0x43,0xfd,0x30,0xee,0x43,0x39,0xc5,0x86,0x43,0xdd,0xbf,0xeb,0x43,0x35,
14836   0xbe,0x81,0x43,0x40,0xde,0xed,0x43,0x08,0x4f,0x34,0x81,0x43,0x36,0x0c,0xee,0x43,0x08,0x98,0x80,0x43,
14837   0xfd,0x30,0xee,0x43,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x40,0xec,
14838   0x43,0x08,0x35,0xbe,0x81,0x43,0x06,0xf7,0xeb,0x43,0x15,0x65,0x83,0x43,0x49,0xa4,0xeb,0x43,0x1e,0x43,
14839   0x85,0x43,0xbe,0x5a,0xeb,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0x18,0xea,0x43,0x42,0x97,0x86,0x43,0x5f,
14840   0x67,0xf4,0x43,0xa9,0x98,0x87,0x43,0xd4,0x1d,0xf4,0x43,0x08,0x5c,0x25,0x8a,0x43,0xcf,0x16,0xef,0x43,
14841   0x46,0xaa,0x8d,0x43,0x5a,0x3c,0xe9,0x43,0x19,0x6c,0x88,0x43,0x53,0x5e,0xe7,0x43,0x08,0xc4,0x02,0x85,
14842   0x43,0x96,0x0b,0xe7,0x43,0x85,0x2c,0x82,0x43,0x83,0x67,0xe7,0x43,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,
14843   0x43,0x07,0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x09,0x06,0xfd,0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,
14844   0x08,0xfa,0x6c,0x78,0x43,0xd1,0xc2,0xe0,0x43,0x25,0x5c,0x6c,0x43,0x25,0x44,0xe8,0x43,0x1d,0xe5,0x7f,
14845   0x43,0x87,0x4a,0xe6,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,0x43,0x08,0xa6,0x27,0x7b,0x43,0x91,
14846   0x28,0xe8,0x43,0xbc,0xa2,0x77,0x43,0xb0,0x8d,0xe8,0x43,0xc6,0x68,0x75,0x43,0x57,0x4d,0xe8,0x43,0x08,
14847   0xe0,0xd2,0x72,0x43,0xab,0x9e,0xe7,0x43,0x50,0x9a,0x71,0x43,0x2a,0x27,0xe7,0x43,0xea,0x98,0x70,0x43,
14848   0x57,0x35,0xe4,0x43,0x08,0x94,0x3b,0x6f,0x43,0x14,0x7c,0xe2,0x43,0xff,0x13,0x6d,0x43,0x06,0xbb,0xe1,
14849   0x43,0xcf,0xfe,0x6a,0x43,0x06,0xbb,0xe1,0x43,0x08,0x44,0x9d,0x66,0x43,0x77,0x8e,0xe2,0x43,0x3b,0xef,
14850   0x6c,0x43,0x91,0x10,0xe4,0x43,0xfd,0x24,0x6c,0x43,0xb0,0x81,0xe6,0x43,0x08,0x96,0x23,0x6b,0x43,0xee,
14851   0x57,0xe9,0x43,0xca,0x0f,0x6a,0x43,0x5f,0x37,0xec,0x43,0x55,0x71,0x6e,0x43,0x9f,0x01,0xed,0x43,0x08,
14852   0xdb,0xfb,0x75,0x43,0x3b,0xef,0xec,0x43,0x09,0x3a,0x7b,0x43,0xb0,0xa5,0xec,0x43,0x1d,0xe5,0x7f,0x43,
14853   0x91,0x40,0xec,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x08,0xa9,0x16,0x7c,0x43,0xb0,0xb1,
14854   0xee,0x43,0x47,0xec,0x77,0x43,0xd9,0xe8,0xee,0x43,0x1e,0x9d,0x73,0x43,0xcf,0x16,0xef,0x43,0x08,0x0e,
14855   0xc9,0x6b,0x43,0xee,0x7b,0xef,0x43,0x7e,0x90,0x6a,0x43,0xfd,0x30,0xee,0x43,0x01,0xfc,0x68,0x43,0x4e,
14856   0x93,0xec,0x43,0x08,0x31,0xf9,0x66,0x43,0x4e,0x87,0xea,0x43,0x31,0x11,0x6b,0x43,0xd4,0xd5,0xe7,0x43,
14857   0xd9,0xc4,0x68,0x43,0xd4,0xc9,0xe5,0x43,0x08,0xe5,0x79,0x67,0x43,0x77,0x9a,0xe4,0x43,0x44,0x9d,0x66,
14858   0x43,0xab,0x86,0xe3,0x43,0x7e,0x78,0x66,0x43,0x0b,0xaa,0xe2,0x43,0x07,0x7e,0x78,0x66,0x43,0x57,0x29,
14859   0xe2,0x43,0x08,0xa7,0xaf,0x66,0x43,0xbe,0x1e,0xe1,0x43,0x87,0x56,0x68,0x43,0x77,0x82,0xe0,0x43,0xfd,
14860   0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,0x09,0x06,0xc4,0x41,0xbf,0x43,0x85,0xc0,0x72,0x42,0x08,0x73,0xdf,
14861   0xc0,0x43,0xf4,0x76,0x72,0x42,0x97,0x33,0xc2,0x43,0x85,0xc0,0x72,0x42,0xb2,0xb5,0xc3,0x43,0x64,0x56,
14862   0x75,0x42,0x08,0x03,0x24,0xc4,0x43,0x5e,0x7f,0x78,0x42,0xfa,0x51,0xc4,0x43,0x01,0x85,0x7c,0x42,0x5c,
14863   0x64,0xc4,0x43,0xa0,0xb3,0x80,0x42,0x07,0x5c,0x64,0xc4,0x43,0x10,0x93,0x83,0x42,0x08,0xc8,0x48,0xc4,
14864   0x43,0x1c,0x78,0x8a,0x42,0x27,0x6c,0xc3,0x43,0xaf,0xcf,0x94,0x42,0x23,0x7d,0xc2,0x43,0x99,0x9c,0xa4,
14865   0x42,0x08,0x3d,0xe7,0xbf,0x43,0xfb,0xfd,0xb5,0x42,0xb3,0x9d,0xbf,0x43,0x88,0x17,0xae,0x42,0xc4,0x41,
14866   0xbf,0x43,0x69,0x76,0xa3,0x42,0x07,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x08,0x4f,0x8b,0xbf,0x43,
14867   0xed,0x81,0x91,0x42,0xe4,0xa6,0xbf,0x43,0x5d,0x61,0x94,0x42,0xfa,0x39,0xc0,0x43,0x3b,0x49,0x9d,0x42,
14868   0x08,0x2b,0x43,0xc0,0x43,0x28,0xed,0xa9,0x42,0x61,0x3b,0xc1,0x43,0x00,0x9e,0xa5,0x42,0xe4,0xb2,0xc1,
14869   0x43,0x5d,0x91,0x9c,0x42,0x08,0x78,0xce,0xc1,0x43,0xfd,0x36,0x90,0x42,0x22,0x89,0xc4,0x43,0x81,0x72,
14870   0x86,0x42,0xae,0xc6,0xc2,0x43,0xa0,0xb3,0x80,0x42,0x08,0x54,0x86,0xc2,0x43,0x58,0xd1,0x7e,0x42,0x30,
14871   0x32,0xc1,0x43,0xce,0x5e,0x7b,0x42,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x07,0xc4,0x41,0xbf,0x43,
14872   0x85,0xc0,0x72,0x42,0x09,0x06,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x08,0x35,0xfd,0xbb,0x43,0xa4,
14873   0xa1,0x5c,0x42,0x5e,0x34,0xbc,0x43,0x9d,0x2a,0x70,0x42,0x5e,0x40,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,
14874   0x4c,0x9c,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,0xef,0xbe,0x43,0x0e,0x0a,0x73,0x42,0xc4,0x41,0xbf,0x43,
14875   0x85,0xc0,0x72,0x42,0x07,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x08,0xcd,0x13,0xbf,0x43,0xe8,0xf1,
14876   0x7b,0x42,0xd6,0xe5,0xbe,0x43,0x71,0x3b,0x7c,0x42,0xdf,0xb7,0xbe,0x43,0x71,0x3b,0x7c,0x42,0x08,0x08,
14877   0xe3,0xbc,0x43,0xa4,0x61,0x7d,0x42,0x28,0x3c,0xbb,0x43,0x91,0x45,0x69,0x42,0x28,0x3c,0xbb,0x43,0x58,
14878   0x71,0x6e,0x42,0x08,0xce,0xfb,0xba,0x43,0xd5,0x35,0x78,0x42,0x59,0x45,0xbb,0x43,0x58,0x23,0x82,0x42,
14879   0xa1,0xe1,0xbb,0x43,0xd7,0xbe,0x88,0x42,0x08,0xc9,0x18,0xbc,0x43,0xaf,0x9f,0x8c,0x42,0x1e,0x76,0xbd,
14880   0x43,0x51,0x7c,0x8d,0x42,0xd6,0xe5,0xbe,0x43,0xf4,0x58,0x8e,0x42,0x08,0x9c,0x0a,0xbf,0x43,0x45,0xc7,
14881   0x8e,0x42,0x30,0x26,0xbf,0x43,0x96,0x35,0x8f,0x42,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x07,0xc4,
14882   0x41,0xbf,0x43,0x69,0x76,0xa3,0x42,0x08,0x08,0xef,0xbe,0x43,0xb1,0xd6,0x99,0x42,0xe8,0x89,0xbe,0x43,
14883   0xde,0xc5,0x8d,0x42,0xc0,0x46,0xbc,0x43,0xc2,0x5b,0x90,0x42,0x08,0x9c,0xf2,0xba,0x43,0x86,0x80,0x90,
14884   0x42,0xf2,0x43,0xba,0x43,0xe8,0x73,0x87,0x42,0x8f,0x31,0xba,0x43,0xb6,0xf4,0x7d,0x42,0x07,0x8f,0x31,
14885   0xba,0x43,0x21,0xc6,0x76,0x42,0x08,0xc0,0x3a,0xba,0x43,0x5f,0x48,0x6b,0x42,0xae,0x96,0xba,0x43,0xe3,
14886   0x83,0x61,0x42,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x09,0x06,0xea,0x74,0xea,0x43,0x61,0x44,0x93,
14887   0x43,0x08,0x24,0x5c,0xec,0x43,0x31,0x3b,0x93,0x43,0xfb,0x30,0xee,0x43,0x93,0x4d,0x93,0x43,0x0d,0xe1,
14888   0xef,0x43,0x80,0xa9,0x93,0x43,0x08,0x8f,0x58,0xf0,0x43,0xd1,0x17,0x94,0x43,0xb7,0x8f,0xf0,0x43,0x10,
14889   0xe2,0x94,0x43,0xea,0x98,0xf0,0x43,0xa9,0xec,0x95,0x43,0x07,0xea,0x98,0xf0,0x43,0x38,0x25,0x97,0x43,
14890   0x08,0x23,0x74,0xf0,0x43,0x9f,0x32,0x9a,0x43,0x5a,0x60,0xef,0x43,0x53,0xcb,0x9e,0x43,0x2d,0x3a,0xee,
14891   0x43,0xfd,0x91,0xa3,0x43,0x08,0xa2,0xf0,0xed,0x43,0xdd,0x38,0xa5,0x43,0x17,0xa7,0xed,0x43,0xbe,0xdf,
14892   0xa6,0x43,0x5a,0x54,0xed,0x43,0x9f,0x86,0xa8,0x43,0x08,0xfc,0x24,0xec,0x43,0xca,0xc4,0xad,0x43,0x48,
14893   0xa4,0xeb,0x43,0x40,0x6f,0xab,0x43,0x28,0x3f,0xeb,0x43,0x1c,0x0f,0xa8,0x43,0x08,0x1f,0x6d,0xeb,0x43,
14894   0x72,0x48,0xa3,0x43,0x67,0x09,0xec,0x43,0xd1,0x53,0x9e,0x43,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,0x43,
14895   0x07,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x08,0x7e,0x90,0xea,0x43,0x8a,0x9f,0x99,0x43,0x12,0xac,
14896   0xea,0x43,0xbc,0xa8,0x99,0x43,0xa7,0xc7,0xea,0x43,0xbc,0xa8,0x99,0x43,0x08,0x51,0x76,0xeb,0x43,0x9f,
14897   0x32,0x9a,0x43,0x5e,0x37,0xec,0x43,0x49,0xed,0x9c,0x43,0xb0,0xa5,0xec,0x43,0x2a,0xa0,0xa0,0x43,0x08,
14898   0x09,0xe6,0xec,0x43,0xd1,0x77,0xa4,0x43,0x28,0x4b,0xed,0x43,0x61,0xa4,0xa3,0x43,0xab,0xc2,0xed,0x43,
14899   0x8e,0xb2,0xa0,0x43,0x08,0x70,0xe7,0xed,0x43,0xde,0x08,0x9d,0x43,0x87,0x86,0xf0,0x43,0x2f,0x53,0x97,
14900   0x43,0x87,0x7a,0xee,0x43,0xec,0x99,0x95,0x43,0x08,0xca,0x27,0xee,0x43,0xff,0x3d,0x95,0x43,0x74,0xca,
14901   0xec,0x43,0x55,0x8f,0x94,0x43,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x07,0xea,0x74,0xea,0x43,0x61,
14902   0x44,0x93,0x43,0x09,0x06,0x05,0xd3,0xe5,0x43,0x19,0x9c,0x90,0x43,0x08,0x09,0xc2,0xe6,0x43,0xd1,0xff,
14903   0x8f,0x43,0x4d,0x6f,0xe6,0x43,0x74,0xe8,0x92,0x43,0x3b,0xd7,0xe8,0x43,0xc3,0x56,0x93,0x43,0x08,0x1f,
14904   0x61,0xe9,0x43,0x93,0x4d,0x93,0x43,0x05,0xeb,0xe9,0x43,0x93,0x4d,0x93,0x43,0xea,0x74,0xea,0x43,0x61,
14905   0x44,0x93,0x43,0x07,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x08,0x24,0x50,0xea,0x43,0xe7,0xaa,0x94,
14906   0x43,0x2d,0x22,0xea,0x43,0xe7,0xaa,0x94,0x43,0x36,0xf4,0xe9,0x43,0xe7,0xaa,0x94,0x43,0x08,0xa2,0xcc,
14907   0xe7,0x43,0xe0,0xd8,0x94,0x43,0xd4,0xc9,0xe5,0x43,0x19,0xa8,0x92,0x43,0xd4,0xc9,0xe5,0x43,0x27,0x69,
14908   0x93,0x43,0x08,0x17,0x77,0xe5,0x43,0xe0,0xd8,0x94,0x43,0x67,0xe5,0xe5,0x43,0x47,0xda,0x95,0x43,0x43,
14909   0x9d,0xe6,0x43,0xe2,0xd3,0x97,0x43,0x08,0x9d,0xdd,0xe6,0x43,0xad,0xe7,0x98,0x43,0x09,0xce,0xe8,0x43,
14910   0xff,0x55,0x99,0x43,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x07,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,
14911   0x43,0x08,0x71,0xcf,0xe9,0x43,0x53,0xb3,0x9a,0x43,0xa7,0xbb,0xe8,0x43,0xdb,0x0d,0x9a,0x43,0xc6,0x14,
14912   0xe7,0x43,0xdb,0x0d,0x9a,0x43,0x08,0x48,0x80,0xe5,0x43,0xdb,0x0d,0x9a,0x43,0x0a,0xb6,0xe4,0x43,0xc3,
14913   0x6e,0x97,0x43,0x76,0x9a,0xe4,0x43,0x74,0xf4,0x94,0x43,0x07,0x76,0x9a,0xe4,0x43,0x79,0xd7,0x93,0x43,
14914   0x08,0xd8,0xac,0xe4,0x43,0x66,0x27,0x92,0x43,0x29,0x1b,0xe5,0x43,0xe0,0xc0,0x90,0x43,0x05,0xd3,0xe5,
14915   0x43,0x19,0x9c,0x90,0x43,0x09,0x06,0x1b,0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x08,0x71,0x0b,0xf4,0x42,
14916   0x00,0x0e,0x8d,0x42,0x8c,0x0f,0x01,0x43,0x3e,0xc0,0x89,0x42,0xf3,0x28,0x06,0x43,0x48,0x9e,0x8b,0x42,
14917   0x08,0x15,0x89,0x09,0x43,0x00,0x0e,0x8d,0x42,0xe0,0x9c,0x0a,0x43,0xc1,0x8b,0x98,0x42,0xa6,0xc1,0x0a,
14918   0x43,0x02,0xa5,0xaa,0x42,0x07,0xa6,0xc1,0x0a,0x43,0xf9,0xf6,0xb0,0x42,0x08,0xa6,0xc1,0x0a,0x43,0x47,
14919   0x8e,0xb4,0x42,0x42,0xaf,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0xe0,0x9c,0x0a,0x43,0xba,0x74,0xbc,0x42,0x08,
14920   0xa1,0xd2,0x09,0x43,0x40,0x47,0xd0,0x42,0x0d,0xab,0x07,0x43,0x91,0xb5,0xd0,0x42,0x3b,0xb9,0x04,0x43,
14921   0xec,0x71,0xba,0x42,0x08,0xe5,0x5b,0x03,0x43,0xe3,0x33,0xa8,0x42,0x63,0xd8,0x00,0x43,0xce,0x70,0x9f,
14922   0x42,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x07,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,0x42,0x08,0xed,
14923   0x6f,0xed,0x42,0x73,0x24,0x9d,0x42,0xd8,0x0c,0xf5,0x42,0x99,0x6c,0x9c,0x42,0x27,0xab,0xfd,0x42,0xea,
14924   0xda,0x9c,0x42,0x08,0x36,0xca,0x03,0x43,0x2b,0x94,0x9e,0x42,0x68,0xc7,0x01,0x43,0x8f,0xbe,0xa2,0x42,
14925   0xfa,0x06,0x08,0x43,0x73,0xb4,0xb5,0x42,0x08,0x8e,0x2e,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0x9d,0xe3,0x08,
14926   0x43,0xd7,0x1e,0x99,0x42,0x28,0x15,0x05,0x43,0x32,0x3b,0x93,0x42,0x08,0x63,0xf0,0x04,0x43,0x70,0xed,
14927   0x8f,0x42,0x71,0x0b,0xf4,0x42,0x32,0x3b,0x93,0x42,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x07,0x1b,
14928   0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x09,0x06,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,0x08,0x8e,0x55,
14929   0xc0,0x42,0xb8,0x4d,0x86,0x42,0x60,0xbf,0xd7,0x42,0x3e,0xf0,0x91,0x42,0x63,0xf6,0xe4,0x42,0x70,0xed,
14930   0x8f,0x42,0x08,0x7a,0x89,0xe5,0x42,0xac,0xc8,0x8f,0x42,0xcc,0xf7,0xe5,0x42,0xac,0xc8,0x8f,0x42,0x1b,
14931   0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x07,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x08,0x63,0xf6,0xe4,
14932   0x42,0x3b,0x19,0x95,0x42,0xe6,0x61,0xe3,0x42,0x00,0x3e,0x95,0x42,0xf4,0x16,0xe2,0x42,0xc4,0x62,0x95,
14933   0x42,0x08,0x6e,0x74,0xd6,0x42,0x15,0xd1,0x95,0x42,0x97,0x63,0xca,0x42,0xaf,0xcf,0x94,0x42,0xfb,0x2d,
14934   0xbe,0x42,0x86,0x80,0x90,0x42,0x08,0x97,0x03,0xba,0x42,0xce,0x10,0x8f,0x42,0x5e,0x28,0xba,0x42,0x3e,
14935   0xf0,0x91,0x42,0xf2,0x4f,0xbc,0x42,0x45,0xf7,0x96,0x42,0x08,0x27,0x54,0xbf,0x42,0x73,0x24,0x9d,0x42,
14936   0xa5,0xe8,0xc0,0x42,0x86,0xe0,0xa0,0x42,0xe4,0xca,0xc5,0x42,0xed,0x11,0xaa,0x42,0x08,0x54,0xaa,0xc8,
14937   0x42,0x86,0x40,0xb1,0x42,0x59,0x81,0xc5,0x42,0xa1,0x11,0xc4,0x42,0x3e,0xe7,0xbf,0x42,0xfb,0x8d,0xce,
14938   0x42,0x08,0xb4,0x6d,0xb7,0x42,0x30,0xc2,0xd9,0x42,0x46,0xf5,0xc9,0x42,0xdf,0x53,0xd9,0x42,0x38,0x40,
14939   0xcb,0x42,0x62,0x8f,0xcf,0x42,0x08,0x7d,0xf9,0xcc,0x42,0xec,0xa1,0xc2,0x42,0x07,0x43,0xcd,0x42,0x6c,
14940   0xdd,0xb8,0x42,0x2b,0x8b,0xcc,0x42,0x92,0xf5,0xaf,0x42,0x08,0xf9,0x8d,0xce,0x42,0x41,0x57,0xa7,0x42,
14941   0x5b,0xb8,0xd2,0x42,0xae,0x2f,0xa5,0x42,0x18,0x2f,0xd9,0x42,0x13,0x2a,0xa1,0x42,0x08,0x41,0x7e,0xdd,
14942   0x42,0xe3,0x03,0xa0,0x42,0x2e,0xf2,0xe1,0x42,0x7c,0x02,0x9f,0x42,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,
14943   0x42,0x07,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x08,0x4d,0x63,0xe4,0x42,0x00,0x9e,0xa5,0x42,0xf4,
14944   0x16,0xe2,0x42,0x15,0x31,0xa6,0x42,0x99,0xca,0xdf,0x42,0x2b,0xc4,0xa6,0x42,0x08,0xc0,0x82,0xc6,0x42,
14945   0xc4,0xc2,0xa5,0x42,0x57,0xe1,0xd5,0x42,0x91,0xb5,0xd0,0x42,0x54,0xda,0xd0,0x42,0x97,0x93,0xd2,0x42,
14946   0x08,0x9c,0x3a,0xc7,0x42,0x17,0x58,0xdc,0x42,0x9c,0x0a,0xbf,0x42,0x6e,0xa4,0xde,0x42,0x90,0x25,0xb8,
14947   0x42,0xdf,0x53,0xd9,0x42,0x08,0x59,0x21,0xb5,0x42,0xf2,0xdf,0xd4,0x42,0x51,0x43,0xb3,0x42,0x91,0xb5,
14948   0xd0,0x42,0xc5,0x29,0xbb,0x42,0x0e,0x1a,0xca,0x42,0x08,0x65,0x36,0xc4,0x42,0xd0,0x07,0xbd,0x42,0x3e,
14949   0xe7,0xbf,0x42,0x37,0x09,0xbe,0x42,0x0c,0xea,0xc1,0x42,0xcd,0xd0,0xaf,0x42,0x08,0x2b,0x5b,0xc4,0x42,
14950   0x18,0x08,0xa3,0x42,0x67,0xa6,0xab,0x42,0x99,0x3c,0x94,0x42,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,
14951   0x09,];
14952 
14953 private struct ThePath {
14954 public:
14955   enum Command {
14956     Bounds, // always first, has 4 args (x0, y0, x1, y1)
14957     StrokeMode,
14958     FillMode,
14959     StrokeFillMode,
14960     NormalStroke,
14961     ThinStroke,
14962     MoveTo,
14963     LineTo,
14964     CubicTo, // cubic bezier
14965     EndPath,
14966   }
14967 
14968 public:
14969   const(ubyte)[] path;
14970   uint ppos;
14971 
14972 public:
14973   this (const(void)[] apath) pure nothrow @trusted @nogc {
14974     path = cast(const(ubyte)[])apath;
14975   }
14976 
14977   @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (ppos >= path.length); }
14978 
14979   Command getCommand () nothrow @trusted @nogc {
14980     pragma(inline, true);
14981     if (ppos >= cast(uint)path.length) assert(0, "invalid path");
14982     return cast(Command)(path.ptr[ppos++]);
14983   }
14984 
14985   // number of (x,y) pairs for this command
14986   static int argCount (in Command cmd) nothrow @safe @nogc {
14987     version(aliced) pragma(inline, true);
14988          if (cmd == Command.Bounds) return 2;
14989     else if (cmd == Command.MoveTo || cmd == Command.LineTo) return 1;
14990     else if (cmd == Command.CubicTo) return 3;
14991     else return 0;
14992   }
14993 
14994   void skipArgs (int argc) nothrow @trusted @nogc {
14995     pragma(inline, true);
14996     ppos += cast(uint)(float.sizeof*2*argc);
14997   }
14998 
14999   float getFloat () nothrow @trusted @nogc {
15000     pragma(inline, true);
15001     if (ppos >= cast(uint)path.length || cast(uint)path.length-ppos < float.sizeof) assert(0, "invalid path");
15002     version(LittleEndian) {
15003       float res = *cast(const(float)*)(&path.ptr[ppos]);
15004       ppos += cast(uint)float.sizeof;
15005       return res;
15006     } else {
15007       static assert(float.sizeof == 4);
15008       uint xp = path.ptr[ppos]|(path.ptr[ppos+1]<<8)|(path.ptr[ppos+2]<<16)|(path.ptr[ppos+3]<<24);
15009       ppos += cast(uint)float.sizeof;
15010       return *cast(const(float)*)(&xp);
15011     }
15012   }
15013 }
15014 
15015 // this will add baphomet's background path to the current NanoVega path, so you can fill it.
15016 public void addBaphometBack (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc {
15017   if (nvg is null) return;
15018 
15019   auto path = ThePath(baphometPath);
15020 
15021   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15022   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15023 
15024   bool inPath = false;
15025   while (!path.empty) {
15026     auto cmd = path.getCommand();
15027     switch (cmd) {
15028       case ThePath.Command.MoveTo:
15029         inPath = true;
15030         immutable float ex = getScaledX();
15031         immutable float ey = getScaledY();
15032         nvg.moveTo(ex, ey);
15033         break;
15034       case ThePath.Command.LineTo:
15035         inPath = true;
15036         immutable float ex = getScaledX();
15037         immutable float ey = getScaledY();
15038         nvg.lineTo(ex, ey);
15039         break;
15040       case ThePath.Command.CubicTo: // cubic bezier
15041         inPath = true;
15042         immutable float x1 = getScaledX();
15043         immutable float y1 = getScaledY();
15044         immutable float x2 = getScaledX();
15045         immutable float y2 = getScaledY();
15046         immutable float ex = getScaledX();
15047         immutable float ey = getScaledY();
15048         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15049         break;
15050       case ThePath.Command.EndPath:
15051         if (inPath) return;
15052         break;
15053       default:
15054         path.skipArgs(path.argCount(cmd));
15055         break;
15056     }
15057   }
15058 }
15059 
15060 // this will add baphomet's pupil paths to the current NanoVega path, so you can fill it.
15061 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 {
15062   // pupils starts with "fill-and-stroke" mode
15063   if (nvg is null) return;
15064 
15065   auto path = ThePath(baphometPath);
15066 
15067   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15068   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15069 
15070   bool inPath = false;
15071   bool pupLeft = true;
15072   while (!path.empty) {
15073     auto cmd = path.getCommand();
15074     switch (cmd) {
15075       case ThePath.Command.StrokeFillMode: inPath = true; break;
15076       case ThePath.Command.MoveTo:
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.moveTo(ex, ey);
15083         break;
15084       case ThePath.Command.LineTo:
15085         if (!inPath) goto default;
15086         static if (!left) { if (pupLeft) goto default; }
15087         static if (!right) { if (!pupLeft) goto default; }
15088         immutable float ex = getScaledX();
15089         immutable float ey = getScaledY();
15090         nvg.lineTo(ex, ey);
15091         break;
15092       case ThePath.Command.CubicTo: // cubic bezier
15093         if (!inPath) goto default;
15094         static if (!left) { if (pupLeft) goto default; }
15095         static if (!right) { if (!pupLeft) goto default; }
15096         immutable float x1 = getScaledX();
15097         immutable float y1 = getScaledY();
15098         immutable float x2 = getScaledX();
15099         immutable float y2 = getScaledY();
15100         immutable float ex = getScaledX();
15101         immutable float ey = getScaledY();
15102         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15103         break;
15104       case ThePath.Command.EndPath:
15105         if (inPath) {
15106           if (pupLeft) pupLeft = false; else return;
15107         }
15108         break;
15109       default:
15110         path.skipArgs(path.argCount(cmd));
15111         break;
15112     }
15113   }
15114 }
15115 
15116 // mode: 'f' to allow fills; 's' to allow strokes; 'w' to allow stroke widths; 'c' to replace fills with strokes
15117 public void renderBaphomet(string mode="fs") (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc {
15118   template hasChar(char ch, string s) {
15119          static if (s.length == 0) enum hasChar = false;
15120     else static if (s[0] == ch) enum hasChar = true;
15121     else enum hasChar = hasChar!(ch, s[1..$]);
15122   }
15123   enum AllowStroke = hasChar!('s', mode);
15124   enum AllowFill = hasChar!('f', mode);
15125   enum AllowWidth = hasChar!('w', mode);
15126   enum Contour = hasChar!('c', mode);
15127   //static assert(AllowWidth || AllowFill);
15128 
15129   if (nvg is null) return;
15130 
15131   auto path = ThePath(baphometPath);
15132 
15133   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15134   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15135 
15136   int mode = 0;
15137   int sw = ThePath.Command.NormalStroke;
15138   nvg.beginPath();
15139   while (!path.empty) {
15140     auto cmd = path.getCommand();
15141     switch (cmd) {
15142       case ThePath.Command.StrokeMode: mode = ThePath.Command.StrokeMode; break;
15143       case ThePath.Command.FillMode: mode = ThePath.Command.FillMode; break;
15144       case ThePath.Command.StrokeFillMode: mode = ThePath.Command.StrokeFillMode; break;
15145       case ThePath.Command.NormalStroke: sw = ThePath.Command.NormalStroke; break;
15146       case ThePath.Command.ThinStroke: sw = ThePath.Command.ThinStroke; break;
15147       case ThePath.Command.MoveTo:
15148         immutable float ex = getScaledX();
15149         immutable float ey = getScaledY();
15150         nvg.moveTo(ex, ey);
15151         break;
15152       case ThePath.Command.LineTo:
15153         immutable float ex = getScaledX();
15154         immutable float ey = getScaledY();
15155         nvg.lineTo(ex, ey);
15156         break;
15157       case ThePath.Command.CubicTo: // cubic bezier
15158         immutable float x1 = getScaledX();
15159         immutable float y1 = getScaledY();
15160         immutable float x2 = getScaledX();
15161         immutable float y2 = getScaledY();
15162         immutable float ex = getScaledX();
15163         immutable float ey = getScaledY();
15164         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15165         break;
15166       case ThePath.Command.EndPath:
15167         if (mode == ThePath.Command.FillMode || mode == ThePath.Command.StrokeFillMode) {
15168           static if (AllowFill || Contour) {
15169             static if (Contour) {
15170               if (mode == ThePath.Command.FillMode) { nvg.strokeWidth = 1; nvg.stroke(); }
15171             } else {
15172               nvg.fill();
15173             }
15174           }
15175         }
15176         if (mode == ThePath.Command.StrokeMode || mode == ThePath.Command.StrokeFillMode) {
15177           static if (AllowStroke || Contour) {
15178             static if (AllowWidth) {
15179                    if (sw == ThePath.Command.NormalStroke) nvg.strokeWidth = 1;
15180               else if (sw == ThePath.Command.ThinStroke) nvg.strokeWidth = 0.5;
15181               else assert(0, "wtf?!");
15182             }
15183             nvg.stroke();
15184           }
15185         }
15186         nvg.newPath();
15187         break;
15188       default:
15189         path.skipArgs(path.argCount(cmd));
15190         break;
15191     }
15192   }
15193   nvg.newPath();
15194 }