The OpenD Programming Language

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     ----------------------
395        // in initialization:
396        auto font = ctx.createFont("Roboto", "fonts/Roboto.ttf");
397        enforce(font != -1, "Failed to load font!");
398        foreach (fallbackPath; ["fonts/NotoSans.ttf"]) {
399          auto fallback = ctx.createFont("fallback", fallbackPath);
400          if (fallback == -1) {
401            stderr.writeln("Failed to load fallback font ", fallbackPath);
402            continue;
403          }
404          ctx.addFallbackFont(font, fallback);
405        }
406 
407        // rendering:
408        ctx.fontFaceId = font;
409        //ctx.fontBlur = 3;
410        //ctx.textAlign = NVGTextAlign.H.Center;
411        ctx.fontSize = 20;
412        ctx.fillColor = NVGColor.white;
413        ctx.text(0, height, text);
414     ----------------------
415 
416     Font measure functions return values in local space, the calculations are
417     carried in the same resolution as the final rendering. This is done because
418     the text glyph positions are snapped to the nearest pixels sharp rendering.
419 
420     The local space means that values are not rotated or scale as per the current
421     transformation. For example if you set font size to 12, which would mean that
422     line height is 16, then regardless of the current scaling and rotation, the
423     returned line height is always 16. Some measures may vary because of the scaling
424     since aforementioned pixel snapping.
425 
426     While this may sound a little odd, the setup allows you to always render the
427     same way regardless of scaling. I.e. following works regardless of scaling:
428 
429     ----------------------
430        string txt = "Text me up.";
431        vg.textBounds(x, y, txt, bounds);
432        vg.beginPath();
433        vg.roundedRect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1], 6);
434        vg.fill();
435     ----------------------
436 
437     Note: currently only solid color fill is supported for text.
438 
439   font_stash =
440     ## Low-Level Font Engine (FontStash)
441 
442     FontStash is used to load fonts, to manage font atlases, and to get various text metrics.
443     You don't need any graphics context to use FontStash, so you can do things like text
444     layouting outside of your rendering code. Loaded fonts are refcounted, so it is cheap
445     to create new FontStash, copy fonts from NanoVega context into it, and use that new
446     FontStash to do some UI layouting, for example. Also note that you can get text metrics
447     without creating glyph bitmaps (by using [FONSTextBoundsIterator], for example); this way
448     you don't need to waste CPU and memory resources to render unneeded images into font atlas,
449     and you can layout alot of text very fast.
450 
451     Note that "FontStash" is abbrevated as "FONS". So when you see some API that contains
452     word "fons" in it, this is not a typo, and it should not read "font" intead.
453 
454     TODO for Ketmar: write some nice example code here, and finish documenting FontStash API.
455  */
456 module arsd.nanovega;
457 
458 /// This example shows how to do the NanoVega sample without the [NVGWindow] helper class.
459 unittest {
460 	import arsd.simpledisplay;
461 
462 	import arsd.nanovega;
463 
464 	void main () {
465 	  NVGContext nvg; // our NanoVega context
466 
467 	  // we need at least OpenGL3 with GLSL to use NanoVega,
468 	  // so let's tell simpledisplay about that
469 	  setOpenGLContextVersion(3, 0);
470 
471 	  // now create OpenGL window
472 	  auto sdmain = new SimpleWindow(800, 600, "NanoVega Simple Sample", OpenGlOptions.yes, Resizability.allowResizing);
473 
474 	  // we need to destroy NanoVega context on window close
475 	  // stricly speaking, it is not necessary, as nothing fatal
476 	  // will happen if you'll forget it, but let's be polite.
477 	  // note that we cannot do that *after* our window was closed,
478 	  // as we need alive OpenGL context to do proper cleanup.
479 	  sdmain.onClosing = delegate () {
480 	    nvg.kill();
481 	  };
482 
483 	  // this is called just before our window will be shown for the first time.
484 	  // we must create NanoVega context here, as it needs to initialize
485 	  // internal OpenGL subsystem with valid OpenGL context.
486 	  sdmain.visibleForTheFirstTime = delegate () {
487 	    // yes, that's all
488             sdmain.setAsCurrentOpenGlContext();
489 	    nvg = nvgCreateContext();
490 	    if (nvg is null) assert(0, "cannot initialize NanoVega");
491 	  };
492 
493 	  // this callback will be called when we will need to repaint our window
494 	  sdmain.redrawOpenGlScene = delegate () {
495 	    // fix viewport (we can do this in resize event, or here, it doesn't matter)
496 	    glViewport(0, 0, sdmain.width, sdmain.height);
497 
498 	    // clear window
499 	    glClearColor(0, 0, 0, 0);
500 	    glClear(glNVGClearFlags); // use NanoVega API to get flags for OpenGL call
501 
502 	    {
503 	      nvg.beginFrame(sdmain.width, sdmain.height); // begin rendering
504 	      scope(exit) nvg.endFrame(); // and flush render queue on exit
505 
506 	      nvg.beginPath(); // start new path
507 	      nvg.roundedRect(20.5, 30.5, sdmain.width-40, sdmain.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation)
508 	      // now set filling mode for our rectangle
509 	      // you can create colors using HTML syntax, or with convenient constants
510 	      nvg.fillPaint = nvg.linearGradient(20.5, 30.5, sdmain.width-40, sdmain.height-60, NVGColor("#f70"), NVGColor.green);
511 	      // now fill our rect
512 	      nvg.fill();
513 	      // and draw a nice outline
514 	      nvg.strokeColor = NVGColor.white;
515 	      nvg.strokeWidth = 2;
516 	      nvg.stroke();
517 	      // that's all, folks!
518 	    }
519 	  };
520 
521 	  sdmain.eventLoop(0, // no pulse timer required
522 	    delegate (KeyEvent event) {
523 	      if (event == "*-Q" || event == "Escape") { sdmain.close(); return; } // quit on Q, Ctrl+Q, and so on
524 	    },
525 	  );
526 
527 	  flushGui(); // let OS do it's cleanup
528 	}
529 }
530 
531 private:
532 
533 version(aliced) {
534   import iv.meta;
535   import iv.vfs;
536 } else {
537   private alias usize = size_t;
538   // i fear phobos!
539   private template Unqual(T) {
540          static if (is(T U ==          immutable U)) alias Unqual = U;
541     else static if (is(T U == shared inout const U)) alias Unqual = U;
542     else static if (is(T U == shared inout       U)) alias Unqual = U;
543     else static if (is(T U == shared       const U)) alias Unqual = U;
544     else static if (is(T U == shared             U)) alias Unqual = U;
545     else static if (is(T U ==        inout const U)) alias Unqual = U;
546     else static if (is(T U ==        inout       U)) alias Unqual = U;
547     else static if (is(T U ==              const U)) alias Unqual = U;
548     else alias Unqual = T;
549   }
550   private template isAnyCharType(T, bool unqual=false) {
551     static if (unqual) private alias UT = Unqual!T; else private alias UT = T;
552     enum isAnyCharType = is(UT == char) || is(UT == wchar) || is(UT == dchar);
553   }
554   private template isWideCharType(T, bool unqual=false) {
555     static if (unqual) private alias UT = Unqual!T; else private alias UT = T;
556     enum isWideCharType = is(UT == wchar) || is(UT == dchar);
557   }
558 }
559 version(nanovg_disable_vfs) {
560   enum NanoVegaHasIVVFS = false;
561 } else {
562   static if (is(typeof((){import iv.vfs;}))) {
563     enum NanoVegaHasIVVFS = true;
564     import iv.vfs;
565   } else {
566     enum NanoVegaHasIVVFS = false;
567   }
568 }
569 
570 // ////////////////////////////////////////////////////////////////////////// //
571 // engine
572 // ////////////////////////////////////////////////////////////////////////// //
573 import core.stdc.stdlib : malloc, realloc, free;
574 import core.stdc.string : memset, memcpy, strlen;
575 import std.math : PI;
576 
577 //version = nanovg_force_stb_ttf;
578 
579 version(Posix) {
580   version = nanovg_use_freetype;
581 } else {
582   version = nanovg_disable_fontconfig;
583 }
584 
585 version (bindbc) {
586   version = nanovg_builtin_fontconfig_bindings;
587   version = nanovg_bindbc_opengl_bindings;
588   version = nanovg_bindbc_freetype_bindings;
589   version(BindFT_Dynamic)
590     static assert(0, "AsumFace was too lazy to write the code for the dynamic bindbc freetype bindings");
591   else {
592     version(BindFT_Static) {}
593     else
594       static assert(0, "well, duh. you got to pass the BindFT_Static version identifier to the compiler");
595   }
596 } else version(aliced) {
597   version = nanovg_default_no_font_aa;
598   version = nanovg_builtin_fontconfig_bindings;
599   version = nanovg_builtin_freetype_bindings;
600   version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings
601 } else {
602   version (neverHave_bindbc_opengl)
603     version = nanovg_bindbc_opengl_bindings;
604    else
605     version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings
606   version (neverHave_bindbc_freetype)
607     version = nanovg_bindbc_freetype_bindings;
608    else
609     version = nanovg_builtin_freetype_bindings;
610   version = nanovg_builtin_fontconfig_bindings;
611 }
612 
613 version(nanovg_disable_fontconfig) {
614   public enum NanoVegaHasFontConfig = false;
615 } else {
616   public enum NanoVegaHasFontConfig = true;
617   version(nanovg_builtin_fontconfig_bindings) {} else import iv.fontconfig;
618 }
619 
620 //version = nanovg_bench_flatten;
621 
622 /++
623 	Annotation to indicate the marked function is compatible with [arsd.script].
624 
625 
626 	Any function that takes a [Color] argument will be passed a string instead.
627 
628 	Scriptable Functions
629 	====================
630 
631 	$(UDA_USES)
632 
633 	$(ALWAYS_DOCUMENT)
634 +/
635 private enum scriptable = "arsd_jsvar_compatible";
636 
637 public:
638 alias NVG_PI = PI;
639 
640 enum NanoVegaHasArsdColor = (is(typeof((){ import arsd.color; })));
641 enum NanoVegaHasArsdImage = (is(typeof((){ import arsd.color; import arsd.image; })));
642 
643 static if (NanoVegaHasArsdColor) private import arsd.color;
644 static if (NanoVegaHasArsdImage) {
645   private import arsd.image;
646 } else {
647   void stbi_set_unpremultiply_on_load (int flag_true_if_should_unpremultiply) {}
648   void stbi_convert_iphone_png_to_rgb (int flag_true_if_should_convert) {}
649   ubyte* stbi_load (const(char)* filename, int* x, int* y, int* comp, int req_comp) { return null; }
650   ubyte* stbi_load_from_memory (const(void)* buffer, int len, int* x, int* y, int* comp, int req_comp) { return null; }
651   void stbi_image_free (void* retval_from_stbi_load) {}
652 }
653 
654 version(nanovg_default_no_font_aa) {
655   __gshared bool NVG_INVERT_FONT_AA = false;
656 } else {
657   __gshared bool NVG_INVERT_FONT_AA = true;
658 }
659 
660 
661 /// this is branchless for ints on x86, and even for longs on x86_64
662 public ubyte nvgClampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) {
663   static if (__VERSION__ > 2067) pragma(inline, true);
664   static if (T.sizeof == 2 || T.sizeof == 4) {
665     static if (__traits(isUnsigned, T)) {
666       return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24)));
667     } else {
668       n &= -cast(int)(n >= 0);
669       return cast(ubyte)(n|((255-cast(int)n)>>31));
670     }
671   } else static if (T.sizeof == 1) {
672     static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?");
673     return cast(ubyte)n;
674   } else static if (T.sizeof == 8) {
675     static if (__traits(isUnsigned, T)) {
676       return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56)));
677     } else {
678       n &= -cast(long)(n >= 0);
679       return cast(ubyte)(n|((255-cast(long)n)>>63));
680     }
681   } else {
682     static assert(false, "clampToByte: integer too big");
683   }
684 }
685 
686 
687 /// NanoVega RGBA color
688 /// Group: color_utils
689 public align(1) struct NVGColor {
690 align(1):
691 public:
692   float[4] rgba = 0; /// default color is transparent (a=1 is opaque)
693 
694 public:
695   @property string toString () const @safe { import std.string : format; return "NVGColor(%s,%s,%s,%s)".format(r, g, b, a); }
696 
697 public:
698   enum transparent = NVGColor(0.0f, 0.0f, 0.0f, 0.0f);
699   enum k8orange = NVGColor(1.0f, 0.5f, 0.0f, 1.0f);
700 
701   enum aliceblue = NVGColor(240, 248, 255);
702   enum antiquewhite = NVGColor(250, 235, 215);
703   enum aqua = NVGColor(0, 255, 255);
704   enum aquamarine = NVGColor(127, 255, 212);
705   enum azure = NVGColor(240, 255, 255);
706   enum beige = NVGColor(245, 245, 220);
707   enum bisque = NVGColor(255, 228, 196);
708   enum black = NVGColor(0, 0, 0); // basic color
709   enum blanchedalmond = NVGColor(255, 235, 205);
710   enum blue = NVGColor(0, 0, 255); // basic color
711   enum blueviolet = NVGColor(138, 43, 226);
712   enum brown = NVGColor(165, 42, 42);
713   enum burlywood = NVGColor(222, 184, 135);
714   enum cadetblue = NVGColor(95, 158, 160);
715   enum chartreuse = NVGColor(127, 255, 0);
716   enum chocolate = NVGColor(210, 105, 30);
717   enum coral = NVGColor(255, 127, 80);
718   enum cornflowerblue = NVGColor(100, 149, 237);
719   enum cornsilk = NVGColor(255, 248, 220);
720   enum crimson = NVGColor(220, 20, 60);
721   enum cyan = NVGColor(0, 255, 255); // basic color
722   enum darkblue = NVGColor(0, 0, 139);
723   enum darkcyan = NVGColor(0, 139, 139);
724   enum darkgoldenrod = NVGColor(184, 134, 11);
725   enum darkgray = NVGColor(169, 169, 169);
726   enum darkgreen = NVGColor(0, 100, 0);
727   enum darkgrey = NVGColor(169, 169, 169);
728   enum darkkhaki = NVGColor(189, 183, 107);
729   enum darkmagenta = NVGColor(139, 0, 139);
730   enum darkolivegreen = NVGColor(85, 107, 47);
731   enum darkorange = NVGColor(255, 140, 0);
732   enum darkorchid = NVGColor(153, 50, 204);
733   enum darkred = NVGColor(139, 0, 0);
734   enum darksalmon = NVGColor(233, 150, 122);
735   enum darkseagreen = NVGColor(143, 188, 143);
736   enum darkslateblue = NVGColor(72, 61, 139);
737   enum darkslategray = NVGColor(47, 79, 79);
738   enum darkslategrey = NVGColor(47, 79, 79);
739   enum darkturquoise = NVGColor(0, 206, 209);
740   enum darkviolet = NVGColor(148, 0, 211);
741   enum deeppink = NVGColor(255, 20, 147);
742   enum deepskyblue = NVGColor(0, 191, 255);
743   enum dimgray = NVGColor(105, 105, 105);
744   enum dimgrey = NVGColor(105, 105, 105);
745   enum dodgerblue = NVGColor(30, 144, 255);
746   enum firebrick = NVGColor(178, 34, 34);
747   enum floralwhite = NVGColor(255, 250, 240);
748   enum forestgreen = NVGColor(34, 139, 34);
749   enum fuchsia = NVGColor(255, 0, 255);
750   enum gainsboro = NVGColor(220, 220, 220);
751   enum ghostwhite = NVGColor(248, 248, 255);
752   enum gold = NVGColor(255, 215, 0);
753   enum goldenrod = NVGColor(218, 165, 32);
754   enum gray = NVGColor(128, 128, 128); // basic color
755   enum green = NVGColor(0, 128, 0); // basic color
756   enum greenyellow = NVGColor(173, 255, 47);
757   enum grey = NVGColor(128, 128, 128); // basic color
758   enum honeydew = NVGColor(240, 255, 240);
759   enum hotpink = NVGColor(255, 105, 180);
760   enum indianred = NVGColor(205, 92, 92);
761   enum indigo = NVGColor(75, 0, 130);
762   enum ivory = NVGColor(255, 255, 240);
763   enum khaki = NVGColor(240, 230, 140);
764   enum lavender = NVGColor(230, 230, 250);
765   enum lavenderblush = NVGColor(255, 240, 245);
766   enum lawngreen = NVGColor(124, 252, 0);
767   enum lemonchiffon = NVGColor(255, 250, 205);
768   enum lightblue = NVGColor(173, 216, 230);
769   enum lightcoral = NVGColor(240, 128, 128);
770   enum lightcyan = NVGColor(224, 255, 255);
771   enum lightgoldenrodyellow = NVGColor(250, 250, 210);
772   enum lightgray = NVGColor(211, 211, 211);
773   enum lightgreen = NVGColor(144, 238, 144);
774   enum lightgrey = NVGColor(211, 211, 211);
775   enum lightpink = NVGColor(255, 182, 193);
776   enum lightsalmon = NVGColor(255, 160, 122);
777   enum lightseagreen = NVGColor(32, 178, 170);
778   enum lightskyblue = NVGColor(135, 206, 250);
779   enum lightslategray = NVGColor(119, 136, 153);
780   enum lightslategrey = NVGColor(119, 136, 153);
781   enum lightsteelblue = NVGColor(176, 196, 222);
782   enum lightyellow = NVGColor(255, 255, 224);
783   enum lime = NVGColor(0, 255, 0);
784   enum limegreen = NVGColor(50, 205, 50);
785   enum linen = NVGColor(250, 240, 230);
786   enum magenta = NVGColor(255, 0, 255); // basic color
787   enum maroon = NVGColor(128, 0, 0);
788   enum mediumaquamarine = NVGColor(102, 205, 170);
789   enum mediumblue = NVGColor(0, 0, 205);
790   enum mediumorchid = NVGColor(186, 85, 211);
791   enum mediumpurple = NVGColor(147, 112, 219);
792   enum mediumseagreen = NVGColor(60, 179, 113);
793   enum mediumslateblue = NVGColor(123, 104, 238);
794   enum mediumspringgreen = NVGColor(0, 250, 154);
795   enum mediumturquoise = NVGColor(72, 209, 204);
796   enum mediumvioletred = NVGColor(199, 21, 133);
797   enum midnightblue = NVGColor(25, 25, 112);
798   enum mintcream = NVGColor(245, 255, 250);
799   enum mistyrose = NVGColor(255, 228, 225);
800   enum moccasin = NVGColor(255, 228, 181);
801   enum navajowhite = NVGColor(255, 222, 173);
802   enum navy = NVGColor(0, 0, 128);
803   enum oldlace = NVGColor(253, 245, 230);
804   enum olive = NVGColor(128, 128, 0);
805   enum olivedrab = NVGColor(107, 142, 35);
806   enum orange = NVGColor(255, 165, 0);
807   enum orangered = NVGColor(255, 69, 0);
808   enum orchid = NVGColor(218, 112, 214);
809   enum palegoldenrod = NVGColor(238, 232, 170);
810   enum palegreen = NVGColor(152, 251, 152);
811   enum paleturquoise = NVGColor(175, 238, 238);
812   enum palevioletred = NVGColor(219, 112, 147);
813   enum papayawhip = NVGColor(255, 239, 213);
814   enum peachpuff = NVGColor(255, 218, 185);
815   enum peru = NVGColor(205, 133, 63);
816   enum pink = NVGColor(255, 192, 203);
817   enum plum = NVGColor(221, 160, 221);
818   enum powderblue = NVGColor(176, 224, 230);
819   enum purple = NVGColor(128, 0, 128);
820   enum red = NVGColor(255, 0, 0); // basic color
821   enum rosybrown = NVGColor(188, 143, 143);
822   enum royalblue = NVGColor(65, 105, 225);
823   enum saddlebrown = NVGColor(139, 69, 19);
824   enum salmon = NVGColor(250, 128, 114);
825   enum sandybrown = NVGColor(244, 164, 96);
826   enum seagreen = NVGColor(46, 139, 87);
827   enum seashell = NVGColor(255, 245, 238);
828   enum sienna = NVGColor(160, 82, 45);
829   enum silver = NVGColor(192, 192, 192);
830   enum skyblue = NVGColor(135, 206, 235);
831   enum slateblue = NVGColor(106, 90, 205);
832   enum slategray = NVGColor(112, 128, 144);
833   enum slategrey = NVGColor(112, 128, 144);
834   enum snow = NVGColor(255, 250, 250);
835   enum springgreen = NVGColor(0, 255, 127);
836   enum steelblue = NVGColor(70, 130, 180);
837   enum tan = NVGColor(210, 180, 140);
838   enum teal = NVGColor(0, 128, 128);
839   enum thistle = NVGColor(216, 191, 216);
840   enum tomato = NVGColor(255, 99, 71);
841   enum turquoise = NVGColor(64, 224, 208);
842   enum violet = NVGColor(238, 130, 238);
843   enum wheat = NVGColor(245, 222, 179);
844   enum white = NVGColor(255, 255, 255); // basic color
845   enum whitesmoke = NVGColor(245, 245, 245);
846   enum yellow = NVGColor(255, 255, 0); // basic color
847   enum yellowgreen = NVGColor(154, 205, 50);
848 
849 nothrow @safe @nogc:
850 public:
851   ///
852   this (ubyte ar, ubyte ag, ubyte ab, ubyte aa=255) pure {
853     pragma(inline, true);
854     r = ar/255.0f;
855     g = ag/255.0f;
856     b = ab/255.0f;
857     a = aa/255.0f;
858   }
859 
860   ///
861   this (float ar, float ag, float ab, float aa=1.0f) pure {
862     pragma(inline, true);
863     r = ar;
864     g = ag;
865     b = ab;
866     a = aa;
867   }
868 
869   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
870   this (uint c) pure {
871     pragma(inline, true);
872     r = (c&0xff)/255.0f;
873     g = ((c>>8)&0xff)/255.0f;
874     b = ((c>>16)&0xff)/255.0f;
875     a = ((c>>24)&0xff)/255.0f;
876   }
877 
878   /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb"
879   this (const(char)[] srgb) {
880     static int c2d (char ch) pure nothrow @safe @nogc {
881       pragma(inline, true);
882       return
883         ch >= '0' && ch <= '9' ? ch-'0' :
884         ch >= 'A' && ch <= 'F' ? ch-'A'+10 :
885         ch >= 'a' && ch <= 'f' ? ch-'a'+10 :
886         -1;
887     }
888     int[8] digs;
889     int dc = -1;
890     foreach (immutable char ch; srgb) {
891       if (ch <= ' ') continue;
892       if (ch == '#') {
893         if (dc != -1) { dc = -1; break; }
894         dc = 0;
895       } else {
896         if (dc >= digs.length) { dc = -1; break; }
897         if ((digs[dc++] = c2d(ch)) < 0) { dc = -1; break; }
898       }
899     }
900     switch (dc) {
901       case 3: // rgb
902         a = 1.0f;
903         r = digs[0]/15.0f;
904         g = digs[1]/15.0f;
905         b = digs[2]/15.0f;
906         break;
907       case 4: // argb
908         a = digs[0]/15.0f;
909         r = digs[1]/15.0f;
910         g = digs[2]/15.0f;
911         b = digs[3]/15.0f;
912         break;
913       case 6: // rrggbb
914         a = 1.0f;
915         r = (digs[0]*16+digs[1])/255.0f;
916         g = (digs[2]*16+digs[3])/255.0f;
917         b = (digs[4]*16+digs[5])/255.0f;
918         break;
919       case 8: // aarrggbb
920         a = (digs[0]*16+digs[1])/255.0f;
921         r = (digs[2]*16+digs[3])/255.0f;
922         g = (digs[4]*16+digs[5])/255.0f;
923         b = (digs[6]*16+digs[7])/255.0f;
924         break;
925       default:
926         break;
927     }
928   }
929 
930   /// Is this color completely opaque?
931   @property bool isOpaque () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] >= 1.0f); }
932   /// Is this color completely transparent?
933   @property bool isTransparent () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] <= 0.0f); }
934 
935   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
936   @property uint asUint () const pure {
937     pragma(inline, true);
938     return
939       cast(uint)(r*255)|
940       (cast(uint)(g*255)<<8)|
941       (cast(uint)(b*255)<<16)|
942       (cast(uint)(a*255)<<24);
943   }
944 
945   alias asUintABGR = asUint; /// Ditto.
946 
947   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
948   static NVGColor fromUint (uint c) pure { pragma(inline, true); return NVGColor(c); }
949 
950   alias fromUintABGR = fromUint; /// Ditto.
951 
952   /// AARRGGBB
953   @property uint asUintARGB () const pure {
954     pragma(inline, true);
955     return
956       cast(uint)(b*255)|
957       (cast(uint)(g*255)<<8)|
958       (cast(uint)(r*255)<<16)|
959       (cast(uint)(a*255)<<24);
960   }
961 
962   /// AARRGGBB
963   static NVGColor fromUintARGB (uint c) pure { pragma(inline, true); return NVGColor((c>>16)&0xff, (c>>8)&0xff, c&0xff, (c>>24)&0xff); }
964 
965   @property ref inout(float) r () inout pure @trusted { pragma(inline, true); return rgba.ptr[0]; } ///
966   @property ref inout(float) g () inout pure @trusted { pragma(inline, true); return rgba.ptr[1]; } ///
967   @property ref inout(float) b () inout pure @trusted { pragma(inline, true); return rgba.ptr[2]; } ///
968   @property ref inout(float) a () inout pure @trusted { pragma(inline, true); return rgba.ptr[3]; } ///
969 
970   ref NVGColor applyTint() (const scope auto ref NVGColor tint) nothrow @trusted @nogc {
971     if (tint.a == 0) return this;
972     foreach (immutable idx, ref float v; rgba[0..4]) {
973       v = nvg__clamp(v*tint.rgba.ptr[idx], 0.0f, 1.0f);
974     }
975     return this;
976   }
977 
978   NVGHSL asHSL() (bool useWeightedLightness=false) const { pragma(inline, true); return NVGHSL.fromColor(this, useWeightedLightness); } ///
979   static fromHSL() (const scope auto ref NVGHSL hsl) { pragma(inline, true); return hsl.asColor; } ///
980 
981   static if (NanoVegaHasArsdColor) {
982     Color toArsd () const { pragma(inline, true); return Color(cast(int)(r*255), cast(int)(g*255), cast(int)(b*255), cast(int)(a*255)); } ///
983     static NVGColor fromArsd (in Color c) { pragma(inline, true); return NVGColor(c.r, c.g, c.b, c.a); } ///
984     ///
985     this (in Color c) {
986       version(aliced) pragma(inline, true);
987       r = c.r/255.0f;
988       g = c.g/255.0f;
989       b = c.b/255.0f;
990       a = c.a/255.0f;
991     }
992   }
993 }
994 
995 
996 /// NanoVega A-HSL color
997 /// Group: color_utils
998 public align(1) struct NVGHSL {
999 align(1):
1000   float h=0, s=0, l=1, a=1; ///
1001 
1002   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)); }
1003 
1004 nothrow @safe @nogc:
1005 public:
1006   ///
1007   this (float ah, float as, float al, float aa=1) pure { pragma(inline, true); h = ah; s = as; l = al; a = aa; }
1008 
1009   NVGColor asColor () const { pragma(inline, true); return nvgHSLA(h, s, l, a); } ///
1010 
1011   // taken from Adam's arsd.color
1012   /** Converts an RGB color into an HSL triplet.
1013    * [useWeightedLightness] will try to get a better value for luminosity for the human eye,
1014    * which is more sensitive to green than red and more to red than blue.
1015    * If it is false, it just does average of the rgb. */
1016   static NVGHSL fromColor() (const scope auto ref NVGColor c, bool useWeightedLightness=false) pure {
1017     NVGHSL res;
1018     res.a = c.a;
1019     float r1 = c.r;
1020     float g1 = c.g;
1021     float b1 = c.b;
1022 
1023     float maxColor = r1;
1024     if (g1 > maxColor) maxColor = g1;
1025     if (b1 > maxColor) maxColor = b1;
1026     float minColor = r1;
1027     if (g1 < minColor) minColor = g1;
1028     if (b1 < minColor) minColor = b1;
1029 
1030     res.l = (maxColor+minColor)/2;
1031     if (useWeightedLightness) {
1032       // the colors don't affect the eye equally
1033       // this is a little more accurate than plain HSL numbers
1034       res.l = 0.2126*r1+0.7152*g1+0.0722*b1;
1035     }
1036     if (maxColor != minColor) {
1037       if (res.l < 0.5) {
1038         res.s = (maxColor-minColor)/(maxColor+minColor);
1039       } else {
1040         res.s = (maxColor-minColor)/(2.0-maxColor-minColor);
1041       }
1042       if (r1 == maxColor) {
1043         res.h = (g1-b1)/(maxColor-minColor);
1044       } else if(g1 == maxColor) {
1045         res.h = 2.0+(b1-r1)/(maxColor-minColor);
1046       } else {
1047         res.h = 4.0+(r1-g1)/(maxColor-minColor);
1048       }
1049     }
1050 
1051     res.h = res.h*60;
1052     if (res.h < 0) res.h += 360;
1053     res.h /= 360;
1054 
1055     return res;
1056   }
1057 }
1058 
1059 
1060 //version = nanovega_debug_image_manager;
1061 //version = nanovega_debug_image_manager_rc;
1062 
1063 /** NanoVega image handle.
1064  *
1065  * This is refcounted struct, so you don't need to do anything special to free it once it is allocated.
1066  *
1067  * Group: images
1068  */
1069 struct NVGImage {
1070 	enum isOpaqueStruct = true;
1071 private:
1072   NVGContext ctx;
1073   int id; // backend image id
1074 
1075 public:
1076   ///
1077   this() (const scope auto ref NVGImage src) nothrow @trusted @nogc {
1078     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); }
1079     if (src.id > 0 && src.ctx !is null) {
1080       ctx = cast(NVGContext)src.ctx;
1081       id = src.id;
1082       ctx.nvg__imageIncRef(id);
1083     }
1084   }
1085 
1086   ///
1087   ~this () nothrow @trusted @nogc { version(aliced) pragma(inline, true); clear(); }
1088 
1089   ///
1090   this (this) nothrow @trusted @nogc {
1091     version(aliced) pragma(inline, true);
1092     if (id > 0 && ctx !is null) {
1093       version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p postblit (imgid=%d)\n", &this, id); }
1094       ctx.nvg__imageIncRef(id);
1095     }
1096   }
1097 
1098   ///
1099   void opAssign() (const scope auto ref NVGImage src) nothrow @trusted @nogc {
1100     if (src.id <= 0 || src.ctx is null) {
1101       clear();
1102     } else {
1103       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); }
1104       if (src.id > 0 && src.ctx !is null) (cast(NVGContext)src.ctx).nvg__imageIncRef(src.id);
1105       if (id > 0 && ctx !is null) ctx.nvg__imageDecRef(id);
1106       ctx = cast(NVGContext)src.ctx;
1107       id = src.id;
1108     }
1109   }
1110 
1111   /// Free this image.
1112   void clear () nothrow @trusted @nogc {
1113     if (id > 0 && ctx !is null) {
1114       version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p cleared (imgid=%d)\n", &this, id); }
1115       ctx.nvg__imageDecRef(id);
1116     }
1117     id = 0;
1118     ctx = null;
1119   }
1120 
1121   /// Is this image valid?
1122   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (id > 0 && ctx.valid); }
1123 
1124   /// Is the given image valid and comes from the same context?
1125   @property bool isSameContext (const(NVGContext) actx) const pure nothrow @safe @nogc { pragma(inline, true); return (actx !is null && ctx is actx); }
1126 
1127   /// Returns image width, or zero for invalid image.
1128   int width () const nothrow @trusted @nogc {
1129     int w = 0;
1130     if (valid) {
1131       int h = void;
1132       ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h);
1133     }
1134     return w;
1135   }
1136 
1137   /// Returns image height, or zero for invalid image.
1138   int height () const nothrow @trusted @nogc {
1139     int h = 0;
1140     if (valid) {
1141       int w = void;
1142       ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h);
1143     }
1144     return h;
1145   }
1146 }
1147 
1148 
1149 /// Paint parameters for various fills. Don't change anything here!
1150 /// Group: render_styles
1151 public struct NVGPaint {
1152   enum isOpaqueStruct = true;
1153 
1154   NVGMatrix xform;
1155   float[2] extent = 0.0f;
1156   float radius = 0.0f;
1157   float feather = 0.0f;
1158   NVGColor innerColor; /// this can be used to modulate images (fill/font)
1159   NVGColor middleColor;
1160   NVGColor outerColor;
1161   float midp = -1; // middle stop for 3-color gradient
1162   NVGImage image;
1163   bool simpleColor; /// if `true`, only innerColor is used, and this is solid-color paint
1164 
1165   this() (const scope auto ref NVGPaint p) nothrow @trusted @nogc {
1166     xform = p.xform;
1167     extent[] = p.extent[];
1168     radius = p.radius;
1169     feather = p.feather;
1170     innerColor = p.innerColor;
1171     middleColor = p.middleColor;
1172     midp = p.midp;
1173     outerColor = p.outerColor;
1174     image = p.image;
1175     simpleColor = p.simpleColor;
1176   }
1177 
1178   void opAssign() (const scope auto ref NVGPaint p) nothrow @trusted @nogc {
1179     xform = p.xform;
1180     extent[] = p.extent[];
1181     radius = p.radius;
1182     feather = p.feather;
1183     innerColor = p.innerColor;
1184     middleColor = p.middleColor;
1185     midp = p.midp;
1186     outerColor = p.outerColor;
1187     image = p.image;
1188     simpleColor = p.simpleColor;
1189   }
1190 
1191   void clear () nothrow @trusted @nogc {
1192     version(aliced) pragma(inline, true);
1193     import core.stdc.string : memset;
1194     image.clear();
1195     memset(&this, 0, this.sizeof);
1196     simpleColor = true;
1197   }
1198 }
1199 
1200 /// Path winding.
1201 /// Group: paths
1202 public enum NVGWinding {
1203   CCW = 1, /// Winding for solid shapes
1204   CW = 2,  /// Winding for holes
1205 }
1206 
1207 /// Path solidity.
1208 /// Group: paths
1209 public enum NVGSolidity {
1210   Solid = 1, /// Solid shape (CCW winding).
1211   Hole = 2, /// Hole (CW winding).
1212 }
1213 
1214 /// Line cap style.
1215 /// Group: render_styles
1216 public enum NVGLineCap {
1217   Butt, ///
1218   Round, ///
1219   Square, ///
1220   Bevel, ///
1221   Miter, ///
1222 }
1223 
1224 /// Text align.
1225 /// Group: text_api
1226 public align(1) struct NVGTextAlign {
1227 align(1):
1228   /// Horizontal align.
1229   enum H : ubyte {
1230     Left   = 0, /// Default, align text horizontally to left.
1231     Center = 1, /// Align text horizontally to center.
1232     Right  = 2, /// Align text horizontally to right.
1233   }
1234 
1235   /// Vertical align.
1236   enum V : ubyte {
1237     Baseline = 0, /// Default, align text vertically to baseline.
1238     Top      = 1, /// Align text vertically to top.
1239     Middle   = 2, /// Align text vertically to middle.
1240     Bottom   = 3, /// Align text vertically to bottom.
1241   }
1242 
1243 pure nothrow @safe @nogc:
1244 public:
1245   this (H h) { pragma(inline, true); value = h; } ///
1246   this (V v) { pragma(inline, true); value = cast(ubyte)(v<<4); } ///
1247   this (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1248   this (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1249   void reset () { pragma(inline, true); value = 0; } ///
1250   void reset (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1251   void reset (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1252 @property:
1253   bool left () const { pragma(inline, true); return ((value&0x0f) == H.Left); } ///
1254   void left (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Left : 0)); } ///
1255   bool center () const { pragma(inline, true); return ((value&0x0f) == H.Center); } ///
1256   void center (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Center : 0)); } ///
1257   bool right () const { pragma(inline, true); return ((value&0x0f) == H.Right); } ///
1258   void right (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Right : 0)); } ///
1259   //
1260   bool baseline () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Baseline); } ///
1261   void baseline (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Baseline<<4 : 0)); } ///
1262   bool top () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Top); } ///
1263   void top (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Top<<4 : 0)); } ///
1264   bool middle () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Middle); } ///
1265   void middle (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Middle<<4 : 0)); } ///
1266   bool bottom () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Bottom); } ///
1267   void bottom (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Bottom<<4 : 0)); } ///
1268   //
1269   H horizontal () const { pragma(inline, true); return cast(H)(value&0x0f); } ///
1270   void horizontal (H v) { pragma(inline, true); value = (value&0xf0)|v; } ///
1271   //
1272   V vertical () const { pragma(inline, true); return cast(V)((value>>4)&0x0f); } ///
1273   void vertical (V v) { pragma(inline, true); value = (value&0x0f)|cast(ubyte)(v<<4); } ///
1274   //
1275 private:
1276   ubyte value = 0; // low nibble: horizontal; high nibble: vertical
1277 }
1278 
1279 /// Blending type.
1280 /// Group: composite_operation
1281 public enum NVGBlendFactor {
1282   Zero = 1<<0, ///
1283   One = 1<<1, ///
1284   SrcColor = 1<<2, ///
1285   OneMinusSrcColor = 1<<3, ///
1286   DstColor = 1<<4, ///
1287   OneMinusDstColor = 1<<5, ///
1288   SrcAlpha = 1<<6, ///
1289   OneMinusSrcAlpha = 1<<7, ///
1290   DstAlpha = 1<<8, ///
1291   OneMinusDstAlpha = 1<<9, ///
1292   SrcAlphaSaturate = 1<<10, ///
1293 }
1294 
1295 /// Composite operation (HTML5-alike).
1296 /// Group: composite_operation
1297 public enum NVGCompositeOperation {
1298   SourceOver, ///
1299   SourceIn, ///
1300   SourceOut, ///
1301   SourceAtop, ///
1302   DestinationOver, ///
1303   DestinationIn, ///
1304   DestinationOut, ///
1305   DestinationAtop, ///
1306   Lighter, ///
1307   Copy, ///
1308   Xor, ///
1309 }
1310 
1311 /// Composite operation state.
1312 /// Group: composite_operation
1313 public struct NVGCompositeOperationState {
1314   bool simple; /// `true`: use `glBlendFunc()` instead of `glBlendFuncSeparate()`
1315   NVGBlendFactor srcRGB; ///
1316   NVGBlendFactor dstRGB; ///
1317   NVGBlendFactor srcAlpha; ///
1318   NVGBlendFactor dstAlpha; ///
1319 }
1320 
1321 /// Mask combining more
1322 /// Group: clipping
1323 public enum NVGClipMode {
1324   None, /// normal rendering (i.e. render path instead of modifying clip region)
1325   Union, /// old mask will be masked with the current one; this is the default mode for [clip]
1326   Or, /// new mask will be added to the current one (logical `OR` operation);
1327   Xor, /// new mask will be logically `XOR`ed with the current one
1328   Sub, /// "subtract" current path from mask
1329   Replace, /// replace current mask
1330   Add = Or, /// Synonym
1331 }
1332 
1333 /// Glyph position info.
1334 /// Group: text_api
1335 public struct NVGGlyphPosition {
1336   usize strpos;     /// Position of the glyph in the input string.
1337   float x;          /// The x-coordinate of the logical glyph position.
1338   float minx, maxx; /// The bounds of the glyph shape.
1339 }
1340 
1341 /// Text row storage.
1342 /// Group: text_api
1343 public struct NVGTextRow(CT) if (isAnyCharType!CT) {
1344   alias CharType = CT;
1345   const(CT)[] s;
1346   int start;        /// Index in the input text where the row starts.
1347   int end;          /// Index in the input text where the row ends (one past the last character).
1348   float width;      /// Logical width of the row.
1349   float minx, maxx; /// Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending.
1350   /// Get rest of the string.
1351   @property const(CT)[] rest () const pure nothrow @trusted @nogc { pragma(inline, true); return (end <= s.length ? s[end..$] : null); }
1352   /// Get current row.
1353   @property const(CT)[] row () const pure nothrow @trusted @nogc { pragma(inline, true); return s[start..end]; }
1354   @property const(CT)[] string () const pure nothrow @trusted @nogc { pragma(inline, true); return s; }
1355   @property void string(CT) (const(CT)[] v) pure nothrow @trusted @nogc { pragma(inline, true); s = v; }
1356 }
1357 
1358 /// Image creation flags.
1359 /// Group: images
1360 public enum NVGImageFlag : uint {
1361   None            =    0, /// Nothing special.
1362   GenerateMipmaps = 1<<0, /// Generate mipmaps during creation of the image.
1363   RepeatX         = 1<<1, /// Repeat image in X direction.
1364   RepeatY         = 1<<2, /// Repeat image in Y direction.
1365   FlipY           = 1<<3, /// Flips (inverses) image in Y direction when rendered.
1366   Premultiplied   = 1<<4, /// Image data has premultiplied alpha.
1367   ClampToBorderX  = 1<<5, /// Clamp image to border (instead of clamping to edge by default)
1368   ClampToBorderY  = 1<<6, /// Clamp image to border (instead of clamping to edge by default)
1369   NoFiltering     = 1<<8, /// use GL_NEAREST instead of GL_LINEAR. Only affects upscaling if GenerateMipmaps is active.
1370   Nearest = NoFiltering,  /// compatibility with original NanoVG
1371   NoDelete        = 1<<16,/// Do not delete GL texture handle.
1372 }
1373 
1374 alias NVGImageFlags = NVGImageFlag; /// Backwards compatibility for [NVGImageFlag].
1375 
1376 
1377 // ////////////////////////////////////////////////////////////////////////// //
1378 private:
1379 
1380 static T* xdup(T) (const(T)* ptr, int count) nothrow @trusted @nogc {
1381   import core.stdc.stdlib : malloc;
1382   import core.stdc.string : memcpy;
1383   if (count == 0) return null;
1384   T* res = cast(T*)malloc(T.sizeof*count);
1385   if (res is null) assert(0, "NanoVega: out of memory");
1386   memcpy(res, ptr, T.sizeof*count);
1387   return res;
1388 }
1389 
1390 // Internal Render API
1391 enum NVGtexture {
1392   Alpha = 0x01,
1393   RGBA  = 0x02,
1394 }
1395 
1396 struct NVGscissor {
1397   NVGMatrix xform;
1398   float[2] extent = -1.0f;
1399 }
1400 
1401 /// General NanoVega vertex struct. Contains geometry coordinates, and (sometimes unused) texture coordinates.
1402 public struct NVGVertex {
1403   float x, y, u, v;
1404 }
1405 
1406 struct NVGpath {
1407   int first;
1408   int count;
1409   bool closed;
1410   int nbevel;
1411   NVGVertex* fill;
1412   int nfill;
1413   NVGVertex* stroke;
1414   int nstroke;
1415   NVGWinding mWinding;
1416   bool convex;
1417   bool cloned;
1418 
1419   @disable this (this); // no copies
1420   void opAssign() (const scope auto ref NVGpath a) { static assert(0, "no copies!"); }
1421 
1422   void clear () nothrow @trusted @nogc {
1423     import core.stdc.stdlib : free;
1424     import core.stdc.string : memset;
1425     if (cloned) {
1426       if (stroke !is null && stroke !is fill) free(stroke);
1427       if (fill !is null) free(fill);
1428     }
1429     memset(&this, 0, this.sizeof);
1430   }
1431 
1432   // won't clear current path
1433   void copyFrom (const NVGpath* src) nothrow @trusted @nogc {
1434     import core.stdc.string : memcpy;
1435     assert(src !is null);
1436     memcpy(&this, src, NVGpath.sizeof);
1437     this.fill = xdup(src.fill, src.nfill);
1438     if (src.stroke is src.fill) {
1439       this.stroke = this.fill;
1440     } else {
1441       this.stroke = xdup(src.stroke, src.nstroke);
1442     }
1443     this.cloned = true;
1444   }
1445 
1446   public @property const(NVGVertex)[] fillVertices () const pure nothrow @trusted @nogc {
1447     pragma(inline, true);
1448     return (nfill > 0 ? fill[0..nfill] : null);
1449   }
1450 
1451   public @property const(NVGVertex)[] strokeVertices () const pure nothrow @trusted @nogc {
1452     pragma(inline, true);
1453     return (nstroke > 0 ? stroke[0..nstroke] : null);
1454   }
1455 
1456   public @property NVGWinding winding () const pure nothrow @trusted @nogc { pragma(inline, true); return mWinding; }
1457   public @property bool complex () const pure nothrow @trusted @nogc { pragma(inline, true); return !convex; }
1458 }
1459 
1460 
1461 struct NVGparams {
1462   void* userPtr;
1463   bool edgeAntiAlias;
1464   bool fontAA;
1465   bool function (void* uptr) nothrow @trusted @nogc renderCreate;
1466   int function (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc renderCreateTexture;
1467   bool function (void* uptr, int image) nothrow @trusted @nogc renderTextureIncRef;
1468   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
1469   bool function (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc renderUpdateTexture;
1470   bool function (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc renderGetTextureSize;
1471   void function (void* uptr, int width, int height) nothrow @trusted @nogc renderViewport; // called in [beginFrame]
1472   void function (void* uptr) nothrow @trusted @nogc renderCancel;
1473   void function (void* uptr) nothrow @trusted @nogc renderFlush;
1474   void function (void* uptr) nothrow @trusted @nogc renderPushClip; // backend should support stack of at least [NVG_MAX_STATES] elements
1475   void function (void* uptr) nothrow @trusted @nogc renderPopClip; // backend should support stack of at least [NVG_MAX_STATES] elements
1476   void function (void* uptr) nothrow @trusted @nogc renderResetClip; // reset current clip region to `non-clipped`
1477   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;
1478   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;
1479   void function (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc renderTriangles;
1480   void function (void* uptr, const scope ref NVGMatrix mat) nothrow @trusted @nogc renderSetAffine;
1481   void function (void* uptr) nothrow @trusted @nogc renderDelete;
1482 }
1483 
1484 // ////////////////////////////////////////////////////////////////////////// //
1485 private:
1486 
1487 enum NVG_INIT_FONTIMAGE_SIZE = 512;
1488 enum NVG_MAX_FONTIMAGE_SIZE  = 2048;
1489 enum NVG_MAX_FONTIMAGES      = 4;
1490 
1491 enum NVG_INIT_COMMANDS_SIZE = 256;
1492 enum NVG_INIT_POINTS_SIZE   = 128;
1493 enum NVG_INIT_PATHS_SIZE    = 16;
1494 enum NVG_INIT_VERTS_SIZE    = 256;
1495 enum NVG_MAX_STATES         = 32;
1496 
1497 public enum NVG_KAPPA90 = 0.5522847493f; /// Length proportional to radius of a cubic bezier handle for 90deg arcs.
1498 enum NVG_MIN_FEATHER = 0.001f; // it should be greater than zero, 'cause it is used in shader for divisions
1499 
1500 enum Command {
1501   MoveTo = 0,
1502   LineTo = 1,
1503   BezierTo = 2,
1504   Close = 3,
1505   Winding = 4,
1506 }
1507 
1508 enum PointFlag : int {
1509   Corner = 0x01,
1510   Left = 0x02,
1511   Bevel = 0x04,
1512   InnerBevelPR = 0x08,
1513 }
1514 
1515 struct NVGstate {
1516   NVGCompositeOperationState compositeOperation;
1517   bool shapeAntiAlias = true;
1518   NVGPaint fill;
1519   NVGPaint stroke;
1520   float strokeWidth = 1.0f;
1521   float miterLimit = 10.0f;
1522   NVGLineCap lineJoin = NVGLineCap.Miter;
1523   NVGLineCap lineCap = NVGLineCap.Butt;
1524   float alpha = 1.0f;
1525   NVGMatrix xform;
1526   NVGscissor scissor;
1527   float fontSize = 16.0f;
1528   float letterSpacing = 0.0f;
1529   float lineHeight = 1.0f;
1530   float fontBlur = 0.0f;
1531   NVGTextAlign textAlign;
1532   int fontId = 0;
1533   bool evenOddMode = false; // use even-odd filling rule (required for some svgs); otherwise use non-zero fill
1534   // dashing
1535   enum MaxDashes = 32; // max 16 dashes
1536   float[MaxDashes] dashes;
1537   uint dashCount = 0;
1538   uint lastFlattenDashCount = 0;
1539   float dashStart = 0;
1540   float totalDashLen;
1541   bool firstDashIsGap = false;
1542   // dasher state for flattener
1543   bool dasherActive = false;
1544 
1545   void clearPaint () nothrow @trusted @nogc {
1546     fill.clear();
1547     stroke.clear();
1548   }
1549 }
1550 
1551 struct NVGpoint {
1552   float x, y;
1553   float dx, dy;
1554   float len;
1555   float dmx, dmy;
1556   ubyte flags;
1557 }
1558 
1559 struct NVGpathCache {
1560   NVGpoint* points;
1561   int npoints;
1562   int cpoints;
1563   NVGpath* paths;
1564   int npaths;
1565   int cpaths;
1566   NVGVertex* verts;
1567   int nverts;
1568   int cverts;
1569   float[4] bounds;
1570   // this is required for saved paths
1571   bool strokeReady;
1572   bool fillReady;
1573   float strokeAlphaMul;
1574   float strokeWidth;
1575   float fringeWidth;
1576   bool evenOddMode;
1577   NVGClipMode clipmode;
1578   // non-saved path will not have this
1579   float* commands;
1580   int ncommands;
1581 
1582   @disable this (this); // no copies
1583   void opAssign() (const scope auto ref NVGpathCache a) { static assert(0, "no copies!"); }
1584 
1585   // won't clear current path
1586   void copyFrom (const NVGpathCache* src) nothrow @trusted @nogc {
1587     import core.stdc.stdlib : malloc;
1588     import core.stdc.string : memcpy, memset;
1589     assert(src !is null);
1590     memcpy(&this, src, NVGpathCache.sizeof);
1591     this.points = xdup(src.points, src.npoints);
1592     this.cpoints = src.npoints;
1593     this.verts = xdup(src.verts, src.nverts);
1594     this.cverts = src.nverts;
1595     this.commands = xdup(src.commands, src.ncommands);
1596     if (src.npaths > 0) {
1597       this.paths = cast(NVGpath*)malloc(src.npaths*NVGpath.sizeof);
1598       memset(this.paths, 0, npaths*NVGpath.sizeof);
1599       foreach (immutable pidx; 0..npaths) this.paths[pidx].copyFrom(&src.paths[pidx]);
1600       this.cpaths = src.npaths;
1601     } else {
1602       this.npaths = this.cpaths = 0;
1603     }
1604   }
1605 
1606   void clear () nothrow @trusted @nogc {
1607     import core.stdc.stdlib : free;
1608     import core.stdc.string : memset;
1609     if (paths !is null) {
1610       foreach (ref p; paths[0..npaths]) p.clear();
1611       free(paths);
1612     }
1613     if (points !is null) free(points);
1614     if (verts !is null) free(verts);
1615     if (commands !is null) free(commands);
1616     memset(&this, 0, this.sizeof);
1617   }
1618 }
1619 
1620 /// Pointer to opaque NanoVega context structure.
1621 /// Group: context_management
1622 public alias NVGContext = NVGcontextinternal*;
1623 
1624 /// FontStash context
1625 /// Group: font_stash
1626 public alias FONSContext = FONScontextInternal*;
1627 
1628 /// Returns FontStash context of the given NanoVega context.
1629 /// Group: font_stash
1630 public FONSContext fonsContext (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.fs : null); }
1631 
1632 /// Returns scale that should be applied to FontStash parameters due to matrix transformations on the context (or 1)
1633 /// Group: font_stash
1634 public float fonsScale (NVGContext ctx) /*pure*/ nothrow @trusted @nogc {
1635   pragma(inline, true);
1636   return (ctx !is null && ctx.contextAlive && ctx.nstates > 0 ? nvg__getFontScale(&ctx.states.ptr[ctx.nstates-1])*ctx.devicePxRatio : 1);
1637 }
1638 
1639 /// Setup FontStash from the given NanoVega context font parameters. Note that this will apply transformation scale too.
1640 /// Returns `false` if FontStash or NanoVega context is not active.
1641 /// Group: font_stash
1642 public bool setupFonsFrom (FONSContext stash, NVGContext ctx) nothrow @trusted @nogc {
1643   if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false;
1644   NVGstate* state = nvg__getState(ctx);
1645   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
1646   stash.size = state.fontSize*scale;
1647   stash.spacing = state.letterSpacing*scale;
1648   stash.blur = state.fontBlur*scale;
1649   stash.textAlign = state.textAlign;
1650   stash.fontId = state.fontId;
1651   return true;
1652 }
1653 
1654 /// Setup NanoVega context font parameters from the given FontStash. Note that NanoVega can apply transformation scale later.
1655 /// Returns `false` if FontStash or NanoVega context is not active.
1656 /// Group: font_stash
1657 public bool setupCtxFrom (NVGContext ctx, FONSContext stash) nothrow @trusted @nogc {
1658   if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false;
1659   NVGstate* state = nvg__getState(ctx);
1660   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
1661   state.fontSize = stash.size;
1662   state.letterSpacing = stash.spacing;
1663   state.fontBlur = stash.blur;
1664   state.textAlign = stash.textAlign;
1665   state.fontId = stash.fontId;
1666   return true;
1667 }
1668 
1669 /** Bezier curve rasterizer.
1670  *
1671  * De Casteljau Bezier rasterizer is faster, but currently rasterizing curves with cusps sligtly wrong.
1672  * It doesn't really matter in practice.
1673  *
1674  * AFD tesselator is somewhat slower, but does cusps better.
1675  *
1676  * McSeem rasterizer should have the best quality, bit it is the slowest method. Basically, you will
1677  * never notice any visial difference (and this code is not really debugged), so you probably should
1678  * not use it. It is there for further experiments.
1679  */
1680 public enum NVGTesselation {
1681   DeCasteljau, /// default: standard well-known tesselation algorithm
1682   AFD, /// adaptive forward differencing
1683   DeCasteljauMcSeem, /// standard well-known tesselation algorithm, with improvements from Maxim Shemanarev; slowest one, but should give best results
1684 }
1685 
1686 /// Default tesselator for Bezier curves.
1687 public __gshared NVGTesselation NVG_DEFAULT_TESSELATOR = NVGTesselation.DeCasteljau;
1688 
1689 
1690 // some public info
1691 
1692 /// valid only inside [beginFrame]/[endFrame]
1693 /// Group: context_management
1694 public int width (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mWidth : 0); }
1695 
1696 /// valid only inside [beginFrame]/[endFrame]
1697 /// Group: context_management
1698 public int height (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mHeight : 0); }
1699 
1700 /// valid only inside [beginFrame]/[endFrame]
1701 /// Group: context_management
1702 public float devicePixelRatio (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.devicePxRatio : float.nan); }
1703 
1704 /// Returns `true` if [beginFrame] was called, and neither [endFrame], nor [cancelFrame] were.
1705 /// Group: context_management
1706 public bool inFrame (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.nstates > 0 : false); }
1707 
1708 // path autoregistration
1709 
1710 /// [pickid] to stop autoregistration.
1711 /// Group: context_management
1712 public enum NVGNoPick = -1;
1713 
1714 /// >=0: this pickid will be assigned to all filled/stroked paths
1715 /// Group: context_management
1716 public int pickid (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickId : NVGNoPick); }
1717 
1718 /// >=0: this pickid will be assigned to all filled/stroked paths
1719 /// Group: context_management
1720 public void pickid (NVGContext ctx, int v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.pathPickId = v; }
1721 
1722 /// pick autoregistration mode; see [NVGPickKind]
1723 /// Group: context_management
1724 public uint pickmode (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickRegistered&NVGPickKind.All : 0); }
1725 
1726 /// pick autoregistration mode; see [NVGPickKind]
1727 /// Group: context_management
1728 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); }
1729 
1730 // tesselator options
1731 
1732 /// Get current Bezier tesselation mode. See [NVGTesselation].
1733 /// Group: context_management
1734 public NVGTesselation tesselation (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.tesselatortype : NVGTesselation.DeCasteljau); }
1735 
1736 /// Set current Bezier tesselation mode. See [NVGTesselation].
1737 /// Group: context_management
1738 public void tesselation (NVGContext ctx, NVGTesselation v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.tesselatortype = v; }
1739 
1740 
1741 private struct NVGcontextinternal {
1742 private:
1743   NVGparams params;
1744   float* commands;
1745   int ccommands;
1746   int ncommands;
1747   float commandx, commandy;
1748   NVGstate[NVG_MAX_STATES] states;
1749   int nstates;
1750   NVGpathCache* cache;
1751   public float tessTol;
1752   public float angleTol; // 0.0f -- angle tolerance for McSeem Bezier rasterizer
1753   public float cuspLimit; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps)
1754   float distTol;
1755   public float fringeWidth;
1756   float devicePxRatio;
1757   FONSContext fs;
1758   NVGImage[NVG_MAX_FONTIMAGES] fontImages;
1759   int fontImageIdx;
1760   int drawCallCount;
1761   int fillTriCount;
1762   int strokeTriCount;
1763   int textTriCount;
1764   NVGTesselation tesselatortype;
1765   // picking API
1766   NVGpickScene* pickScene;
1767   int pathPickId; // >=0: register all paths for picking using this id
1768   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
1769   // path recording
1770   NVGPathSet recset;
1771   int recstart; // used to cancel recording
1772   bool recblockdraw;
1773   // internals
1774   NVGMatrix gpuAffine;
1775   int mWidth, mHeight;
1776   // image manager
1777   shared int imageCount; // number of alive images in this context
1778   bool contextAlive; // context can be dead, but still contain some images
1779 
1780   @disable this (this); // no copies
1781   void opAssign() (const scope auto ref NVGcontextinternal a) { static assert(0, "no copies!"); }
1782 
1783   // debug feature
1784   public @property int getImageCount () nothrow @trusted @nogc {
1785     import core.atomic;
1786     return atomicLoad(imageCount);
1787   }
1788 }
1789 
1790 /** Returns number of tesselated pathes in context.
1791  *
1792  * Tesselated pathes are either triangle strips (for strokes), or
1793  * triangle fans (for fills). Note that NanoVega can generate some
1794  * surprising pathes (like fringe stroke for antialiasing, for example).
1795  *
1796  * One render path can contain vertices both for fill, and for stroke triangles.
1797  */
1798 public int renderPathCount (NVGContext ctx) pure nothrow @trusted @nogc {
1799   pragma(inline, true);
1800   return (ctx !is null && ctx.contextAlive ? ctx.cache.npaths : 0);
1801 }
1802 
1803 /** Get vertices of "fill" triangle fan for the given render path. Can return empty slice.
1804  *
1805  * $(WARNING Returned slice can be invalidated by any other NanoVega API call
1806  *           (except the calls to render path accessors), and using it in such
1807  *           case is UB. So copy vertices to freshly allocated array if you want
1808  *           to keep them for further processing.)
1809  */
1810 public const(NVGVertex)[] renderPathFillVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1811   pragma(inline, true);
1812   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].fillVertices : null);
1813 }
1814 
1815 /** Get vertices of "stroke" triangle strip for the given render path. Can return empty slice.
1816  *
1817  * $(WARNING Returned slice can be invalidated by any other NanoVega API call
1818  *           (except the calls to render path accessors), and using it in such
1819  *           case is UB. So copy vertices to freshly allocated array if you want
1820  *           to keep them for further processing.)
1821  */
1822 public const(NVGVertex)[] renderPathStrokeVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1823   pragma(inline, true);
1824   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].strokeVertices : null);
1825 
1826 }
1827 
1828 /// Returns winding for the given render path.
1829 public NVGWinding renderPathWinding (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1830   pragma(inline, true);
1831   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].winding : NVGWinding.CCW);
1832 
1833 }
1834 
1835 /// Returns "complex path" flag for the given render path.
1836 public bool renderPathComplex (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1837   pragma(inline, true);
1838   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].complex : false);
1839 
1840 }
1841 
1842 void nvg__imageIncRef (NVGContext ctx, int imgid, bool increfInGL=true) nothrow @trusted @nogc {
1843   if (ctx !is null && imgid > 0) {
1844     import core.atomic : atomicOp;
1845     atomicOp!"+="(ctx.imageCount, 1);
1846     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[++]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); }
1847     if (ctx.contextAlive && increfInGL) ctx.params.renderTextureIncRef(ctx.params.userPtr, imgid);
1848   }
1849 }
1850 
1851 void nvg__imageDecRef (NVGContext ctx, int imgid) nothrow @trusted @nogc {
1852   if (ctx !is null && imgid > 0) {
1853     import core.atomic : atomicOp;
1854     int icnt = atomicOp!"-="(ctx.imageCount, 1);
1855     if (icnt < 0) assert(0, "NanoVega: internal image refcounting error");
1856     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[--]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); }
1857     if (ctx.contextAlive) ctx.params.renderDeleteTexture(ctx.params.userPtr, imgid);
1858     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); }
1859     if (!ctx.contextAlive && icnt == 0) {
1860       // it is finally safe to free context memory
1861       import core.stdc.stdlib : free;
1862       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("killed zombie context %p\n", ctx); }
1863       free(ctx);
1864     }
1865   }
1866 }
1867 
1868 
1869 public import core.stdc.math :
1870   nvg__sqrtf = sqrtf,
1871   nvg__modf = fmodf,
1872   nvg__sinf = sinf,
1873   nvg__cosf = cosf,
1874   nvg__tanf = tanf,
1875   nvg__atan2f = atan2f,
1876   nvg__acosf = acosf,
1877   nvg__ceilf = ceilf;
1878 
1879 version(Windows) {
1880   public int nvg__lrintf (float f) nothrow @trusted @nogc { pragma(inline, true); return cast(int)(f+0.5); }
1881 } else {
1882   public import core.stdc.math : nvg__lrintf = lrintf;
1883 }
1884 
1885 public auto nvg__min(T) (T a, T b) { pragma(inline, true); return (a < b ? a : b); }
1886 public auto nvg__max(T) (T a, T b) { pragma(inline, true); return (a > b ? a : b); }
1887 public auto nvg__clamp(T) (T a, T mn, T mx) { pragma(inline, true); return (a < mn ? mn : (a > mx ? mx : a)); }
1888 //float nvg__absf() (float a) { pragma(inline, true); return (a >= 0.0f ? a : -a); }
1889 public auto nvg__sign(T) (T a) { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); }
1890 public float nvg__cross() (float dx0, float dy0, float dx1, float dy1) { pragma(inline, true); return (dx1*dy0-dx0*dy1); }
1891 
1892 //public import core.stdc.math : nvg__absf = fabsf;
1893 public import core.math : nvg__absf = fabs;
1894 
1895 
1896 float nvg__normalize (float* x, float* y) nothrow @safe @nogc {
1897   float d = nvg__sqrtf((*x)*(*x)+(*y)*(*y));
1898   if (d > 1e-6f) {
1899     immutable float id = 1.0f/d;
1900     *x *= id;
1901     *y *= id;
1902   }
1903   return d;
1904 }
1905 
1906 void nvg__deletePathCache (ref NVGpathCache* c) nothrow @trusted @nogc {
1907   if (c !is null) {
1908     c.clear();
1909     free(c);
1910   }
1911 }
1912 
1913 NVGpathCache* nvg__allocPathCache () nothrow @trusted @nogc {
1914   NVGpathCache* c = cast(NVGpathCache*)malloc(NVGpathCache.sizeof);
1915   if (c is null) goto error;
1916   memset(c, 0, NVGpathCache.sizeof);
1917 
1918   c.points = cast(NVGpoint*)malloc(NVGpoint.sizeof*NVG_INIT_POINTS_SIZE);
1919   if (c.points is null) goto error;
1920   assert(c.npoints == 0);
1921   c.cpoints = NVG_INIT_POINTS_SIZE;
1922 
1923   c.paths = cast(NVGpath*)malloc(NVGpath.sizeof*NVG_INIT_PATHS_SIZE);
1924   if (c.paths is null) goto error;
1925   assert(c.npaths == 0);
1926   c.cpaths = NVG_INIT_PATHS_SIZE;
1927 
1928   c.verts = cast(NVGVertex*)malloc(NVGVertex.sizeof*NVG_INIT_VERTS_SIZE);
1929   if (c.verts is null) goto error;
1930   assert(c.nverts == 0);
1931   c.cverts = NVG_INIT_VERTS_SIZE;
1932 
1933   return c;
1934 
1935 error:
1936   nvg__deletePathCache(c);
1937   return null;
1938 }
1939 
1940 void nvg__setDevicePixelRatio (NVGContext ctx, float ratio) pure nothrow @safe @nogc {
1941   ctx.tessTol = 0.25f/ratio;
1942   ctx.distTol = 0.01f/ratio;
1943   ctx.fringeWidth = 1.0f/ratio;
1944   ctx.devicePxRatio = ratio;
1945 }
1946 
1947 NVGCompositeOperationState nvg__compositeOperationState (NVGCompositeOperation op) pure nothrow @safe @nogc {
1948   NVGCompositeOperationState state;
1949   NVGBlendFactor sfactor, dfactor;
1950 
1951        if (op == NVGCompositeOperation.SourceOver) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha;}
1952   else if (op == NVGCompositeOperation.SourceIn) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.Zero; }
1953   else if (op == NVGCompositeOperation.SourceOut) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.Zero; }
1954   else if (op == NVGCompositeOperation.SourceAtop) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.OneMinusSrcAlpha; }
1955   else if (op == NVGCompositeOperation.DestinationOver) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.One; }
1956   else if (op == NVGCompositeOperation.DestinationIn) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.SrcAlpha; }
1957   else if (op == NVGCompositeOperation.DestinationOut) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.OneMinusSrcAlpha; }
1958   else if (op == NVGCompositeOperation.DestinationAtop) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.SrcAlpha; }
1959   else if (op == NVGCompositeOperation.Lighter) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.One; }
1960   else if (op == NVGCompositeOperation.Copy) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.Zero;  }
1961   else if (op == NVGCompositeOperation.Xor) {
1962     state.simple = false;
1963     state.srcRGB = NVGBlendFactor.OneMinusDstColor;
1964     state.srcAlpha = NVGBlendFactor.OneMinusDstAlpha;
1965     state.dstRGB = NVGBlendFactor.OneMinusSrcColor;
1966     state.dstAlpha = NVGBlendFactor.OneMinusSrcAlpha;
1967     return state;
1968   }
1969   else { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } // default value for invalid op: SourceOver
1970 
1971   state.simple = true;
1972   state.srcAlpha = sfactor;
1973   state.dstAlpha = dfactor;
1974   return state;
1975 }
1976 
1977 NVGstate* nvg__getState (NVGContext ctx) pure nothrow @trusted @nogc {
1978   pragma(inline, true);
1979   if (ctx is null || !ctx.contextAlive || ctx.nstates == 0) assert(0, "NanoVega: cannot perform commands on inactive context");
1980   return &ctx.states.ptr[ctx.nstates-1];
1981 }
1982 
1983 // Constructor called by the render back-end.
1984 NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc {
1985   FONSParams fontParams;
1986   NVGContext ctx = cast(NVGContext)malloc(NVGcontextinternal.sizeof);
1987   if (ctx is null) goto error;
1988   memset(ctx, 0, NVGcontextinternal.sizeof);
1989 
1990   ctx.angleTol = 0; // angle tolerance for McSeem Bezier rasterizer
1991   ctx.cuspLimit = 0; // cusp limit for McSeem Bezier rasterizer (0: real cusps)
1992 
1993   ctx.contextAlive = true;
1994 
1995   ctx.params = *params;
1996   //ctx.fontImages[0..NVG_MAX_FONTIMAGES] = 0;
1997 
1998   ctx.commands = cast(float*)malloc(float.sizeof*NVG_INIT_COMMANDS_SIZE);
1999   if (ctx.commands is null) goto error;
2000   ctx.ncommands = 0;
2001   ctx.ccommands = NVG_INIT_COMMANDS_SIZE;
2002 
2003   ctx.cache = nvg__allocPathCache();
2004   if (ctx.cache is null) goto error;
2005 
2006   ctx.save();
2007   ctx.reset();
2008 
2009   nvg__setDevicePixelRatio(ctx, 1.0f);
2010   ctx.mWidth = ctx.mHeight = 0;
2011 
2012   if (!ctx.params.renderCreate(ctx.params.userPtr)) goto error;
2013 
2014   // init font rendering
2015   memset(&fontParams, 0, fontParams.sizeof);
2016   fontParams.width = NVG_INIT_FONTIMAGE_SIZE;
2017   fontParams.height = NVG_INIT_FONTIMAGE_SIZE;
2018   fontParams.flags = FONSParams.Flag.ZeroTopLeft;
2019   fontParams.renderCreate = null;
2020   fontParams.renderUpdate = null;
2021   fontParams.renderDelete = null;
2022   fontParams.userPtr = null;
2023   ctx.fs = FONSContext.create(fontParams);
2024   if (ctx.fs is null) goto error;
2025 
2026   // create font texture
2027   ctx.fontImages[0].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, fontParams.width, fontParams.height, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null);
2028   if (ctx.fontImages[0].id == 0) goto error;
2029   ctx.fontImages[0].ctx = ctx;
2030   ctx.nvg__imageIncRef(ctx.fontImages[0].id, false); // don't increment driver refcount
2031   ctx.fontImageIdx = 0;
2032 
2033   ctx.pathPickId = -1;
2034   ctx.tesselatortype = NVG_DEFAULT_TESSELATOR;
2035 
2036   return ctx;
2037 
2038 error:
2039   ctx.deleteInternal();
2040   return null;
2041 }
2042 
2043 // Called by render backend.
2044 NVGparams* internalParams (NVGContext ctx) nothrow @trusted @nogc {
2045   return &ctx.params;
2046 }
2047 
2048 // Destructor called by the render back-end.
2049 void deleteInternal (ref NVGContext ctx) nothrow @trusted @nogc {
2050   if (ctx is null) return;
2051   if (ctx.contextAlive) {
2052     if (ctx.commands !is null) free(ctx.commands);
2053     nvg__deletePathCache(ctx.cache);
2054 
2055     if (ctx.fs) ctx.fs.kill();
2056 
2057     foreach (uint i; 0..NVG_MAX_FONTIMAGES) ctx.fontImages[i].clear();
2058 
2059     if (ctx.params.renderDelete !is null) ctx.params.renderDelete(ctx.params.userPtr);
2060 
2061     if (ctx.pickScene !is null) nvg__deletePickScene(ctx.pickScene);
2062 
2063     ctx.contextAlive = false;
2064 
2065     import core.atomic : atomicLoad;
2066     if (atomicLoad(ctx.imageCount) == 0) {
2067       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("destroyed context %p\n", ctx); }
2068       free(ctx);
2069     } else {
2070       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("context %p is zombie now (%d image refs)\n", ctx, ctx.imageCount); }
2071     }
2072   }
2073 }
2074 
2075 /// Delete NanoVega context.
2076 /// Group: context_management
2077 public void kill (ref NVGContext ctx) nothrow @trusted @nogc {
2078   if (ctx !is null) {
2079     ctx.deleteInternal();
2080     ctx = null;
2081   }
2082 }
2083 
2084 /// Returns `true` if the given context is not `null` and can be used for painting.
2085 /// Group: context_management
2086 public bool valid (in NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive); }
2087 
2088 
2089 // ////////////////////////////////////////////////////////////////////////// //
2090 // Frame Management
2091 
2092 /** Begin drawing a new frame.
2093  *
2094  * Calls to NanoVega drawing API should be wrapped in [beginFrame] and [endFrame]
2095  *
2096  * [beginFrame] defines the size of the window to render to in relation currently
2097  * set viewport (i.e. glViewport on GL backends). Device pixel ration allows to
2098  * control the rendering on Hi-DPI devices.
2099  *
2100  * For example, GLFW returns two dimension for an opened window: window size and
2101  * frame buffer size. In that case you would set windowWidth/windowHeight to the window size,
2102  * devicePixelRatio to: `windowWidth/windowHeight`.
2103  *
2104  * Default ratio is `1`.
2105  *
2106  * Note that fractional ratio can (and will) distort your fonts and images.
2107  *
2108  * This call also resets pick marks (see picking API for non-rasterized paths),
2109  * path recording, and GPU affine transformatin matrix.
2110  *
2111  * see also [glNVGClearFlags], which returns necessary flags for [glClear].
2112  *
2113  * Group: frame_management
2114  */
2115 public void beginFrame (NVGContext ctx, int windowWidth, int windowHeight, float devicePixelRatio=1.0f) nothrow @trusted @nogc {
2116   import std.math : isNaN;
2117   /*
2118   printf("Tris: draws:%d  fill:%d  stroke:%d  text:%d  TOT:%d\n",
2119          ctx.drawCallCount, ctx.fillTriCount, ctx.strokeTriCount, ctx.textTriCount,
2120          ctx.fillTriCount+ctx.strokeTriCount+ctx.textTriCount);
2121   */
2122   if (ctx.nstates > 0) ctx.cancelFrame();
2123 
2124   if (windowWidth < 1) windowWidth = 1;
2125   if (windowHeight < 1) windowHeight = 1;
2126 
2127   if (isNaN(devicePixelRatio)) devicePixelRatio = (windowHeight > 0 ? cast(float)windowWidth/cast(float)windowHeight : 1024.0/768.0);
2128 
2129   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2130   ctx.nstates = 0;
2131   ctx.save();
2132   ctx.reset();
2133 
2134   nvg__setDevicePixelRatio(ctx, devicePixelRatio);
2135 
2136   ctx.params.renderViewport(ctx.params.userPtr, windowWidth, windowHeight);
2137   ctx.mWidth = windowWidth;
2138   ctx.mHeight = windowHeight;
2139 
2140   ctx.recset = null;
2141   ctx.recstart = -1;
2142 
2143   ctx.pathPickId = NVGNoPick;
2144   ctx.pathPickRegistered = 0;
2145 
2146   ctx.drawCallCount = 0;
2147   ctx.fillTriCount = 0;
2148   ctx.strokeTriCount = 0;
2149   ctx.textTriCount = 0;
2150 
2151   ctx.ncommands = 0;
2152   ctx.pathPickRegistered = 0;
2153   nvg__clearPathCache(ctx);
2154 
2155   ctx.gpuAffine = NVGMatrix.Identity;
2156 
2157   nvg__pickBeginFrame(ctx, windowWidth, windowHeight);
2158 }
2159 
2160 /// Cancels drawing the current frame. Cancels path recording.
2161 /// Group: frame_management
2162 public void cancelFrame (NVGContext ctx) nothrow @trusted @nogc {
2163   ctx.cancelRecording();
2164   //ctx.mWidth = 0;
2165   //ctx.mHeight = 0;
2166   // cancel render queue
2167   ctx.params.renderCancel(ctx.params.userPtr);
2168   // clear saved states (this may free some textures)
2169   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2170   ctx.nstates = 0;
2171 }
2172 
2173 /// Ends drawing the current frame (flushing remaining render state). Commits recorded paths.
2174 /// Group: frame_management
2175 public void endFrame (NVGContext ctx) nothrow @trusted @nogc {
2176   if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx);
2177   ctx.stopRecording();
2178   //ctx.mWidth = 0;
2179   //ctx.mHeight = 0;
2180   // flush render queue
2181   NVGstate* state = nvg__getState(ctx);
2182   ctx.params.renderFlush(ctx.params.userPtr);
2183   if (ctx.fontImageIdx != 0) {
2184     auto fontImage = ctx.fontImages[ctx.fontImageIdx];
2185     int j = 0, iw, ih;
2186     // delete images that smaller than current one
2187     if (!fontImage.valid) return;
2188     ctx.imageSize(fontImage, iw, ih);
2189     foreach (int i; 0..ctx.fontImageIdx) {
2190       if (ctx.fontImages[i].valid) {
2191         int nw, nh;
2192         ctx.imageSize(ctx.fontImages[i], nw, nh);
2193         if (nw < iw || nh < ih) {
2194           ctx.deleteImage(ctx.fontImages[i]);
2195         } else {
2196           ctx.fontImages[j++] = ctx.fontImages[i];
2197         }
2198       }
2199     }
2200     // make current font image to first
2201     ctx.fontImages[j++] = ctx.fontImages[0];
2202     ctx.fontImages[0] = fontImage;
2203     ctx.fontImageIdx = 0;
2204     // clear all images after j
2205     ctx.fontImages[j..NVG_MAX_FONTIMAGES] = NVGImage.init;
2206   }
2207   // clear saved states (this may free some textures)
2208   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2209   ctx.nstates = 0;
2210 }
2211 
2212 
2213 // ////////////////////////////////////////////////////////////////////////// //
2214 // Recording and Replaying Pathes
2215 
2216 // Saved path set.
2217 // Group: path_recording
2218 public alias NVGPathSet = NVGPathSetS*;
2219 
2220 
2221 //TODO: save scissor info?
2222 struct NVGPathSetS {
2223 private:
2224   // either path cache, or text item
2225   static struct Node {
2226     NVGPaint paint;
2227     NVGpathCache* path;
2228   }
2229 
2230 private:
2231   Node* nodes;
2232   int nnodes, cnodes;
2233   NVGpickScene* pickscene;
2234   //int npickscenes, cpickscenes;
2235   NVGContext svctx; // used to do some sanity checks, and to free resources
2236 
2237 private:
2238   Node* allocNode () nothrow @trusted @nogc {
2239     import core.stdc.string : memset;
2240     // grow buffer if necessary
2241     if (nnodes+1 > cnodes) {
2242       import core.stdc.stdlib : realloc;
2243       int newsz = (cnodes == 0 ? 8 : cnodes <= 1024 ? cnodes*2 : cnodes+1024);
2244       nodes = cast(Node*)realloc(nodes, newsz*Node.sizeof);
2245       if (nodes is null) assert(0, "NanoVega: out of memory");
2246       //memset(svp.caches+svp.ccaches, 0, (newsz-svp.ccaches)*NVGpathCache.sizeof);
2247       cnodes = newsz;
2248     }
2249     assert(nnodes < cnodes);
2250     memset(nodes+nnodes, 0, Node.sizeof);
2251     return &nodes[nnodes++];
2252   }
2253 
2254   Node* allocPathNode () nothrow @trusted @nogc {
2255     import core.stdc.stdlib : malloc;
2256     import core.stdc.string : memset;
2257     auto node = allocNode();
2258     // allocate path cache
2259     auto pc = cast(NVGpathCache*)malloc(NVGpathCache.sizeof);
2260     if (pc is null) assert(0, "NanoVega: out of memory");
2261     node.path = pc;
2262     return node;
2263   }
2264 
2265   void clearNode (int idx) nothrow @trusted @nogc {
2266     if (idx < 0 || idx >= nnodes) return;
2267     Node* node = &nodes[idx];
2268     if (svctx !is null && node.paint.image.valid) node.paint.image.clear();
2269     if (node.path !is null) node.path.clear();
2270   }
2271 
2272 private:
2273   void takeCurrentPickScene (NVGContext ctx) nothrow @trusted @nogc {
2274     NVGpickScene* ps = ctx.pickScene;
2275     if (ps is null) return; // nothing to do
2276     if (ps.npaths == 0) return; // pick scene is empty
2277     ctx.pickScene = null;
2278     pickscene = ps;
2279   }
2280 
2281   void replay (NVGContext ctx, const scope ref NVGColor fillTint, const scope ref NVGColor strokeTint) nothrow @trusted @nogc {
2282     NVGstate* state = nvg__getState(ctx);
2283     foreach (ref node; nodes[0..nnodes]) {
2284       if (auto cc = node.path) {
2285         if (cc.npaths <= 0) continue;
2286 
2287         if (cc.fillReady) {
2288           NVGPaint fillPaint = node.paint;
2289 
2290           // apply global alpha
2291           fillPaint.innerColor.a *= state.alpha;
2292           fillPaint.middleColor.a *= state.alpha;
2293           fillPaint.outerColor.a *= state.alpha;
2294 
2295           fillPaint.innerColor.applyTint(fillTint);
2296           fillPaint.middleColor.applyTint(fillTint);
2297           fillPaint.outerColor.applyTint(fillTint);
2298 
2299           ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &fillPaint, &state.scissor, cc.fringeWidth, cc.bounds.ptr, cc.paths, cc.npaths, cc.evenOddMode);
2300 
2301           // count triangles
2302           foreach (int i; 0..cc.npaths) {
2303             NVGpath* path = &cc.paths[i];
2304             ctx.fillTriCount += path.nfill-2;
2305             ctx.fillTriCount += path.nstroke-2;
2306             ctx.drawCallCount += 2;
2307           }
2308         }
2309 
2310         if (cc.strokeReady) {
2311           NVGPaint strokePaint = node.paint;
2312 
2313           strokePaint.innerColor.a *= cc.strokeAlphaMul;
2314           strokePaint.middleColor.a *= cc.strokeAlphaMul;
2315           strokePaint.outerColor.a *= cc.strokeAlphaMul;
2316 
2317           // apply global alpha
2318           strokePaint.innerColor.a *= state.alpha;
2319           strokePaint.middleColor.a *= state.alpha;
2320           strokePaint.outerColor.a *= state.alpha;
2321 
2322           strokePaint.innerColor.applyTint(strokeTint);
2323           strokePaint.middleColor.applyTint(strokeTint);
2324           strokePaint.outerColor.applyTint(strokeTint);
2325 
2326           ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &strokePaint, &state.scissor, cc.fringeWidth, cc.strokeWidth, cc.paths, cc.npaths);
2327 
2328           // count triangles
2329           foreach (int i; 0..cc.npaths) {
2330             NVGpath* path = &cc.paths[i];
2331             ctx.strokeTriCount += path.nstroke-2;
2332             ++ctx.drawCallCount;
2333           }
2334         }
2335       }
2336     }
2337   }
2338 
2339 public:
2340   @disable this (this); // no copies
2341   void opAssign() (const scope auto ref NVGPathSetS a) { static assert(0, "no copies!"); }
2342 
2343   // pick test
2344   // Call delegate [dg] for each path under the specified position (in no particular order).
2345   // Returns the id of the path for which delegate [dg] returned true or -1.
2346   // dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending).
2347   int hitTestDG(bool bestOrder=false, DG) (in float x, in float y, NVGPickKind kind, scope DG dg) if (IsGoodHitTestDG!DG || IsGoodHitTestInternalDG!DG) {
2348     if (pickscene is null) return -1;
2349 
2350     NVGpickScene* ps = pickscene;
2351     int levelwidth = 1<<(ps.nlevels-1);
2352     int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth);
2353     int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth);
2354     int npicked = 0;
2355 
2356     for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) {
2357       NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx];
2358       while (pp !is null) {
2359         if (nvg__pickPathTestBounds(svctx, ps, pp, x, y)) {
2360           int hit = 0;
2361           if ((kind&NVGPickKind.Stroke) && (pp.flags&NVGPathFlags.Stroke)) hit = nvg__pickPathStroke(ps, pp, x, y);
2362           if (!hit && (kind&NVGPickKind.Fill) && (pp.flags&NVGPathFlags.Fill)) hit = nvg__pickPath(ps, pp, x, y);
2363           if (hit) {
2364             static if (IsGoodHitTestDG!DG) {
2365               static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) {
2366                 if (dg(pp.id, cast(int)pp.order)) return pp.id;
2367               } else {
2368                 dg(pp.id, cast(int)pp.order);
2369               }
2370             } else {
2371               static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) {
2372                 if (dg(pp)) return pp.id;
2373               } else {
2374                 dg(pp);
2375               }
2376             }
2377           }
2378         }
2379         pp = pp.next;
2380       }
2381       cellx >>= 1;
2382       celly >>= 1;
2383       levelwidth >>= 1;
2384     }
2385 
2386     return -1;
2387   }
2388 
2389   // Fills ids with a list of the top most hit ids under the specified position.
2390   // Returns the slice of [ids].
2391   int[] hitTestAll (in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc {
2392     if (pickscene is null || ids.length == 0) return ids[0..0];
2393 
2394     int npicked = 0;
2395     NVGpickScene* ps = pickscene;
2396 
2397     hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
2398       if (npicked == ps.cpicked) {
2399         int cpicked = ps.cpicked+ps.cpicked;
2400         NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked);
2401         if (picked is null) return true; // abort
2402         ps.cpicked = cpicked;
2403         ps.picked = picked;
2404       }
2405       ps.picked[npicked] = pp;
2406       ++npicked;
2407       return false; // go on
2408     });
2409 
2410     qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths);
2411 
2412     assert(npicked >= 0);
2413     if (npicked > ids.length) npicked = cast(int)ids.length;
2414     foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id;
2415 
2416     return ids[0..npicked];
2417   }
2418 
2419   // Returns the id of the pickable shape containing x,y or -1 if no shape was found.
2420   int hitTest (in float x, in float y, NVGPickKind kind) nothrow @trusted @nogc {
2421     if (pickscene is null) return -1;
2422 
2423     int bestOrder = -1;
2424     int bestID = -1;
2425 
2426     hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
2427       if (pp.order > bestOrder) {
2428         bestOrder = pp.order;
2429         bestID = pp.id;
2430       }
2431     });
2432 
2433     return bestID;
2434   }
2435 }
2436 
2437 // Append current path to existing path set. Is is safe to call this with `null` [svp].
2438 void appendCurrentPathToCache (NVGContext ctx, NVGPathSet svp, const scope ref NVGPaint paint) nothrow @trusted @nogc {
2439   if (ctx is null || svp is null) return;
2440   if (ctx !is svp.svctx) assert(0, "NanoVega: cannot save paths from different contexts");
2441   if (ctx.ncommands == 0) {
2442     assert(ctx.cache.npaths == 0);
2443     return;
2444   }
2445   if (!ctx.cache.fillReady && !ctx.cache.strokeReady) return;
2446 
2447   // tesselate current path
2448   //if (!ctx.cache.fillReady) nvg__prepareFill(ctx);
2449   //if (!ctx.cache.strokeReady) nvg__prepareStroke(ctx);
2450 
2451   auto node = svp.allocPathNode();
2452   NVGpathCache* cc = node.path;
2453   cc.copyFrom(ctx.cache);
2454   node.paint = paint;
2455   // copy path commands (we may need 'em for picking)
2456   version(all) {
2457     cc.ncommands = ctx.ncommands;
2458     if (cc.ncommands) {
2459       import core.stdc.stdlib : malloc;
2460       import core.stdc.string : memcpy;
2461       cc.commands = cast(float*)malloc(ctx.ncommands*float.sizeof);
2462       if (cc.commands is null) assert(0, "NanoVega: out of memory");
2463       memcpy(cc.commands, ctx.commands, ctx.ncommands*float.sizeof);
2464     } else {
2465       cc.commands = null;
2466     }
2467   }
2468 }
2469 
2470 // Create new empty path set.
2471 // Group: path_recording
2472 public NVGPathSet newPathSet (NVGContext ctx) nothrow @trusted @nogc {
2473   import core.stdc.stdlib : malloc;
2474   import core.stdc.string : memset;
2475   if (ctx is null) return null;
2476   NVGPathSet res = cast(NVGPathSet)malloc(NVGPathSetS.sizeof);
2477   if (res is null) assert(0, "NanoVega: out of memory");
2478   memset(res, 0, NVGPathSetS.sizeof);
2479   res.svctx = ctx;
2480   return res;
2481 }
2482 
2483 // Is the given path set empty? Empty path set can be `null`.
2484 // Group: path_recording
2485 public bool empty (NVGPathSet svp) pure nothrow @safe @nogc { pragma(inline, true); return (svp is null || svp.nnodes == 0); }
2486 
2487 // Clear path set contents. Will release $(B some) allocated memory (this function is meant to clear something that will be reused).
2488 // Group: path_recording
2489 public void clear (NVGPathSet svp) nothrow @trusted @nogc {
2490   if (svp !is null) {
2491     import core.stdc.stdlib : free;
2492     foreach (immutable idx; 0.. svp.nnodes) svp.clearNode(idx);
2493     svp.nnodes = 0;
2494   }
2495 }
2496 
2497 // Destroy path set (frees all allocated memory).
2498 // Group: path_recording
2499 public void kill (ref NVGPathSet svp) nothrow @trusted @nogc {
2500   if (svp !is null) {
2501     import core.stdc.stdlib : free;
2502     svp.clear();
2503     if (svp.nodes !is null) free(svp.nodes);
2504     free(svp);
2505     if (svp.pickscene !is null) nvg__deletePickScene(svp.pickscene);
2506     svp = null;
2507   }
2508 }
2509 
2510 // Start path recording. [svp] should be alive until recording is cancelled or stopped.
2511 // Group: path_recording
2512 public void startRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc {
2513   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2514   ctx.stopRecording();
2515   ctx.recset = svp;
2516   ctx.recstart = (svp !is null ? svp.nnodes : -1);
2517   ctx.recblockdraw = false;
2518 }
2519 
2520 /* Start path recording. [svp] should be alive until recording is cancelled or stopped.
2521  *
2522  * This will block all rendering, so you can call your rendering functions to record paths without actual drawing.
2523  * Commiting or cancelling will re-enable rendering.
2524  * You can call this with `null` svp to block rendering without recording any paths.
2525  *
2526  * Group: path_recording
2527  */
2528 public void startBlockingRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc {
2529   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2530   ctx.stopRecording();
2531   ctx.recset = svp;
2532   ctx.recstart = (svp !is null ? svp.nnodes : -1);
2533   ctx.recblockdraw = true;
2534 }
2535 
2536 // Commit recorded paths. It is safe to call this when recording is not started.
2537 // Group: path_recording
2538 public void stopRecording (NVGContext ctx) nothrow @trusted @nogc {
2539   if (ctx.recset !is null && ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2540   if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx);
2541   ctx.recset = null;
2542   ctx.recstart = -1;
2543   ctx.recblockdraw = false;
2544 }
2545 
2546 // Cancel path recording.
2547 // Group: path_recording
2548 public void cancelRecording (NVGContext ctx) nothrow @trusted @nogc {
2549   if (ctx.recset !is null) {
2550     if (ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2551     assert(ctx.recstart >= 0 && ctx.recstart <= ctx.recset.nnodes);
2552     foreach (immutable idx; ctx.recstart..ctx.recset.nnodes) ctx.recset.clearNode(idx);
2553     ctx.recset.nnodes = ctx.recstart;
2554     ctx.recset = null;
2555     ctx.recstart = -1;
2556   }
2557   ctx.recblockdraw = false;
2558 }
2559 
2560 /* Replay saved path set.
2561  *
2562  * Replaying record while you're recording another one is undefined behavior.
2563  *
2564  * Group: path_recording
2565  */
2566 public void replayRecording() (NVGContext ctx, NVGPathSet svp, const scope auto ref NVGColor fillTint, const scope auto ref NVGColor strokeTint) nothrow @trusted @nogc {
2567   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2568   svp.replay(ctx, fillTint, strokeTint);
2569 }
2570 
2571 /// Ditto.
2572 public void replayRecording() (NVGContext ctx, NVGPathSet svp, const scope auto ref NVGColor fillTint) nothrow @trusted @nogc { ctx.replayRecording(svp, fillTint, NVGColor.transparent); }
2573 
2574 /// Ditto.
2575 public void replayRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { ctx.replayRecording(svp, NVGColor.transparent, NVGColor.transparent); }
2576 
2577 
2578 // ////////////////////////////////////////////////////////////////////////// //
2579 // Composite operation
2580 
2581 /// Sets the composite operation.
2582 /// Group: composite_operation
2583 public void globalCompositeOperation (NVGContext ctx, NVGCompositeOperation op) nothrow @trusted @nogc {
2584   NVGstate* state = nvg__getState(ctx);
2585   state.compositeOperation = nvg__compositeOperationState(op);
2586 }
2587 
2588 /// Sets the composite operation with custom pixel arithmetic.
2589 /// Group: composite_operation
2590 public void globalCompositeBlendFunc (NVGContext ctx, NVGBlendFactor sfactor, NVGBlendFactor dfactor) nothrow @trusted @nogc {
2591   ctx.globalCompositeBlendFuncSeparate(sfactor, dfactor, sfactor, dfactor);
2592 }
2593 
2594 /// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately.
2595 /// Group: composite_operation
2596 public void globalCompositeBlendFuncSeparate (NVGContext ctx, NVGBlendFactor srcRGB, NVGBlendFactor dstRGB, NVGBlendFactor srcAlpha, NVGBlendFactor dstAlpha) nothrow @trusted @nogc {
2597   NVGCompositeOperationState op;
2598   op.simple = false;
2599   op.srcRGB = srcRGB;
2600   op.dstRGB = dstRGB;
2601   op.srcAlpha = srcAlpha;
2602   op.dstAlpha = dstAlpha;
2603   NVGstate* state = nvg__getState(ctx);
2604   state.compositeOperation = op;
2605 }
2606 
2607 
2608 // ////////////////////////////////////////////////////////////////////////// //
2609 // Color utils
2610 
2611 /// Returns a color value from string form.
2612 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb"
2613 /// Group: color_utils
2614 public NVGColor nvgRGB (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); }
2615 
2616 /// Ditto.
2617 public NVGColor nvgRGBA (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); }
2618 
2619 /// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f).
2620 /// Group: color_utils
2621 public NVGColor nvgRGB (int r, int g, int b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(nvgClampToByte(r), nvgClampToByte(g), nvgClampToByte(b), 255); }
2622 
2623 /// Returns a color value from red, green, blue values. Alpha will be set to 1.0f.
2624 /// Group: color_utils
2625 public NVGColor nvgRGBf (float r, float g, float b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(r, g, b, 1.0f); }
2626 
2627 /// Returns a color value from red, green, blue and alpha values.
2628 /// Group: color_utils
2629 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)); }
2630 
2631 /// Returns a color value from red, green, blue and alpha values.
2632 /// Group: color_utils
2633 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); }
2634 
2635 /// Returns new color with transparency (alpha) set to [a].
2636 /// Group: color_utils
2637 public NVGColor nvgTransRGBA (NVGColor c, ubyte a) nothrow @trusted @nogc {
2638   pragma(inline, true);
2639   c.a = a/255.0f;
2640   return c;
2641 }
2642 
2643 /// Ditto.
2644 public NVGColor nvgTransRGBAf (NVGColor c, float a) nothrow @trusted @nogc {
2645   pragma(inline, true);
2646   c.a = a;
2647   return c;
2648 }
2649 
2650 /// Linearly interpolates from color c0 to c1, and returns resulting color value.
2651 /// Group: color_utils
2652 public NVGColor nvgLerpRGBA() (const scope auto ref NVGColor c0, const scope auto ref NVGColor c1, float u) nothrow @trusted @nogc {
2653   NVGColor cint = void;
2654   u = nvg__clamp(u, 0.0f, 1.0f);
2655   float oneminu = 1.0f-u;
2656   foreach (uint i; 0..4) cint.rgba.ptr[i] = c0.rgba.ptr[i]*oneminu+c1.rgba.ptr[i]*u;
2657   return cint;
2658 }
2659 
2660 /* see below
2661 public NVGColor nvgHSL() (float h, float s, float l) {
2662   //pragma(inline, true); // alas
2663   return nvgHSLA(h, s, l, 255);
2664 }
2665 */
2666 
2667 float nvg__hue (float h, float m1, float m2) pure nothrow @safe @nogc {
2668   if (h < 0) h += 1;
2669   if (h > 1) h -= 1;
2670   if (h < 1.0f/6.0f) return m1+(m2-m1)*h*6.0f;
2671   if (h < 3.0f/6.0f) return m2;
2672   if (h < 4.0f/6.0f) return m1+(m2-m1)*(2.0f/3.0f-h)*6.0f;
2673   return m1;
2674 }
2675 
2676 /// Returns color value specified by hue, saturation and lightness.
2677 /// HSL values are all in range [0..1], alpha will be set to 255.
2678 /// Group: color_utils
2679 public alias nvgHSL = nvgHSLA; // trick to allow inlining
2680 
2681 /// Returns color value specified by hue, saturation and lightness and alpha.
2682 /// HSL values are all in range [0..1], alpha in range [0..255].
2683 /// Group: color_utils
2684 public NVGColor nvgHSLA (float h, float s, float l, ubyte a=255) nothrow @trusted @nogc {
2685   pragma(inline, true);
2686   NVGColor col = void;
2687   h = nvg__modf(h, 1.0f);
2688   if (h < 0.0f) h += 1.0f;
2689   s = nvg__clamp(s, 0.0f, 1.0f);
2690   l = nvg__clamp(l, 0.0f, 1.0f);
2691   immutable float m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s);
2692   immutable float m1 = 2*l-m2;
2693   col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2694   col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f);
2695   col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2696   col.a = a/255.0f;
2697   return col;
2698 }
2699 
2700 /// Returns color value specified by hue, saturation and lightness and alpha.
2701 /// HSL values and alpha are all in range [0..1].
2702 /// Group: color_utils
2703 public NVGColor nvgHSLA (float h, float s, float l, float a) nothrow @trusted @nogc {
2704   // sorry for copypasta, it is for inliner
2705   static if (__VERSION__ >= 2072) pragma(inline, true);
2706   NVGColor col = void;
2707   h = nvg__modf(h, 1.0f);
2708   if (h < 0.0f) h += 1.0f;
2709   s = nvg__clamp(s, 0.0f, 1.0f);
2710   l = nvg__clamp(l, 0.0f, 1.0f);
2711   immutable m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s);
2712   immutable m1 = 2*l-m2;
2713   col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2714   col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f);
2715   col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2716   col.a = a;
2717   return col;
2718 }
2719 
2720 
2721 // ////////////////////////////////////////////////////////////////////////// //
2722 // Matrices and Transformations
2723 
2724 /** Matrix class.
2725  *
2726  * Group: matrices
2727  */
2728 public align(1) struct NVGMatrix {
2729 align(1):
2730 private:
2731   static immutable float[6] IdentityMat = [
2732     1.0f, 0.0f,
2733     0.0f, 1.0f,
2734     0.0f, 0.0f,
2735   ];
2736 
2737 public:
2738   /// Matrix values. Initial value is identity matrix.
2739   float[6] mat = [
2740     1.0f, 0.0f,
2741     0.0f, 1.0f,
2742     0.0f, 0.0f,
2743   ];
2744 
2745 public nothrow @trusted @nogc:
2746   /// Create Matrix with the given values.
2747   this (const(float)[] amat...) {
2748     pragma(inline, true);
2749     if (amat.length >= 6) {
2750       mat.ptr[0..6] = amat.ptr[0..6];
2751     } else {
2752       mat.ptr[0..6] = 0;
2753       mat.ptr[0..amat.length] = amat[];
2754     }
2755   }
2756 
2757   /// Can be used to check validity of [inverted] result
2758   @property bool valid () const { import core.stdc.math : isfinite; return (isfinite(mat.ptr[0]) != 0); }
2759 
2760   /// Returns `true` if this matrix is identity matrix.
2761   @property bool isIdentity () const { version(aliced) pragma(inline, true); return (mat[] == IdentityMat[]); }
2762 
2763   /// Returns new inverse matrix.
2764   /// If inverted matrix cannot be calculated, `res.valid` fill be `false`.
2765   NVGMatrix inverted () const {
2766     NVGMatrix res = this;
2767     res.invert;
2768     return res;
2769   }
2770 
2771   /// Inverts this matrix.
2772   /// If inverted matrix cannot be calculated, `this.valid` fill be `false`.
2773   ref NVGMatrix invert () return {
2774     float[6] inv = void;
2775     immutable double det = cast(double)mat.ptr[0]*mat.ptr[3]-cast(double)mat.ptr[2]*mat.ptr[1];
2776     if (det > -1e-6 && det < 1e-6) {
2777       inv[] = float.nan;
2778     } else {
2779       immutable double invdet = 1.0/det;
2780       inv.ptr[0] = cast(float)(mat.ptr[3]*invdet);
2781       inv.ptr[2] = cast(float)(-mat.ptr[2]*invdet);
2782       inv.ptr[4] = cast(float)((cast(double)mat.ptr[2]*mat.ptr[5]-cast(double)mat.ptr[3]*mat.ptr[4])*invdet);
2783       inv.ptr[1] = cast(float)(-mat.ptr[1]*invdet);
2784       inv.ptr[3] = cast(float)(mat.ptr[0]*invdet);
2785       inv.ptr[5] = cast(float)((cast(double)mat.ptr[1]*mat.ptr[4]-cast(double)mat.ptr[0]*mat.ptr[5])*invdet);
2786     }
2787     mat.ptr[0..6] = inv.ptr[0..6];
2788     return this;
2789   }
2790 
2791   /// Sets this matrix to identity matrix.
2792   ref NVGMatrix identity () return { version(aliced) pragma(inline, true); mat[] = IdentityMat[]; return this; }
2793 
2794   /// Translate this matrix.
2795   ref NVGMatrix translate (in float tx, in float ty) return {
2796     version(aliced) pragma(inline, true);
2797     return this.mul(Translated(tx, ty));
2798   }
2799 
2800   /// Scale this matrix.
2801   ref NVGMatrix scale (in float sx, in float sy) return {
2802     version(aliced) pragma(inline, true);
2803     return this.mul(Scaled(sx, sy));
2804   }
2805 
2806   /// Rotate this matrix.
2807   ref NVGMatrix rotate (in float a) return {
2808     version(aliced) pragma(inline, true);
2809     return this.mul(Rotated(a));
2810   }
2811 
2812   /// Skew this matrix by X axis.
2813   ref NVGMatrix skewX (in float a) return {
2814     version(aliced) pragma(inline, true);
2815     return this.mul(SkewedX(a));
2816   }
2817 
2818   /// Skew this matrix by Y axis.
2819   ref NVGMatrix skewY (in float a) return {
2820     version(aliced) pragma(inline, true);
2821     return this.mul(SkewedY(a));
2822   }
2823 
2824   /// Skew this matrix by both axes.
2825   ref NVGMatrix skewY (in float ax, in float ay) return {
2826     version(aliced) pragma(inline, true);
2827     return this.mul(SkewedXY(ax, ay));
2828   }
2829 
2830   /// Transform point with this matrix. `null` destinations are allowed.
2831   /// [sx] and [sy] is the source point. [dx] and [dy] may point to the same variables.
2832   void point (float* dx, float* dy, float sx, float sy) nothrow @trusted @nogc {
2833     version(aliced) pragma(inline, true);
2834     if (dx !is null) *dx = sx*mat.ptr[0]+sy*mat.ptr[2]+mat.ptr[4];
2835     if (dy !is null) *dy = sx*mat.ptr[1]+sy*mat.ptr[3]+mat.ptr[5];
2836   }
2837 
2838   /// Transform point with this matrix.
2839   void point (ref float x, ref float y) nothrow @trusted @nogc {
2840     version(aliced) pragma(inline, true);
2841     immutable float nx = x*mat.ptr[0]+y*mat.ptr[2]+mat.ptr[4];
2842     immutable float ny = x*mat.ptr[1]+y*mat.ptr[3]+mat.ptr[5];
2843     x = nx;
2844     y = ny;
2845   }
2846 
2847   /// Sets this matrix to the result of multiplication of `this` and [s] (this * S).
2848   ref NVGMatrix mul() (const scope auto ref NVGMatrix s) {
2849     immutable float t0 = mat.ptr[0]*s.mat.ptr[0]+mat.ptr[1]*s.mat.ptr[2];
2850     immutable float t2 = mat.ptr[2]*s.mat.ptr[0]+mat.ptr[3]*s.mat.ptr[2];
2851     immutable float t4 = mat.ptr[4]*s.mat.ptr[0]+mat.ptr[5]*s.mat.ptr[2]+s.mat.ptr[4];
2852     mat.ptr[1] = mat.ptr[0]*s.mat.ptr[1]+mat.ptr[1]*s.mat.ptr[3];
2853     mat.ptr[3] = mat.ptr[2]*s.mat.ptr[1]+mat.ptr[3]*s.mat.ptr[3];
2854     mat.ptr[5] = mat.ptr[4]*s.mat.ptr[1]+mat.ptr[5]*s.mat.ptr[3]+s.mat.ptr[5];
2855     mat.ptr[0] = t0;
2856     mat.ptr[2] = t2;
2857     mat.ptr[4] = t4;
2858     return this;
2859   }
2860 
2861   /// Sets this matrix to the result of multiplication of [s] and `this` (S * this).
2862   /// Sets the transform to the result of multiplication of two transforms, of A = B*A.
2863   /// Group: matrices
2864   ref NVGMatrix premul() (const scope auto ref NVGMatrix s) {
2865     NVGMatrix s2 = s;
2866     s2.mul(this);
2867     mat[] = s2.mat[];
2868     return this;
2869   }
2870 
2871   /// Multiply this matrix by [s], return result as new matrix.
2872   /// Performs operations in this left-to-right order.
2873   NVGMatrix opBinary(string op="*") (const scope auto ref NVGMatrix s) const {
2874     version(aliced) pragma(inline, true);
2875     NVGMatrix res = this;
2876     res.mul(s);
2877     return res;
2878   }
2879 
2880   /// Multiply this matrix by [s].
2881   /// Performs operations in this left-to-right order.
2882   ref NVGMatrix opOpAssign(string op="*") (const scope auto ref NVGMatrix s) {
2883     version(aliced) pragma(inline, true);
2884     return this.mul(s);
2885   }
2886 
2887   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.
2888   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.
2889   float rotation () const { pragma(inline, true); return nvg__atan2f(mat.ptr[1], mat.ptr[0]); } /// Returns rotation of this matrix.
2890   float tx () const { pragma(inline, true); return mat.ptr[4]; } /// Returns x translation of this matrix.
2891   float ty () const { pragma(inline, true); return mat.ptr[5]; } /// Returns y translation of this matrix.
2892 
2893   ref NVGMatrix scaleX (in float v) return { pragma(inline, true); return scaleRotateTransform(v, scaleY, rotation, tx, ty); } /// Sets x scaling of this matrix.
2894   ref NVGMatrix scaleY (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, v, rotation, tx, ty); } /// Sets y scaling of this matrix.
2895   ref NVGMatrix rotation (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, scaleY, v, tx, ty); } /// Sets rotation of this matrix.
2896   ref NVGMatrix tx (in float v) return { pragma(inline, true); mat.ptr[4] = v; return this; } /// Sets x translation of this matrix.
2897   ref NVGMatrix ty (in float v) return { pragma(inline, true); mat.ptr[5] = v; return this; } /// Sets y translation of this matrix.
2898 
2899   /// Utility function to be used in `setXXX()`.
2900   /// This is the same as doing: `mat.identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster
2901   ref NVGMatrix scaleRotateTransform (in float xscale, in float yscale, in float a, in float tx, in float ty) return {
2902     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2903     mat.ptr[0] = xscale*cs; mat.ptr[1] = yscale*sn;
2904     mat.ptr[2] = xscale*-sn; mat.ptr[3] = yscale*cs;
2905     mat.ptr[4] = tx; mat.ptr[5] = ty;
2906     return this;
2907   }
2908 
2909   /// This is the same as doing: `mat.identity.rotate(a).translate(tx, ty)`, only faster
2910   ref NVGMatrix rotateTransform (in float a, in float tx, in float ty) return {
2911     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2912     mat.ptr[0] = cs; mat.ptr[1] = sn;
2913     mat.ptr[2] = -sn; mat.ptr[3] = cs;
2914     mat.ptr[4] = tx; mat.ptr[5] = ty;
2915     return this;
2916   }
2917 
2918   /// Returns new identity matrix.
2919   static NVGMatrix Identity () { pragma(inline, true); return NVGMatrix.init; }
2920 
2921   /// Returns new translation matrix.
2922   static NVGMatrix Translated (in float tx, in float ty) {
2923     version(aliced) pragma(inline, true);
2924     NVGMatrix res = void;
2925     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f;
2926     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f;
2927     res.mat.ptr[4] = tx; res.mat.ptr[5] = ty;
2928     return res;
2929   }
2930 
2931   /// Returns new scaling matrix.
2932   static NVGMatrix Scaled (in float sx, in float sy) {
2933     version(aliced) pragma(inline, true);
2934     NVGMatrix res = void;
2935     res.mat.ptr[0] = sx; res.mat.ptr[1] = 0.0f;
2936     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = sy;
2937     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2938     return res;
2939   }
2940 
2941   /// Returns new rotation matrix. Angle is specified in radians.
2942   static NVGMatrix Rotated (in float a) {
2943     version(aliced) pragma(inline, true);
2944     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2945     NVGMatrix res = void;
2946     res.mat.ptr[0] = cs; res.mat.ptr[1] = sn;
2947     res.mat.ptr[2] = -sn; res.mat.ptr[3] = cs;
2948     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2949     return res;
2950   }
2951 
2952   /// Returns new x-skewing matrix. Angle is specified in radians.
2953   static NVGMatrix SkewedX (in float a) {
2954     version(aliced) pragma(inline, true);
2955     NVGMatrix res = void;
2956     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f;
2957     res.mat.ptr[2] = nvg__tanf(a); res.mat.ptr[3] = 1.0f;
2958     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2959     return res;
2960   }
2961 
2962   /// Returns new y-skewing matrix. Angle is specified in radians.
2963   static NVGMatrix SkewedY (in float a) {
2964     version(aliced) pragma(inline, true);
2965     NVGMatrix res = void;
2966     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(a);
2967     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f;
2968     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2969     return res;
2970   }
2971 
2972   /// Returns new xy-skewing matrix. Angles are specified in radians.
2973   static NVGMatrix SkewedXY (in float ax, in float ay) {
2974     version(aliced) pragma(inline, true);
2975     NVGMatrix res = void;
2976     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(ay);
2977     res.mat.ptr[2] = nvg__tanf(ax); res.mat.ptr[3] = 1.0f;
2978     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2979     return res;
2980   }
2981 
2982   /// Utility function to be used in `setXXX()`.
2983   /// This is the same as doing: `NVGMatrix.Identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster
2984   static NVGMatrix ScaledRotatedTransformed (in float xscale, in float yscale, in float a, in float tx, in float ty) {
2985     NVGMatrix res = void;
2986     res.scaleRotateTransform(xscale, yscale, a, tx, ty);
2987     return res;
2988   }
2989 
2990   /// This is the same as doing: `NVGMatrix.Identity.rotate(a).translate(tx, ty)`, only faster
2991   static NVGMatrix RotatedTransformed (in float a, in float tx, in float ty) {
2992     NVGMatrix res = void;
2993     res.rotateTransform(a, tx, ty);
2994     return res;
2995   }
2996 }
2997 
2998 
2999 /// Converts degrees to radians.
3000 /// Group: matrices
3001 public float nvgDegToRad() (in float deg) pure nothrow @safe @nogc { pragma(inline, true); return deg/180.0f*NVG_PI; }
3002 
3003 /// Converts radians to degrees.
3004 /// Group: matrices
3005 public float nvgRadToDeg() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad/NVG_PI*180.0f; }
3006 
3007 public alias nvgDegrees = nvgDegToRad; /// Use this like `42.nvgDegrees`
3008 public float nvgRadians() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad; } /// Use this like `0.1.nvgRadians`
3009 
3010 
3011 // ////////////////////////////////////////////////////////////////////////// //
3012 void nvg__setPaintColor() (ref NVGPaint p, const scope auto ref NVGColor color) nothrow @trusted @nogc {
3013   p.clear();
3014   p.xform.identity;
3015   p.radius = 0.0f;
3016   p.feather = 1.0f;
3017   p.innerColor = p.middleColor = p.outerColor = color;
3018   p.midp = -1;
3019   p.simpleColor = true;
3020 }
3021 
3022 
3023 // ////////////////////////////////////////////////////////////////////////// //
3024 // State handling
3025 
3026 version(nanovega_debug_clipping) {
3027   public void nvgClipDumpOn (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, true); }
3028   public void nvgClipDumpOff (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, false); }
3029 }
3030 
3031 /** Pushes and saves the current render state into a state stack.
3032  * A matching [restore] must be used to restore the state.
3033  * Returns `false` if state stack overflowed.
3034  *
3035  * Group: state_handling
3036  */
3037 public bool save (NVGContext ctx) nothrow @trusted @nogc {
3038   if (ctx.nstates >= NVG_MAX_STATES) return false;
3039   if (ctx.nstates > 0) {
3040     //memcpy(&ctx.states[ctx.nstates], &ctx.states[ctx.nstates-1], NVGstate.sizeof);
3041     ctx.states[ctx.nstates] = ctx.states[ctx.nstates-1];
3042     ctx.params.renderPushClip(ctx.params.userPtr);
3043   }
3044   ++ctx.nstates;
3045   return true;
3046 }
3047 
3048 /// Pops and restores current render state.
3049 /// Group: state_handling
3050 public bool restore (NVGContext ctx) nothrow @trusted @nogc {
3051   if (ctx.nstates <= 1) return false;
3052   ctx.states[ctx.nstates-1].clearPaint();
3053   ctx.params.renderPopClip(ctx.params.userPtr);
3054   --ctx.nstates;
3055   return true;
3056 }
3057 
3058 /// Resets current render state to default values. Does not affect the render state stack.
3059 /// Group: state_handling
3060 public void reset (NVGContext ctx) nothrow @trusted @nogc {
3061   NVGstate* state = nvg__getState(ctx);
3062   state.clearPaint();
3063 
3064   nvg__setPaintColor(state.fill, nvgRGBA(255, 255, 255, 255));
3065   nvg__setPaintColor(state.stroke, nvgRGBA(0, 0, 0, 255));
3066   state.compositeOperation = nvg__compositeOperationState(NVGCompositeOperation.SourceOver);
3067   state.shapeAntiAlias = true;
3068   state.strokeWidth = 1.0f;
3069   state.miterLimit = 10.0f;
3070   state.lineCap = NVGLineCap.Butt;
3071   state.lineJoin = NVGLineCap.Miter;
3072   state.alpha = 1.0f;
3073   state.xform.identity;
3074 
3075   state.scissor.extent[] = -1.0f;
3076 
3077   state.fontSize = 16.0f;
3078   state.letterSpacing = 0.0f;
3079   state.lineHeight = 1.0f;
3080   state.fontBlur = 0.0f;
3081   state.textAlign.reset;
3082   state.fontId = 0;
3083   state.evenOddMode = false;
3084   state.dashCount = 0;
3085   state.lastFlattenDashCount = 0;
3086   state.dashStart = 0;
3087   state.firstDashIsGap = false;
3088   state.dasherActive = false;
3089 
3090   ctx.params.renderResetClip(ctx.params.userPtr);
3091 }
3092 
3093 /** Returns `true` if we have any room in state stack.
3094  * It is guaranteed to have at least 32 stack slots.
3095  *
3096  * Group: state_handling
3097  */
3098 public bool canSave (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates < NVG_MAX_STATES); }
3099 
3100 /** Returns `true` if we have any saved state.
3101  *
3102  * Group: state_handling
3103  */
3104 public bool canRestore (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates > 1); }
3105 
3106 /// Returns `true` if rendering is currently blocked.
3107 /// Group: state_handling
3108 public bool renderBlocked (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.recblockdraw : false); }
3109 
3110 /// Blocks/unblocks rendering
3111 /// Group: state_handling
3112 public void renderBlocked (NVGContext ctx, bool v) pure nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null && ctx.contextAlive) ctx.recblockdraw = v; }
3113 
3114 /// Blocks/unblocks rendering; returns previous state.
3115 /// Group: state_handling
3116 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; }
3117 
3118 
3119 // ////////////////////////////////////////////////////////////////////////// //
3120 // Render styles
3121 
3122 /// Sets filling mode to "even-odd".
3123 /// Group: render_styles
3124 public void evenOddFill (NVGContext ctx) nothrow @trusted @nogc {
3125   NVGstate* state = nvg__getState(ctx);
3126   state.evenOddMode = true;
3127 }
3128 
3129 /// Sets filling mode to "non-zero" (this is default mode).
3130 /// Group: render_styles
3131 public void nonZeroFill (NVGContext ctx) nothrow @trusted @nogc {
3132   NVGstate* state = nvg__getState(ctx);
3133   state.evenOddMode = false;
3134 }
3135 
3136 /// Sets whether to draw antialias for [stroke] and [fill]. It's enabled by default.
3137 /// Group: render_styles
3138 public void shapeAntiAlias (NVGContext ctx, bool enabled) {
3139   NVGstate* state = nvg__getState(ctx);
3140   state.shapeAntiAlias = enabled;
3141 }
3142 
3143 /// Sets the stroke width of the stroke style.
3144 /// Group: render_styles
3145 @scriptable
3146 public void strokeWidth (NVGContext ctx, float width) nothrow @trusted @nogc {
3147   NVGstate* state = nvg__getState(ctx);
3148   state.strokeWidth = width;
3149 }
3150 
3151 /// Sets the miter limit of the stroke style. Miter limit controls when a sharp corner is beveled.
3152 /// Group: render_styles
3153 public void miterLimit (NVGContext ctx, float limit) nothrow @trusted @nogc {
3154   NVGstate* state = nvg__getState(ctx);
3155   state.miterLimit = limit;
3156 }
3157 
3158 /// Sets how the end of the line (cap) is drawn,
3159 /// Can be one of: NVGLineCap.Butt (default), NVGLineCap.Round, NVGLineCap.Square.
3160 /// Group: render_styles
3161 public void lineCap (NVGContext ctx, NVGLineCap cap) nothrow @trusted @nogc {
3162   NVGstate* state = nvg__getState(ctx);
3163   state.lineCap = cap;
3164 }
3165 
3166 /// Sets how sharp path corners are drawn.
3167 /// Can be one of NVGLineCap.Miter (default), NVGLineCap.Round, NVGLineCap.Bevel.
3168 /// Group: render_styles
3169 public void lineJoin (NVGContext ctx, NVGLineCap join) nothrow @trusted @nogc {
3170   NVGstate* state = nvg__getState(ctx);
3171   state.lineJoin = join;
3172 }
3173 
3174 /// Sets stroke dashing, using (dash_length, gap_length) pairs.
3175 /// Current limit is 16 pairs.
3176 /// Resets dash start to zero.
3177 /// Group: render_styles
3178 public void setLineDash (NVGContext ctx, const(float)[] dashdata) nothrow @trusted @nogc {
3179   NVGstate* state = nvg__getState(ctx);
3180   state.dashCount = 0;
3181   state.dashStart = 0;
3182   state.firstDashIsGap = false;
3183   if (dashdata.length >= 2) {
3184     bool curFIsGap = true; // trick
3185     foreach (immutable idx, float f; dashdata) {
3186       curFIsGap = !curFIsGap;
3187       if (f < 0.01f) continue; // skip it
3188       if (idx == 0) {
3189         // register first dash
3190         state.firstDashIsGap = curFIsGap;
3191         state.dashes.ptr[state.dashCount++] = f;
3192       } else {
3193         if ((idx&1) != ((state.dashCount&1)^cast(uint)state.firstDashIsGap)) {
3194           // oops, continuation
3195           state.dashes[state.dashCount-1] += f;
3196         } else {
3197           if (state.dashCount == state.dashes.length) break;
3198           state.dashes[state.dashCount++] = f;
3199         }
3200       }
3201     }
3202     if (state.dashCount&1) {
3203       if (state.dashCount == 1) {
3204         state.dashCount = 0;
3205       } else {
3206         assert(state.dashCount < state.dashes.length);
3207         state.dashes[state.dashCount++] = 0;
3208       }
3209     }
3210     // calculate total dash path length
3211     state.totalDashLen = 0;
3212     foreach (float f; state.dashes.ptr[0..state.dashCount]) state.totalDashLen += f;
3213     if (state.totalDashLen < 0.01f) {
3214       state.dashCount = 0; // nothing to do
3215     } else {
3216       if (state.lastFlattenDashCount != 0) state.lastFlattenDashCount = uint.max; // force re-flattening
3217     }
3218   }
3219 }
3220 
3221 public alias lineDash = setLineDash; /// Ditto.
3222 
3223 /// Sets stroke dashing, using (dash_length, gap_length) pairs.
3224 /// Current limit is 16 pairs.
3225 /// Group: render_styles
3226 public void setLineDashStart (NVGContext ctx, in float dashStart) nothrow @trusted @nogc {
3227   NVGstate* state = nvg__getState(ctx);
3228   if (state.lastFlattenDashCount != 0 && state.dashStart != dashStart) {
3229     state.lastFlattenDashCount = uint.max; // force re-flattening
3230   }
3231   state.dashStart = dashStart;
3232 }
3233 
3234 public alias lineDashStart = setLineDashStart; /// Ditto.
3235 
3236 /// Sets the transparency applied to all rendered shapes.
3237 /// Already transparent paths will get proportionally more transparent as well.
3238 /// Group: render_styles
3239 public void globalAlpha (NVGContext ctx, float alpha) nothrow @trusted @nogc {
3240   NVGstate* state = nvg__getState(ctx);
3241   state.alpha = alpha;
3242 }
3243 
3244 private void strokeColor() {}
3245 
3246 static if (NanoVegaHasArsdColor) {
3247 /// Sets current stroke style to a solid color.
3248 /// Group: render_styles
3249 @scriptable
3250 public void strokeColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
3251   NVGstate* state = nvg__getState(ctx);
3252   nvg__setPaintColor(state.stroke, NVGColor(color));
3253 }
3254 }
3255 
3256 /// Sets current stroke style to a solid color.
3257 /// Group: render_styles
3258 public void strokeColor() (NVGContext ctx, const scope auto ref NVGColor color) nothrow @trusted @nogc {
3259   NVGstate* state = nvg__getState(ctx);
3260   nvg__setPaintColor(state.stroke, color);
3261 }
3262 
3263 @scriptable
3264 public void strokePaint(NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc {
3265 	strokePaint(ctx, *paint);
3266 }
3267 
3268 /// Sets current stroke style to a paint, which can be a one of the gradients or a pattern.
3269 /// Group: render_styles
3270 @scriptable
3271 public void strokePaint() (NVGContext ctx, const scope auto ref NVGPaint paint) nothrow @trusted @nogc {
3272   NVGstate* state = nvg__getState(ctx);
3273   state.stroke = paint;
3274   //nvgTransformMultiply(state.stroke.xform[], state.xform[]);
3275   state.stroke.xform.mul(state.xform);
3276 }
3277 
3278 // this is a hack to work around https://issues.dlang.org/show_bug.cgi?id=16206
3279 // for scriptable reflection. it just needs to be declared first among the overloads
3280 private void fillColor (NVGContext ctx) nothrow @trusted @nogc { }
3281 
3282 static if (NanoVegaHasArsdColor) {
3283 /// Sets current fill style to a solid color.
3284 /// Group: render_styles
3285 @scriptable
3286 public void fillColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
3287   NVGstate* state = nvg__getState(ctx);
3288   nvg__setPaintColor(state.fill, NVGColor(color));
3289 }
3290 }
3291 
3292 /// Sets current fill style to a solid color.
3293 /// Group: render_styles
3294 public void fillColor() (NVGContext ctx, const scope auto ref NVGColor color) nothrow @trusted @nogc {
3295   NVGstate* state = nvg__getState(ctx);
3296   nvg__setPaintColor(state.fill, color);
3297 
3298 }
3299 
3300 @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)
3301 public void fillPaint (NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc {
3302 	fillPaint(ctx, *paint);
3303 }
3304 
3305 /// Sets current fill style to a paint, which can be a one of the gradients or a pattern.
3306 /// Group: render_styles
3307 @scriptable
3308 public void fillPaint() (NVGContext ctx, const scope auto ref NVGPaint paint) nothrow @trusted @nogc {
3309   NVGstate* state = nvg__getState(ctx);
3310   state.fill = paint;
3311   //nvgTransformMultiply(state.fill.xform[], state.xform[]);
3312   state.fill.xform.mul(state.xform);
3313 }
3314 
3315 /// Sets current fill style to a multistop linear gradient.
3316 /// Group: render_styles
3317 public void fillPaint() (NVGContext ctx, const scope auto ref NVGLGS lgs) nothrow @trusted @nogc {
3318   if (!lgs.valid) {
3319     NVGPaint p = void;
3320     memset(&p, 0, p.sizeof);
3321     nvg__setPaintColor(p, NVGColor.red);
3322     ctx.fillPaint = p;
3323   } else if (lgs.midp >= -1) {
3324     //{ import core.stdc.stdio; printf("SIMPLE! midp=%f\n", cast(double)lgs.midp); }
3325     ctx.fillPaint = ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
3326   } else {
3327     ctx.fillPaint = ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
3328   }
3329 }
3330 
3331 /// Returns current transformation matrix.
3332 /// Group: render_transformations
3333 public NVGMatrix currTransform (NVGContext ctx) pure nothrow @trusted @nogc {
3334   NVGstate* state = nvg__getState(ctx);
3335   return state.xform;
3336 }
3337 
3338 /// Sets current transformation matrix.
3339 /// Group: render_transformations
3340 public void currTransform() (NVGContext ctx, const scope auto ref NVGMatrix m) nothrow @trusted @nogc {
3341   NVGstate* state = nvg__getState(ctx);
3342   state.xform = m;
3343 }
3344 
3345 /// Resets current transform to an identity matrix.
3346 /// Group: render_transformations
3347 @scriptable
3348 public void resetTransform (NVGContext ctx) nothrow @trusted @nogc {
3349   NVGstate* state = nvg__getState(ctx);
3350   state.xform.identity;
3351 }
3352 
3353 /// Premultiplies current coordinate system by specified matrix.
3354 /// Group: render_transformations
3355 public void transform() (NVGContext ctx, const scope auto ref NVGMatrix mt) nothrow @trusted @nogc {
3356   NVGstate* state = nvg__getState(ctx);
3357   //nvgTransformPremultiply(state.xform[], t[]);
3358   state.xform *= mt;
3359 }
3360 
3361 /// Translates current coordinate system.
3362 /// Group: render_transformations
3363 @scriptable
3364 public void translate (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
3365   NVGstate* state = nvg__getState(ctx);
3366   //NVGMatrix t = void;
3367   //nvgTransformTranslate(t[], x, y);
3368   //nvgTransformPremultiply(state.xform[], t[]);
3369   state.xform.premul(NVGMatrix.Translated(x, y));
3370 }
3371 
3372 /// Rotates current coordinate system. Angle is specified in radians.
3373 /// Group: render_transformations
3374 @scriptable
3375 public void rotate (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3376   NVGstate* state = nvg__getState(ctx);
3377   //NVGMatrix t = void;
3378   //nvgTransformRotate(t[], angle);
3379   //nvgTransformPremultiply(state.xform[], t[]);
3380   state.xform.premul(NVGMatrix.Rotated(angle));
3381 }
3382 
3383 /// Skews the current coordinate system along X axis. Angle is specified in radians.
3384 /// Group: render_transformations
3385 @scriptable
3386 public void skewX (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3387   NVGstate* state = nvg__getState(ctx);
3388   //NVGMatrix t = void;
3389   //nvgTransformSkewX(t[], angle);
3390   //nvgTransformPremultiply(state.xform[], t[]);
3391   state.xform.premul(NVGMatrix.SkewedX(angle));
3392 }
3393 
3394 /// Skews the current coordinate system along Y axis. Angle is specified in radians.
3395 /// Group: render_transformations
3396 @scriptable
3397 public void skewY (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3398   NVGstate* state = nvg__getState(ctx);
3399   //NVGMatrix t = void;
3400   //nvgTransformSkewY(t[], angle);
3401   //nvgTransformPremultiply(state.xform[], t[]);
3402   state.xform.premul(NVGMatrix.SkewedY(angle));
3403 }
3404 
3405 /// Scales the current coordinate system.
3406 /// Group: render_transformations
3407 @scriptable
3408 public void scale (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
3409   NVGstate* state = nvg__getState(ctx);
3410   //NVGMatrix t = void;
3411   //nvgTransformScale(t[], x, y);
3412   //nvgTransformPremultiply(state.xform[], t[]);
3413   state.xform.premul(NVGMatrix.Scaled(x, y));
3414 }
3415 
3416 
3417 // ////////////////////////////////////////////////////////////////////////// //
3418 // Images
3419 
3420 /// Creates image by loading it from the disk from specified file name.
3421 /// Returns handle to the image or 0 on error.
3422 /// Group: images
3423 public NVGImage createImage() (NVGContext ctx, const(char)[] filename, const(NVGImageFlag)[] imageFlagsList...) {
3424   static if (NanoVegaHasArsdImage) {
3425     import arsd.image;
3426     // do we have new arsd API to load images?
3427     static if (!is(typeof(MemoryImage.fromImageFile)) || !is(typeof(MemoryImage.clearInternal))) {
3428       static assert(0, "Sorry, your ARSD is too old. Please, update it.");
3429     }
3430     try {
3431       auto oimg = MemoryImage.fromImageFile(filename);
3432       if (auto img = cast(TrueColorImage)oimg) {
3433         scope(exit) oimg.clearInternal();
3434         return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList);
3435       } else {
3436         TrueColorImage img = oimg.getAsTrueColorImage;
3437         scope(exit) img.clearInternal();
3438         oimg.clearInternal(); // drop original image, as `getAsTrueColorImage()` MUST create a new one here
3439         oimg = null;
3440         return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList);
3441       }
3442     } catch (Exception) {}
3443     return NVGImage.init;
3444   } else {
3445     import std.internal.cstring;
3446     ubyte* img;
3447     int w, h, n;
3448     stbi_set_unpremultiply_on_load(1);
3449     stbi_convert_iphone_png_to_rgb(1);
3450     img = stbi_load(filename.tempCString, &w, &h, &n, 4);
3451     if (img is null) {
3452       //printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
3453       return NVGImage.init;
3454     }
3455     auto image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList);
3456     stbi_image_free(img);
3457     return image;
3458   }
3459 }
3460 
3461 static if (NanoVegaHasArsdImage) {
3462   /// Creates image by loading it from the specified memory image.
3463   /// Returns handle to the image or 0 on error.
3464   /// Group: images
3465   public NVGImage createImageFromMemoryImage() (NVGContext ctx, MemoryImage img, const(NVGImageFlag)[] imageFlagsList...) {
3466     if (img is null) return NVGImage.init;
3467     if (auto tc = cast(TrueColorImage)img) {
3468       return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList);
3469     } else {
3470       auto tc = img.getAsTrueColorImage;
3471       scope(exit) tc.clearInternal(); // here, it is guaranteed that `tc` is newly allocated image, so it is safe to kill it
3472       return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList);
3473     }
3474   }
3475 } else {
3476   /// Creates image by loading it from the specified chunk of memory.
3477   /// Returns handle to the image or 0 on error.
3478   /// Group: images
3479   public NVGImage createImageMem() (NVGContext ctx, const(ubyte)* data, int ndata, const(NVGImageFlag)[] imageFlagsList...) {
3480     int w, h, n, image;
3481     ubyte* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4);
3482     if (img is null) {
3483       //printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
3484       return NVGImage.init;
3485     }
3486     image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList);
3487     stbi_image_free(img);
3488     return image;
3489   }
3490 }
3491 
3492 /// Creates image from specified image data.
3493 /// Returns handle to the image or 0 on error.
3494 /// Group: images
3495 public NVGImage createImageRGBA (NVGContext ctx, int w, int h, const(void)[] data, const(NVGImageFlag)[] imageFlagsList...) nothrow @trusted @nogc {
3496   if (w < 1 || h < 1 || data.length < w*h*4) return NVGImage.init;
3497   uint imageFlags = 0;
3498   foreach (immutable uint flag; imageFlagsList) imageFlags |= flag;
3499   NVGImage res;
3500   res.id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.RGBA, w, h, imageFlags, cast(const(ubyte)*)data.ptr);
3501   if (res.id > 0) {
3502     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); }
3503     res.ctx = ctx;
3504     ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount
3505   }
3506   return res;
3507 }
3508 
3509 /// Updates image data specified by image handle.
3510 /// Group: images
3511 public void updateImage() (NVGContext ctx, auto ref NVGImage image, const(void)[] data) nothrow @trusted @nogc {
3512   if (image.valid) {
3513     int w, h;
3514     if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3515     ctx.params.renderGetTextureSize(ctx.params.userPtr, image.id, &w, &h);
3516     ctx.params.renderUpdateTexture(ctx.params.userPtr, image.id, 0, 0, w, h, cast(const(ubyte)*)data.ptr);
3517   }
3518 }
3519 
3520 /// Returns the dimensions of a created image.
3521 /// Group: images
3522 public void imageSize() (NVGContext ctx, const scope auto ref NVGImage image, out int w, out int h) nothrow @trusted @nogc {
3523   if (image.valid) {
3524     if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3525     ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, image.id, &w, &h);
3526   }
3527 }
3528 
3529 /// Deletes created image.
3530 /// Group: images
3531 public void deleteImage() (NVGContext ctx, ref NVGImage image) nothrow @trusted @nogc {
3532   if (ctx is null || !image.valid) return;
3533   if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3534   image.clear();
3535 }
3536 
3537 
3538 // ////////////////////////////////////////////////////////////////////////// //
3539 // Paints
3540 
3541 private void linearGradient() {} // hack for dmd bug
3542 
3543 static if (NanoVegaHasArsdColor) {
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 @scriptable
3551 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 {
3552   return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), NVGColor(ocol));
3553 }
3554 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
3555  * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
3556  * range `(0..1)`, and ocol the end color.
3557  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3558  *
3559  * Group: paints
3560  */
3561 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 {
3562   return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), midp, NVGColor(mcol), NVGColor(ocol));
3563 }
3564 }
3565 
3566 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates
3567  * of the linear gradient, icol specifies the start color and ocol the end color.
3568  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3569  *
3570  * Group: paints
3571  */
3572 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 {
3573   enum large = 1e5f;
3574 
3575   NVGPaint p = void;
3576   memset(&p, 0, p.sizeof);
3577   p.simpleColor = false;
3578 
3579   // Calculate transform aligned to the line
3580   float dx = ex-sx;
3581   float dy = ey-sy;
3582   immutable float d = nvg__sqrtf(dx*dx+dy*dy);
3583   if (d > 0.0001f) {
3584     dx /= d;
3585     dy /= d;
3586   } else {
3587     dx = 0;
3588     dy = 1;
3589   }
3590 
3591   p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx;
3592   p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy;
3593   p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large;
3594 
3595   p.extent.ptr[0] = large;
3596   p.extent.ptr[1] = large+d*0.5f;
3597 
3598   p.radius = 0.0f;
3599 
3600   p.feather = nvg__max(NVG_MIN_FEATHER, d);
3601 
3602   p.innerColor = p.middleColor = icol;
3603   p.outerColor = ocol;
3604   p.midp = -1;
3605 
3606   return p;
3607 }
3608 
3609 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
3610  * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
3611  * range `(0..1)`, and ocol the end color.
3612  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3613  *
3614  * Group: paints
3615  */
3616 public NVGPaint 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 {
3617   enum large = 1e5f;
3618 
3619   NVGPaint p = void;
3620   memset(&p, 0, p.sizeof);
3621   p.simpleColor = false;
3622 
3623   // Calculate transform aligned to the line
3624   float dx = ex-sx;
3625   float dy = ey-sy;
3626   immutable float d = nvg__sqrtf(dx*dx+dy*dy);
3627   if (d > 0.0001f) {
3628     dx /= d;
3629     dy /= d;
3630   } else {
3631     dx = 0;
3632     dy = 1;
3633   }
3634 
3635   p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx;
3636   p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy;
3637   p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large;
3638 
3639   p.extent.ptr[0] = large;
3640   p.extent.ptr[1] = large+d*0.5f;
3641 
3642   p.radius = 0.0f;
3643 
3644   p.feather = nvg__max(NVG_MIN_FEATHER, d);
3645 
3646   if (midp <= 0) {
3647     p.innerColor = p.middleColor = mcol;
3648     p.midp = -1;
3649   } else if (midp > 1) {
3650     p.innerColor = p.middleColor = icol;
3651     p.midp = -1;
3652   } else {
3653     p.innerColor = icol;
3654     p.middleColor = mcol;
3655     p.midp = midp;
3656   }
3657   p.outerColor = ocol;
3658 
3659   return p;
3660 }
3661 
3662 static if (NanoVegaHasArsdColor) {
3663 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify
3664  * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
3665  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3666  *
3667  * Group: paints
3668  */
3669 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 {
3670   return ctx.radialGradient(cx, cy, inr, outr, NVGColor(icol), NVGColor(ocol));
3671 }
3672 }
3673 
3674 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify
3675  * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
3676  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3677  *
3678  * Group: paints
3679  */
3680 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 {
3681   immutable float r = (inr+outr)*0.5f;
3682   immutable float f = (outr-inr);
3683 
3684   NVGPaint p = void;
3685   memset(&p, 0, p.sizeof);
3686   p.simpleColor = false;
3687 
3688   p.xform.identity;
3689   p.xform.mat.ptr[4] = cx;
3690   p.xform.mat.ptr[5] = cy;
3691 
3692   p.extent.ptr[0] = r;
3693   p.extent.ptr[1] = r;
3694 
3695   p.radius = r;
3696 
3697   p.feather = nvg__max(NVG_MIN_FEATHER, f);
3698 
3699   p.innerColor = p.middleColor = icol;
3700   p.outerColor = ocol;
3701   p.midp = -1;
3702 
3703   return p;
3704 }
3705 
3706 static if (NanoVegaHasArsdColor) {
3707 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
3708  * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle,
3709  * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
3710  * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
3711  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3712  *
3713  * Group: paints
3714  */
3715 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 {
3716   return ctx.boxGradient(x, y, w, h, r, f, NVGColor(icol), NVGColor(ocol));
3717 }
3718 }
3719 
3720 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
3721  * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle,
3722  * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
3723  * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
3724  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3725  *
3726  * Group: paints
3727  */
3728 public NVGPaint 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 {
3729   NVGPaint p = void;
3730   memset(&p, 0, p.sizeof);
3731   p.simpleColor = false;
3732 
3733   p.xform.identity;
3734   p.xform.mat.ptr[4] = x+w*0.5f;
3735   p.xform.mat.ptr[5] = y+h*0.5f;
3736 
3737   p.extent.ptr[0] = w*0.5f;
3738   p.extent.ptr[1] = h*0.5f;
3739 
3740   p.radius = r;
3741 
3742   p.feather = nvg__max(NVG_MIN_FEATHER, f);
3743 
3744   p.innerColor = p.middleColor = icol;
3745   p.outerColor = ocol;
3746   p.midp = -1;
3747 
3748   return p;
3749 }
3750 
3751 /** Creates and returns an image pattern. Parameters `(cx, cy)` specify the left-top location of the image pattern,
3752  * `(w, h)` the size of one image, [angle] rotation around the top-left corner, [image] is handle to the image to render.
3753  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3754  *
3755  * Group: paints
3756  */
3757 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 {
3758   NVGPaint p = void;
3759   memset(&p, 0, p.sizeof);
3760   p.simpleColor = false;
3761 
3762   p.xform.identity.rotate(angle);
3763   p.xform.mat.ptr[4] = cx;
3764   p.xform.mat.ptr[5] = cy;
3765 
3766   p.extent.ptr[0] = w;
3767   p.extent.ptr[1] = h;
3768 
3769   p.image = image;
3770 
3771   p.innerColor = p.middleColor = p.outerColor = nvgRGBAf(1, 1, 1, alpha);
3772   p.midp = -1;
3773 
3774   return p;
3775 }
3776 
3777 /// Linear gradient with multiple stops.
3778 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3779 /// Group: paints
3780 public struct NVGLGS {
3781 private:
3782   NVGColor ic, mc, oc; // inner, middle, out
3783   float midp;
3784   NVGImage imgid;
3785   // [imagePattern] arguments
3786   float cx, cy, dimx, dimy; // dimx and dimy are ex and ey for simple gradients
3787   public float angle; ///
3788 
3789 public:
3790   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (imgid.valid || midp >= -1); } ///
3791   void clear ()  nothrow @safe @nogc { pragma(inline, true); imgid.clear(); midp = float.nan; } ///
3792 }
3793 
3794 /** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops].
3795  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3796  *
3797  * $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3798  * Group: paints
3799  */
3800 public NVGPaint asPaint() (NVGContext ctx, const scope auto ref NVGLGS lgs) nothrow @trusted @nogc {
3801   if (!lgs.valid) {
3802     NVGPaint p = void;
3803     memset(&p, 0, p.sizeof);
3804     nvg__setPaintColor(p, NVGColor.red);
3805     return p;
3806   } else if (lgs.midp >= -1) {
3807     return ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
3808   } else {
3809     return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
3810   }
3811 }
3812 
3813 /// Gradient Stop Point.
3814 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3815 /// Group: paints
3816 public struct NVGGradientStop {
3817   float offset = 0; /// [0..1]
3818   NVGColor color; ///
3819 
3820   this() (in float aofs, const scope auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } ///
3821   static if (NanoVegaHasArsdColor) {
3822     this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } ///
3823   }
3824 }
3825 
3826 /// Create linear gradient data suitable to use with `linearGradient(res)`.
3827 /// Don't forget to destroy the result when you don't need it anymore with `ctx.kill(res);`.
3828 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3829 /// Group: paints
3830 public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, const(NVGGradientStop)[] stops...) nothrow @trusted @nogc {
3831   // based on the code by Jorge Acereda <jacereda@gmail.com>
3832   enum NVG_GRADIENT_SAMPLES = 1024;
3833   static void gradientSpan (uint* dst, const(NVGGradientStop)* s0, const(NVGGradientStop)* s1) nothrow @trusted @nogc {
3834     immutable float s0o = nvg__clamp(s0.offset, 0.0f, 1.0f);
3835     immutable float s1o = nvg__clamp(s1.offset, 0.0f, 1.0f);
3836     uint s = cast(uint)(s0o*NVG_GRADIENT_SAMPLES);
3837     uint e = cast(uint)(s1o*NVG_GRADIENT_SAMPLES);
3838     uint sc = 0xffffffffU;
3839     uint sh = 24;
3840     uint r = cast(uint)(s0.color.rgba[0]*sc);
3841     uint g = cast(uint)(s0.color.rgba[1]*sc);
3842     uint b = cast(uint)(s0.color.rgba[2]*sc);
3843     uint a = cast(uint)(s0.color.rgba[3]*sc);
3844     uint dr = cast(uint)((s1.color.rgba[0]*sc-r)/(e-s));
3845     uint dg = cast(uint)((s1.color.rgba[1]*sc-g)/(e-s));
3846     uint db = cast(uint)((s1.color.rgba[2]*sc-b)/(e-s));
3847     uint da = cast(uint)((s1.color.rgba[3]*sc-a)/(e-s));
3848     dst += s;
3849     foreach (immutable _; s..e) {
3850       version(BigEndian) {
3851         *dst++ = ((r>>sh)<<24)+((g>>sh)<<16)+((b>>sh)<<8)+((a>>sh)<<0);
3852       } else {
3853         *dst++ = ((a>>sh)<<24)+((b>>sh)<<16)+((g>>sh)<<8)+((r>>sh)<<0);
3854       }
3855       r += dr;
3856       g += dg;
3857       b += db;
3858       a += da;
3859     }
3860   }
3861 
3862   NVGLGS res;
3863   res.cx = sx;
3864   res.cy = sy;
3865 
3866   if (stops.length == 2 && stops.ptr[0].offset <= 0 && stops.ptr[1].offset >= 1) {
3867     // create simple linear gradient
3868     res.ic = res.mc = stops.ptr[0].color;
3869     res.oc = stops.ptr[1].color;
3870     res.midp = -1;
3871     res.dimx = ex;
3872     res.dimy = ey;
3873   } else if (stops.length == 3 && stops.ptr[0].offset <= 0 && stops.ptr[2].offset >= 1) {
3874     // create simple linear gradient with middle stop
3875     res.ic = stops.ptr[0].color;
3876     res.mc = stops.ptr[1].color;
3877     res.oc = stops.ptr[2].color;
3878     res.midp = stops.ptr[1].offset;
3879     res.dimx = ex;
3880     res.dimy = ey;
3881   } else {
3882     // create image gradient
3883     uint[NVG_GRADIENT_SAMPLES] data = void;
3884     immutable float w = ex-sx;
3885     immutable float h = ey-sy;
3886     res.dimx = nvg__sqrtf(w*w+h*h);
3887     res.dimy = 1; //???
3888 
3889     res.angle =
3890       (/*nvg__absf(h) < 0.0001 ? 0 :
3891        nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/
3892        nvg__atan2f(h/*ey-sy*/, w/*ex-sx*/));
3893 
3894     if (stops.length > 0) {
3895       auto s0 = NVGGradientStop(0, nvgRGBAf(0, 0, 0, 1));
3896       auto s1 = NVGGradientStop(1, nvgRGBAf(1, 1, 1, 1));
3897       if (stops.length > 64) stops = stops[0..64];
3898       if (stops.length) {
3899         s0.color = stops[0].color;
3900         s1.color = stops[$-1].color;
3901       }
3902       gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1));
3903       foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1);
3904       gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1);
3905       res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[]/*, NVGImageFlag.RepeatX, NVGImageFlag.RepeatY*/);
3906     }
3907   }
3908   return res;
3909 }
3910 
3911 
3912 // ////////////////////////////////////////////////////////////////////////// //
3913 // Scissoring
3914 
3915 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform.
3916 /// Group: scissoring
3917 public void scissor (NVGContext ctx, in float x, in float y, float w, float h) nothrow @trusted @nogc {
3918   NVGstate* state = nvg__getState(ctx);
3919 
3920   w = nvg__max(0.0f, w);
3921   h = nvg__max(0.0f, h);
3922 
3923   state.scissor.xform.identity;
3924   state.scissor.xform.mat.ptr[4] = x+w*0.5f;
3925   state.scissor.xform.mat.ptr[5] = y+h*0.5f;
3926   //nvgTransformMultiply(state.scissor.xform[], state.xform[]);
3927   state.scissor.xform.mul(state.xform);
3928 
3929   state.scissor.extent.ptr[0] = w*0.5f;
3930   state.scissor.extent.ptr[1] = h*0.5f;
3931 }
3932 
3933 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform.
3934 /// Arguments: [x, y, w, h]*
3935 /// Group: scissoring
3936 public void scissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
3937   enum ArgC = 4;
3938   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [scissor] call");
3939   if (args.length < ArgC) return;
3940   NVGstate* state = nvg__getState(ctx);
3941   const(float)* aptr = args.ptr;
3942   foreach (immutable idx; 0..args.length/ArgC) {
3943     immutable x = *aptr++;
3944     immutable y = *aptr++;
3945     immutable w = nvg__max(0.0f, *aptr++);
3946     immutable h = nvg__max(0.0f, *aptr++);
3947 
3948     state.scissor.xform.identity;
3949     state.scissor.xform.mat.ptr[4] = x+w*0.5f;
3950     state.scissor.xform.mat.ptr[5] = y+h*0.5f;
3951     //nvgTransformMultiply(state.scissor.xform[], state.xform[]);
3952     state.scissor.xform.mul(state.xform);
3953 
3954     state.scissor.extent.ptr[0] = w*0.5f;
3955     state.scissor.extent.ptr[1] = h*0.5f;
3956   }
3957 }
3958 
3959 void nvg__isectRects (float* dst, float ax, float ay, float aw, float ah, float bx, float by, float bw, float bh) nothrow @trusted @nogc {
3960   immutable float minx = nvg__max(ax, bx);
3961   immutable float miny = nvg__max(ay, by);
3962   immutable float maxx = nvg__min(ax+aw, bx+bw);
3963   immutable float maxy = nvg__min(ay+ah, by+bh);
3964   dst[0] = minx;
3965   dst[1] = miny;
3966   dst[2] = nvg__max(0.0f, maxx-minx);
3967   dst[3] = nvg__max(0.0f, maxy-miny);
3968 }
3969 
3970 /** Intersects current scissor rectangle with the specified rectangle.
3971  * The scissor rectangle is transformed by the current transform.
3972  * Note: in case the rotation of previous scissor rect differs from
3973  * the current one, the intersection will be done between the specified
3974  * rectangle and the previous scissor rectangle transformed in the current
3975  * transform space. The resulting shape is always rectangle.
3976  *
3977  * Group: scissoring
3978  */
3979 public void intersectScissor (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc {
3980   NVGstate* state = nvg__getState(ctx);
3981 
3982   // If no previous scissor has been set, set the scissor as current scissor.
3983   if (state.scissor.extent.ptr[0] < 0) {
3984     ctx.scissor(x, y, w, h);
3985     return;
3986   }
3987 
3988   NVGMatrix pxform = void;
3989   NVGMatrix invxorm = void;
3990   float[4] rect = void;
3991 
3992   // Transform the current scissor rect into current transform space.
3993   // If there is difference in rotation, this will be approximation.
3994   //memcpy(pxform.mat.ptr, state.scissor.xform.ptr, float.sizeof*6);
3995   pxform = state.scissor.xform;
3996   immutable float ex = state.scissor.extent.ptr[0];
3997   immutable float ey = state.scissor.extent.ptr[1];
3998   //nvgTransformInverse(invxorm[], state.xform[]);
3999   invxorm = state.xform.inverted;
4000   //nvgTransformMultiply(pxform[], invxorm[]);
4001   pxform.mul(invxorm);
4002   immutable float tex = ex*nvg__absf(pxform.mat.ptr[0])+ey*nvg__absf(pxform.mat.ptr[2]);
4003   immutable float tey = ex*nvg__absf(pxform.mat.ptr[1])+ey*nvg__absf(pxform.mat.ptr[3]);
4004 
4005   // Intersect rects.
4006   nvg__isectRects(rect.ptr, pxform.mat.ptr[4]-tex, pxform.mat.ptr[5]-tey, tex*2, tey*2, x, y, w, h);
4007 
4008   //ctx.scissor(rect.ptr[0], rect.ptr[1], rect.ptr[2], rect.ptr[3]);
4009   ctx.scissor(rect.ptr[0..4]);
4010 }
4011 
4012 /** Intersects current scissor rectangle with the specified rectangle.
4013  * The scissor rectangle is transformed by the current transform.
4014  * Note: in case the rotation of previous scissor rect differs from
4015  * the current one, the intersection will be done between the specified
4016  * rectangle and the previous scissor rectangle transformed in the current
4017  * transform space. The resulting shape is always rectangle.
4018  *
4019  * Arguments: [x, y, w, h]*
4020  *
4021  * Group: scissoring
4022  */
4023 public void intersectScissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
4024   enum ArgC = 4;
4025   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [intersectScissor] call");
4026   if (args.length < ArgC) return;
4027   const(float)* aptr = args.ptr;
4028   foreach (immutable idx; 0..args.length/ArgC) {
4029     immutable x = *aptr++;
4030     immutable y = *aptr++;
4031     immutable w = *aptr++;
4032     immutable h = *aptr++;
4033     ctx.intersectScissor(x, y, w, h);
4034   }
4035 }
4036 
4037 /// Reset and disables scissoring.
4038 /// Group: scissoring
4039 public void resetScissor (NVGContext ctx) nothrow @trusted @nogc {
4040   NVGstate* state = nvg__getState(ctx);
4041   state.scissor.xform.mat[] = 0.0f;
4042   state.scissor.extent[] = -1.0f;
4043 }
4044 
4045 
4046 // ////////////////////////////////////////////////////////////////////////// //
4047 // Render-Time Affine Transformations
4048 
4049 /// Sets GPU affine transformatin matrix. Don't do scaling or skewing here.
4050 /// This matrix won't be saved/restored with context state save/restore operations, as it is not a part of that state.
4051 /// Group: gpu_affine
4052 public void affineGPU() (NVGContext ctx, const scope auto ref NVGMatrix mat) nothrow @trusted @nogc {
4053   ctx.gpuAffine = mat;
4054   ctx.params.renderSetAffine(ctx.params.userPtr, ctx.gpuAffine);
4055 }
4056 
4057 /// Get current GPU affine transformatin matrix.
4058 /// Group: gpu_affine
4059 public NVGMatrix affineGPU (NVGContext ctx) nothrow @safe @nogc {
4060   pragma(inline, true);
4061   return ctx.gpuAffine;
4062 }
4063 
4064 /// "Untransform" point using current GPU affine matrix.
4065 /// Group: gpu_affine
4066 public void gpuUntransformPoint (NVGContext ctx, float *dx, float *dy, in float x, in float y) nothrow @safe @nogc {
4067   if (ctx.gpuAffine.isIdentity) {
4068     if (dx !is null) *dx = x;
4069     if (dy !is null) *dy = y;
4070   } else {
4071     // inverse GPU transformation
4072     NVGMatrix igpu = ctx.gpuAffine.inverted;
4073     igpu.point(dx, dy, x, y);
4074   }
4075 }
4076 
4077 
4078 // ////////////////////////////////////////////////////////////////////////// //
4079 // rasterization (tesselation) code
4080 
4081 int nvg__ptEquals (float x1, float y1, float x2, float y2, float tol) pure nothrow @safe @nogc {
4082   //pragma(inline, true);
4083   immutable float dx = x2-x1;
4084   immutable float dy = y2-y1;
4085   return dx*dx+dy*dy < tol*tol;
4086 }
4087 
4088 float nvg__distPtSeg (float x, float y, float px, float py, float qx, float qy) pure nothrow @safe @nogc {
4089   immutable float pqx = qx-px;
4090   immutable float pqy = qy-py;
4091   float dx = x-px;
4092   float dy = y-py;
4093   immutable float d = pqx*pqx+pqy*pqy;
4094   float t = pqx*dx+pqy*dy;
4095   if (d > 0) t /= d;
4096   if (t < 0) t = 0; else if (t > 1) t = 1;
4097   dx = px+t*pqx-x;
4098   dy = py+t*pqy-y;
4099   return dx*dx+dy*dy;
4100 }
4101 
4102 void nvg__appendCommands(bool useCommand=true) (NVGContext ctx, Command acmd, const(float)[] vals...) nothrow @trusted @nogc {
4103   int nvals = cast(int)vals.length;
4104   static if (useCommand) {
4105     enum addon = 1;
4106   } else {
4107     enum addon = 0;
4108     if (nvals == 0) return; // nothing to do
4109   }
4110 
4111   NVGstate* state = nvg__getState(ctx);
4112 
4113   if (ctx.ncommands+nvals+addon > ctx.ccommands) {
4114     //int ccommands = ctx.ncommands+nvals+ctx.ccommands/2;
4115     int ccommands = ((ctx.ncommands+(nvals+addon))|0xfff)+1;
4116     float* commands = cast(float*)realloc(ctx.commands, float.sizeof*ccommands);
4117     if (commands is null) assert(0, "NanoVega: out of memory");
4118     ctx.commands = commands;
4119     ctx.ccommands = ccommands;
4120     assert(ctx.ncommands+(nvals+addon) <= ctx.ccommands);
4121   }
4122 
4123   static if (!useCommand) acmd = cast(Command)vals.ptr[0];
4124 
4125   if (acmd != Command.Close && acmd != Command.Winding) {
4126     //assert(nvals+addon >= 3);
4127     ctx.commandx = vals.ptr[nvals-2];
4128     ctx.commandy = vals.ptr[nvals-1];
4129   }
4130 
4131   // copy commands
4132   float* vp = ctx.commands+ctx.ncommands;
4133   static if (useCommand) {
4134     vp[0] = cast(float)acmd;
4135     if (nvals > 0) memcpy(vp+1, vals.ptr, nvals*float.sizeof);
4136   } else {
4137     memcpy(vp, vals.ptr, nvals*float.sizeof);
4138   }
4139   ctx.ncommands += nvals+addon;
4140 
4141   // transform commands
4142   int i = nvals+addon;
4143   while (i > 0) {
4144     int nlen = 1;
4145     final switch (cast(Command)(*vp)) {
4146       case Command.MoveTo:
4147       case Command.LineTo:
4148         assert(i >= 3);
4149         state.xform.point(vp+1, vp+2, vp[1], vp[2]);
4150         nlen = 3;
4151         break;
4152       case Command.BezierTo:
4153         assert(i >= 7);
4154         state.xform.point(vp+1, vp+2, vp[1], vp[2]);
4155         state.xform.point(vp+3, vp+4, vp[3], vp[4]);
4156         state.xform.point(vp+5, vp+6, vp[5], vp[6]);
4157         nlen = 7;
4158         break;
4159       case Command.Close:
4160         nlen = 1;
4161         break;
4162       case Command.Winding:
4163         nlen = 2;
4164         break;
4165     }
4166     assert(nlen > 0 && nlen <= i);
4167     i -= nlen;
4168     vp += nlen;
4169   }
4170 }
4171 
4172 void nvg__clearPathCache (NVGContext ctx) nothrow @trusted @nogc {
4173   // no need to clear paths, as data is not copied there
4174   //foreach (ref p; ctx.cache.paths[0..ctx.cache.npaths]) p.clear();
4175   ctx.cache.npoints = 0;
4176   ctx.cache.npaths = 0;
4177   ctx.cache.fillReady = ctx.cache.strokeReady = false;
4178   ctx.cache.clipmode = NVGClipMode.None;
4179 }
4180 
4181 NVGpath* nvg__lastPath (NVGContext ctx) nothrow @trusted @nogc {
4182   return (ctx.cache.npaths > 0 ? &ctx.cache.paths[ctx.cache.npaths-1] : null);
4183 }
4184 
4185 void nvg__addPath (NVGContext ctx) nothrow @trusted @nogc {
4186   import core.stdc.stdlib : realloc;
4187   import core.stdc.string : memset;
4188 
4189   if (ctx.cache.npaths+1 > ctx.cache.cpaths) {
4190     int cpaths = ctx.cache.npaths+1+ctx.cache.cpaths/2;
4191     NVGpath* paths = cast(NVGpath*)realloc(ctx.cache.paths, NVGpath.sizeof*cpaths);
4192     if (paths is null) assert(0, "NanoVega: out of memory");
4193     ctx.cache.paths = paths;
4194     ctx.cache.cpaths = cpaths;
4195   }
4196 
4197   NVGpath* path = &ctx.cache.paths[ctx.cache.npaths++];
4198   memset(path, 0, NVGpath.sizeof);
4199   path.first = ctx.cache.npoints;
4200   path.mWinding = NVGWinding.CCW;
4201 }
4202 
4203 NVGpoint* nvg__lastPoint (NVGContext ctx) nothrow @trusted @nogc {
4204   return (ctx.cache.npoints > 0 ? &ctx.cache.points[ctx.cache.npoints-1] : null);
4205 }
4206 
4207 void nvg__addPoint (NVGContext ctx, float x, float y, int flags) nothrow @trusted @nogc {
4208   NVGpath* path = nvg__lastPath(ctx);
4209   if (path is null) return;
4210 
4211   if (path.count > 0 && ctx.cache.npoints > 0) {
4212     NVGpoint* pt = nvg__lastPoint(ctx);
4213     if (nvg__ptEquals(pt.x, pt.y, x, y, ctx.distTol)) {
4214       pt.flags |= flags;
4215       return;
4216     }
4217   }
4218 
4219   if (ctx.cache.npoints+1 > ctx.cache.cpoints) {
4220     int cpoints = ctx.cache.npoints+1+ctx.cache.cpoints/2;
4221     NVGpoint* points = cast(NVGpoint*)realloc(ctx.cache.points, NVGpoint.sizeof*cpoints);
4222     if (points is null) return;
4223     ctx.cache.points = points;
4224     ctx.cache.cpoints = cpoints;
4225   }
4226 
4227   NVGpoint* pt = &ctx.cache.points[ctx.cache.npoints];
4228   memset(pt, 0, (*pt).sizeof);
4229   pt.x = x;
4230   pt.y = y;
4231   pt.flags = cast(ubyte)flags;
4232 
4233   ++ctx.cache.npoints;
4234   ++path.count;
4235 }
4236 
4237 void nvg__closePath (NVGContext ctx) nothrow @trusted @nogc {
4238   NVGpath* path = nvg__lastPath(ctx);
4239   if (path is null) return;
4240   path.closed = true;
4241 }
4242 
4243 void nvg__pathWinding (NVGContext ctx, NVGWinding winding) nothrow @trusted @nogc {
4244   NVGpath* path = nvg__lastPath(ctx);
4245   if (path is null) return;
4246   path.mWinding = winding;
4247 }
4248 
4249 float nvg__getAverageScale() (const scope auto ref NVGMatrix t) /*pure*/ nothrow @trusted @nogc {
4250   immutable float sx = nvg__sqrtf(t.mat.ptr[0]*t.mat.ptr[0]+t.mat.ptr[2]*t.mat.ptr[2]);
4251   immutable float sy = nvg__sqrtf(t.mat.ptr[1]*t.mat.ptr[1]+t.mat.ptr[3]*t.mat.ptr[3]);
4252   return (sx+sy)*0.5f;
4253 }
4254 
4255 NVGVertex* nvg__allocTempVerts (NVGContext ctx, int nverts) nothrow @trusted @nogc {
4256   if (nverts > ctx.cache.cverts) {
4257     int cverts = (nverts+0xff)&~0xff; // Round up to prevent allocations when things change just slightly.
4258     NVGVertex* verts = cast(NVGVertex*)realloc(ctx.cache.verts, NVGVertex.sizeof*cverts);
4259     if (verts is null) return null;
4260     ctx.cache.verts = verts;
4261     ctx.cache.cverts = cverts;
4262   }
4263 
4264   return ctx.cache.verts;
4265 }
4266 
4267 float nvg__triarea2 (float ax, float ay, float bx, float by, float cx, float cy) pure nothrow @safe @nogc {
4268   immutable float abx = bx-ax;
4269   immutable float aby = by-ay;
4270   immutable float acx = cx-ax;
4271   immutable float acy = cy-ay;
4272   return acx*aby-abx*acy;
4273 }
4274 
4275 float nvg__polyArea (NVGpoint* pts, int npts) nothrow @trusted @nogc {
4276   float area = 0;
4277   foreach (int i; 2..npts) {
4278     NVGpoint* a = &pts[0];
4279     NVGpoint* b = &pts[i-1];
4280     NVGpoint* c = &pts[i];
4281     area += nvg__triarea2(a.x, a.y, b.x, b.y, c.x, c.y);
4282   }
4283   return area*0.5f;
4284 }
4285 
4286 void nvg__polyReverse (NVGpoint* pts, int npts) nothrow @trusted @nogc {
4287   NVGpoint tmp = void;
4288   int i = 0, j = npts-1;
4289   while (i < j) {
4290     tmp = pts[i];
4291     pts[i] = pts[j];
4292     pts[j] = tmp;
4293     ++i;
4294     --j;
4295   }
4296 }
4297 
4298 void nvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc {
4299   vtx.x = x;
4300   vtx.y = y;
4301   vtx.u = u;
4302   vtx.v = v;
4303 }
4304 
4305 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 {
4306   if (level > 10) return;
4307 
4308   // check for collinear points, and use AFD tesselator on such curves (it is WAY faster for this case)
4309   /*
4310   if (level == 0 && ctx.tesselatortype == NVGTesselation.Combined) {
4311     static bool collinear (in float v0x, in float v0y, in float v1x, in float v1y, in float v2x, in float v2y) nothrow @trusted @nogc {
4312       immutable float cz = (v1x-v0x)*(v2y-v0y)-(v2x-v0x)*(v1y-v0y);
4313       return (nvg__absf(cz*cz) <= 0.01f); // arbitrary number, seems to work ok with NanoSVG output
4314     }
4315     if (collinear(x1, y1, x2, y2, x3, y3) && collinear(x2, y2, x3, y3, x3, y4)) {
4316       //{ import core.stdc.stdio; printf("AFD fallback!\n"); }
4317       ctx.nvg__tesselateBezierAFD(x1, y1, x2, y2, x3, y3, x4, y4, type);
4318       return;
4319     }
4320   }
4321   */
4322 
4323   immutable float x12 = (x1+x2)*0.5f;
4324   immutable float y12 = (y1+y2)*0.5f;
4325   immutable float x23 = (x2+x3)*0.5f;
4326   immutable float y23 = (y2+y3)*0.5f;
4327   immutable float x34 = (x3+x4)*0.5f;
4328   immutable float y34 = (y3+y4)*0.5f;
4329   immutable float x123 = (x12+x23)*0.5f;
4330   immutable float y123 = (y12+y23)*0.5f;
4331 
4332   immutable float dx = x4-x1;
4333   immutable float dy = y4-y1;
4334   immutable float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx));
4335   immutable float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx));
4336 
4337   if ((d2+d3)*(d2+d3) < ctx.tessTol*(dx*dx+dy*dy)) {
4338     nvg__addPoint(ctx, x4, y4, type);
4339     return;
4340   }
4341 
4342   immutable float x234 = (x23+x34)*0.5f;
4343   immutable float y234 = (y23+y34)*0.5f;
4344   immutable float x1234 = (x123+x234)*0.5f;
4345   immutable float y1234 = (y123+y234)*0.5f;
4346 
4347   // "taxicab" / "manhattan" check for flat curves
4348   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) {
4349     nvg__addPoint(ctx, x1234, y1234, type);
4350     return;
4351   }
4352 
4353   nvg__tesselateBezier(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
4354   nvg__tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
4355 }
4356 
4357 // based on the ideas and code of Maxim Shemanarev. Rest in Peace, bro!
4358 // see http://www.antigrain.com/research/adaptive_bezier/index.html
4359 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 {
4360   enum CollinearEPS = 0.00000001f; // 0.00001f;
4361   enum AngleTolEPS = 0.01f;
4362 
4363   static float distSquared (in float x1, in float y1, in float x2, in float y2) pure nothrow @safe @nogc {
4364     pragma(inline, true);
4365     immutable float dx = x2-x1;
4366     immutable float dy = y2-y1;
4367     return dx*dx+dy*dy;
4368   }
4369 
4370   if (level == 0) {
4371     nvg__addPoint(ctx, x1, y1, 0);
4372     nvg__tesselateBezierMcSeem(ctx, x1, y1, x2, y2, x3, y3, x4, y4, 1, type);
4373     nvg__addPoint(ctx, x4, y4, type);
4374     return;
4375   }
4376 
4377   if (level >= 32) return; // recurse limit; practically, it should be never reached, but...
4378 
4379   // calculate all the mid-points of the line segments
4380   immutable float x12 = (x1+x2)*0.5f;
4381   immutable float y12 = (y1+y2)*0.5f;
4382   immutable float x23 = (x2+x3)*0.5f;
4383   immutable float y23 = (y2+y3)*0.5f;
4384   immutable float x34 = (x3+x4)*0.5f;
4385   immutable float y34 = (y3+y4)*0.5f;
4386   immutable float x123 = (x12+x23)*0.5f;
4387   immutable float y123 = (y12+y23)*0.5f;
4388   immutable float x234 = (x23+x34)*0.5f;
4389   immutable float y234 = (y23+y34)*0.5f;
4390   immutable float x1234 = (x123+x234)*0.5f;
4391   immutable float y1234 = (y123+y234)*0.5f;
4392 
4393   // try to approximate the full cubic curve by a single straight line
4394   immutable float dx = x4-x1;
4395   immutable float dy = y4-y1;
4396 
4397   float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx));
4398   float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx));
4399   //immutable float da1, da2, k;
4400 
4401   final switch ((cast(int)(d2 > CollinearEPS)<<1)+cast(int)(d3 > CollinearEPS)) {
4402     case 0:
4403       // all collinear or p1 == p4
4404       float k = dx*dx+dy*dy;
4405       if (k == 0) {
4406         d2 = distSquared(x1, y1, x2, y2);
4407         d3 = distSquared(x4, y4, x3, y3);
4408       } else {
4409         k = 1.0f/k;
4410         float da1 = x2-x1;
4411         float da2 = y2-y1;
4412         d2 = k*(da1*dx+da2*dy);
4413         da1 = x3-x1;
4414         da2 = y3-y1;
4415         d3 = k*(da1*dx+da2*dy);
4416         if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) {
4417           // Simple collinear case, 1---2---3---4
4418           // We can leave just two endpoints
4419           return;
4420         }
4421              if (d2 <= 0) d2 = distSquared(x2, y2, x1, y1);
4422         else if (d2 >= 1) d2 = distSquared(x2, y2, x4, y4);
4423         else d2 = distSquared(x2, y2, x1+d2*dx, y1+d2*dy);
4424 
4425              if (d3 <= 0) d3 = distSquared(x3, y3, x1, y1);
4426         else if (d3 >= 1) d3 = distSquared(x3, y3, x4, y4);
4427         else d3 = distSquared(x3, y3, x1+d3*dx, y1+d3*dy);
4428       }
4429       if (d2 > d3) {
4430         if (d2 < ctx.tessTol) {
4431           nvg__addPoint(ctx, x2, y2, type);
4432           return;
4433         }
4434       } if (d3 < ctx.tessTol) {
4435         nvg__addPoint(ctx, x3, y3, type);
4436         return;
4437       }
4438       break;
4439     case 1:
4440       // p1,p2,p4 are collinear, p3 is significant
4441       if (d3*d3 <= ctx.tessTol*(dx*dx+dy*dy)) {
4442         if (ctx.angleTol < AngleTolEPS) {
4443           nvg__addPoint(ctx, x23, y23, type);
4444           return;
4445         } else {
4446           // angle condition
4447           float da1 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-nvg__atan2f(y3-y2, x3-x2));
4448           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4449           if (da1 < ctx.angleTol) {
4450             nvg__addPoint(ctx, x2, y2, type);
4451             nvg__addPoint(ctx, x3, y3, type);
4452             return;
4453           }
4454           if (ctx.cuspLimit != 0.0) {
4455             if (da1 > ctx.cuspLimit) {
4456               nvg__addPoint(ctx, x3, y3, type);
4457               return;
4458             }
4459           }
4460         }
4461       }
4462       break;
4463     case 2:
4464       // p1,p3,p4 are collinear, p2 is significant
4465       if (d2*d2 <= ctx.tessTol*(dx*dx+dy*dy)) {
4466         if (ctx.angleTol < AngleTolEPS) {
4467           nvg__addPoint(ctx, x23, y23, type);
4468           return;
4469         } else {
4470           // angle condition
4471           float da1 = nvg__absf(nvg__atan2f(y3-y2, x3-x2)-nvg__atan2f(y2-y1, x2-x1));
4472           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4473           if (da1 < ctx.angleTol) {
4474             nvg__addPoint(ctx, x2, y2, type);
4475             nvg__addPoint(ctx, x3, y3, type);
4476             return;
4477           }
4478           if (ctx.cuspLimit != 0.0) {
4479             if (da1 > ctx.cuspLimit) {
4480               nvg__addPoint(ctx, x2, y2, type);
4481               return;
4482             }
4483           }
4484         }
4485       }
4486       break;
4487     case 3:
4488       // regular case
4489       if ((d2+d3)*(d2+d3) <= ctx.tessTol*(dx*dx+dy*dy)) {
4490         // if the curvature doesn't exceed the distance tolerance value, we tend to finish subdivisions
4491         if (ctx.angleTol < AngleTolEPS) {
4492           nvg__addPoint(ctx, x23, y23, type);
4493           return;
4494         } else {
4495           // angle and cusp condition
4496           immutable float k = nvg__atan2f(y3-y2, x3-x2);
4497           float da1 = nvg__absf(k-nvg__atan2f(y2-y1, x2-x1));
4498           float da2 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-k);
4499           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4500           if (da2 >= NVG_PI) da2 = 2*NVG_PI-da2;
4501           if (da1+da2 < ctx.angleTol) {
4502             // finally we can stop the recursion
4503             nvg__addPoint(ctx, x23, y23, type);
4504             return;
4505           }
4506           if (ctx.cuspLimit != 0.0) {
4507             if (da1 > ctx.cuspLimit) {
4508               nvg__addPoint(ctx, x2, y2, type);
4509               return;
4510             }
4511             if (da2 > ctx.cuspLimit) {
4512               nvg__addPoint(ctx, x3, y3, type);
4513               return;
4514             }
4515           }
4516         }
4517       }
4518       break;
4519   }
4520 
4521   // continue subdivision
4522   nvg__tesselateBezierMcSeem(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
4523   nvg__tesselateBezierMcSeem(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
4524 }
4525 
4526 
4527 // Adaptive forward differencing for bezier tesselation.
4528 // See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt.
4529 // "Adaptive forward differencing for rendering curves and surfaces."
4530 // ACM SIGGRAPH Computer Graphics. Vol. 21. No. 4. ACM, 1987.
4531 // original code by Taylor Holliday <taylor@audulus.com>
4532 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 {
4533   enum AFD_ONE = (1<<10);
4534 
4535   // power basis
4536   immutable float ax = -x1+3*x2-3*x3+x4;
4537   immutable float ay = -y1+3*y2-3*y3+y4;
4538   immutable float bx = 3*x1-6*x2+3*x3;
4539   immutable float by = 3*y1-6*y2+3*y3;
4540   immutable float cx = -3*x1+3*x2;
4541   immutable float cy = -3*y1+3*y2;
4542 
4543   // Transform to forward difference basis (stepsize 1)
4544   float px = x1;
4545   float py = y1;
4546   float dx = ax+bx+cx;
4547   float dy = ay+by+cy;
4548   float ddx = 6*ax+2*bx;
4549   float ddy = 6*ay+2*by;
4550   float dddx = 6*ax;
4551   float dddy = 6*ay;
4552 
4553   //printf("dx: %f, dy: %f\n", dx, dy);
4554   //printf("ddx: %f, ddy: %f\n", ddx, ddy);
4555   //printf("dddx: %f, dddy: %f\n", dddx, dddy);
4556 
4557   int t = 0;
4558   int dt = AFD_ONE;
4559 
4560   immutable float tol = ctx.tessTol*4;
4561 
4562   while (t < AFD_ONE) {
4563     // Flatness measure.
4564     float d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4565 
4566     // printf("d: %f, th: %f\n", d, th);
4567 
4568     // Go to higher resolution if we're moving a lot or overshooting the end.
4569     while ((d > tol && dt > 1) || (t+dt > AFD_ONE)) {
4570       // printf("up\n");
4571 
4572       // Apply L to the curve. Increase curve resolution.
4573       dx = 0.5f*dx-(1.0f/8.0f)*ddx+(1.0f/16.0f)*dddx;
4574       dy = 0.5f*dy-(1.0f/8.0f)*ddy+(1.0f/16.0f)*dddy;
4575       ddx = (1.0f/4.0f)*ddx-(1.0f/8.0f)*dddx;
4576       ddy = (1.0f/4.0f)*ddy-(1.0f/8.0f)*dddy;
4577       dddx = (1.0f/8.0f)*dddx;
4578       dddy = (1.0f/8.0f)*dddy;
4579 
4580       // Half the stepsize.
4581       dt >>= 1;
4582 
4583       // Recompute d
4584       d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4585     }
4586 
4587     // Go to lower resolution if we're really flat
4588     // and we aren't going to overshoot the end.
4589     // XXX: tol/32 is just a guess for when we are too flat.
4590     while ((d > 0 && d < tol/32.0f && dt < AFD_ONE) && (t+2*dt <= AFD_ONE)) {
4591       // printf("down\n");
4592 
4593       // Apply L^(-1) to the curve. Decrease curve resolution.
4594       dx = 2*dx+ddx;
4595       dy = 2*dy+ddy;
4596       ddx = 4*ddx+4*dddx;
4597       ddy = 4*ddy+4*dddy;
4598       dddx = 8*dddx;
4599       dddy = 8*dddy;
4600 
4601       // Double the stepsize.
4602       dt <<= 1;
4603 
4604       // Recompute d
4605       d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4606     }
4607 
4608     // Forward differencing.
4609     px += dx;
4610     py += dy;
4611     dx += ddx;
4612     dy += ddy;
4613     ddx += dddx;
4614     ddy += dddy;
4615 
4616     // Output a point.
4617     nvg__addPoint(ctx, px, py, (t > 0 ? type : 0));
4618 
4619     // Advance along the curve.
4620     t += dt;
4621 
4622     // Ensure we don't overshoot.
4623     assert(t <= AFD_ONE);
4624   }
4625 }
4626 
4627 
4628 void nvg__dashLastPath (NVGContext ctx) nothrow @trusted @nogc {
4629   import core.stdc.stdlib : realloc;
4630   import core.stdc.string : memcpy;
4631 
4632   NVGpathCache* cache = ctx.cache;
4633   if (cache.npaths == 0) return;
4634 
4635   NVGpath* path = nvg__lastPath(ctx);
4636   if (path is null) return;
4637 
4638   NVGstate* state = nvg__getState(ctx);
4639   if (!state.dasherActive) return;
4640 
4641   static NVGpoint* pts = null;
4642   static uint ptsCount = 0;
4643   static uint ptsSize = 0;
4644 
4645   if (path.count < 2) return; // just in case
4646 
4647   // copy path points (reserve one point for closed pathes)
4648   if (ptsSize < path.count+1) {
4649     ptsSize = cast(uint)(path.count+1);
4650     pts = cast(NVGpoint*)realloc(pts, ptsSize*NVGpoint.sizeof);
4651     if (pts is null) assert(0, "NanoVega: out of memory");
4652   }
4653   ptsCount = cast(uint)path.count;
4654   memcpy(pts, &cache.points[path.first], ptsCount*NVGpoint.sizeof);
4655   // add closing point for closed pathes
4656   if (path.closed && !nvg__ptEquals(pts[0].x, pts[0].y, pts[ptsCount-1].x, pts[ptsCount-1].y, ctx.distTol)) {
4657     pts[ptsCount++] = pts[0];
4658   }
4659 
4660   // remove last path (with its points)
4661   --cache.npaths;
4662   cache.npoints -= path.count;
4663 
4664   // add stroked pathes
4665   const(float)* dashes = state.dashes.ptr;
4666   immutable uint dashCount = state.dashCount;
4667   float currDashStart = 0;
4668   uint currDashIdx = 0;
4669   immutable bool firstIsGap = state.firstDashIsGap;
4670 
4671   // calculate lengthes
4672   {
4673     NVGpoint* v1 = &pts[0];
4674     NVGpoint* v2 = &pts[1];
4675     foreach (immutable _; 0..ptsCount) {
4676       float dx = v2.x-v1.x;
4677       float dy = v2.y-v1.y;
4678       v1.len = nvg__normalize(&dx, &dy);
4679       v1 = v2++;
4680     }
4681   }
4682 
4683   void calcDashStart (float ds) {
4684     if (ds < 0) {
4685       ds = ds%state.totalDashLen;
4686       while (ds < 0) ds += state.totalDashLen;
4687     }
4688     currDashIdx = 0;
4689     currDashStart = 0;
4690     while (ds > 0) {
4691       if (ds > dashes[currDashIdx]) {
4692         ds -= dashes[currDashIdx];
4693         ++currDashIdx;
4694         currDashStart = 0;
4695         if (currDashIdx >= dashCount) currDashIdx = 0;
4696       } else {
4697         currDashStart = ds;
4698         ds = 0;
4699       }
4700     }
4701   }
4702 
4703   calcDashStart(state.dashStart);
4704 
4705   uint srcPointIdx = 1;
4706   const(NVGpoint)* v1 = &pts[0];
4707   const(NVGpoint)* v2 = &pts[1];
4708   float currRest = v1.len;
4709   nvg__addPath(ctx);
4710   nvg__addPoint(ctx, v1.x, v1.y, PointFlag.Corner);
4711 
4712   void fixLastPoint () {
4713     auto lpt = nvg__lastPath(ctx);
4714     if (lpt !is null && lpt.count > 0) {
4715       // fix last point
4716       if (auto lps = nvg__lastPoint(ctx)) lps.flags = PointFlag.Corner;
4717       // fix first point
4718       NVGpathCache* cache = ctx.cache;
4719       cache.points[lpt.first].flags = PointFlag.Corner;
4720     }
4721   }
4722 
4723   for (;;) {
4724     immutable float dlen = dashes[currDashIdx];
4725     if (dlen == 0) {
4726       ++currDashIdx;
4727       if (currDashIdx >= dashCount) currDashIdx = 0;
4728       continue;
4729     }
4730     immutable float dashRest = dlen-currDashStart;
4731     if ((currDashIdx&1) != firstIsGap) {
4732       // this is "moveto" command, so create new path
4733       fixLastPoint();
4734       nvg__addPath(ctx);
4735     }
4736     if (currRest > dashRest) {
4737       currRest -= dashRest;
4738       ++currDashIdx;
4739       if (currDashIdx >= dashCount) currDashIdx = 0;
4740       currDashStart = 0;
4741       nvg__addPoint(ctx,
4742         v2.x-(v2.x-v1.x)*currRest/v1.len,
4743         v2.y-(v2.y-v1.y)*currRest/v1.len,
4744         PointFlag.Corner
4745       );
4746     } else {
4747       currDashStart += currRest;
4748       nvg__addPoint(ctx, v2.x, v2.y, v1.flags); //k8:fix flags here?
4749       ++srcPointIdx;
4750       v1 = v2;
4751       currRest = v1.len;
4752       if (srcPointIdx >= ptsCount) break;
4753       v2 = &pts[srcPointIdx];
4754     }
4755   }
4756   fixLastPoint();
4757 }
4758 
4759 
4760 version(nanovg_bench_flatten) import iv.timer : Timer;
4761 
4762 void nvg__flattenPaths(bool asStroke) (NVGContext ctx) nothrow @trusted @nogc {
4763   version(nanovg_bench_flatten) {
4764     Timer timer;
4765     char[128] tmbuf;
4766     int bzcount;
4767   }
4768   NVGpathCache* cache = ctx.cache;
4769   NVGstate* state = nvg__getState(ctx);
4770 
4771   // check if we already did flattening
4772   static if (asStroke) {
4773     if (state.dashCount >= 2) {
4774       if (cache.npaths > 0 && state.lastFlattenDashCount == state.dashCount) return; // already flattened
4775       state.dasherActive = true;
4776       state.lastFlattenDashCount = state.dashCount;
4777     } else {
4778       if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened
4779       state.dasherActive = false;
4780       state.lastFlattenDashCount = 0;
4781     }
4782   } else {
4783     if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened
4784     state.lastFlattenDashCount = 0; // so next stroke flattening will redo it
4785     state.dasherActive = false;
4786   }
4787 
4788   // clear path cache
4789   cache.npaths = 0;
4790   cache.npoints = 0;
4791 
4792   // flatten
4793   version(nanovg_bench_flatten) timer.restart();
4794   int i = 0;
4795   while (i < ctx.ncommands) {
4796     final switch (cast(Command)ctx.commands[i]) {
4797       case Command.MoveTo:
4798         //assert(i+3 <= ctx.ncommands);
4799         static if (asStroke) {
4800           if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx);
4801         }
4802         nvg__addPath(ctx);
4803         const p = &ctx.commands[i+1];
4804         nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner);
4805         i += 3;
4806         break;
4807       case Command.LineTo:
4808         //assert(i+3 <= ctx.ncommands);
4809         const p = &ctx.commands[i+1];
4810         nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner);
4811         i += 3;
4812         break;
4813       case Command.BezierTo:
4814         //assert(i+7 <= ctx.ncommands);
4815         const last = nvg__lastPoint(ctx);
4816         if (last !is null) {
4817           const cp1 = &ctx.commands[i+1];
4818           const cp2 = &ctx.commands[i+3];
4819           const p = &ctx.commands[i+5];
4820           if (ctx.tesselatortype == NVGTesselation.DeCasteljau) {
4821             nvg__tesselateBezier(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
4822           } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) {
4823             nvg__tesselateBezierMcSeem(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
4824           } else {
4825             nvg__tesselateBezierAFD(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], PointFlag.Corner);
4826           }
4827           version(nanovg_bench_flatten) ++bzcount;
4828         }
4829         i += 7;
4830         break;
4831       case Command.Close:
4832         //assert(i+1 <= ctx.ncommands);
4833         nvg__closePath(ctx);
4834         i += 1;
4835         break;
4836       case Command.Winding:
4837         //assert(i+2 <= ctx.ncommands);
4838         nvg__pathWinding(ctx, cast(NVGWinding)ctx.commands[i+1]);
4839         i += 2;
4840         break;
4841     }
4842   }
4843   static if (asStroke) {
4844     if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx);
4845   }
4846   version(nanovg_bench_flatten) {{
4847     timer.stop();
4848     auto xb = timer.toBuffer(tmbuf[]);
4849     import core.stdc.stdio : printf;
4850     printf("flattening time: [%.*s] (%d beziers)\n", cast(uint)xb.length, xb.ptr, bzcount);
4851   }}
4852 
4853   cache.bounds.ptr[0] = cache.bounds.ptr[1] = float.max;
4854   cache.bounds.ptr[2] = cache.bounds.ptr[3] = -float.max;
4855 
4856   // calculate the direction and length of line segments
4857   version(nanovg_bench_flatten) timer.restart();
4858   foreach (int j; 0..cache.npaths) {
4859     NVGpath* path = &cache.paths[j];
4860     NVGpoint* pts = &cache.points[path.first];
4861 
4862     // if the first and last points are the same, remove the last, mark as closed path
4863     NVGpoint* p0 = &pts[path.count-1];
4864     NVGpoint* p1 = &pts[0];
4865     if (nvg__ptEquals(p0.x, p0.y, p1.x, p1.y, ctx.distTol)) {
4866       --path.count;
4867       p0 = &pts[path.count-1];
4868       path.closed = true;
4869     }
4870 
4871     // enforce winding
4872     if (path.count > 2) {
4873       immutable float area = nvg__polyArea(pts, path.count);
4874       if (path.mWinding == NVGWinding.CCW && area < 0.0f) nvg__polyReverse(pts, path.count);
4875       if (path.mWinding == NVGWinding.CW && area > 0.0f) nvg__polyReverse(pts, path.count);
4876     }
4877 
4878     foreach (immutable _; 0..path.count) {
4879       // calculate segment direction and length
4880       p0.dx = p1.x-p0.x;
4881       p0.dy = p1.y-p0.y;
4882       p0.len = nvg__normalize(&p0.dx, &p0.dy);
4883       // update bounds
4884       cache.bounds.ptr[0] = nvg__min(cache.bounds.ptr[0], p0.x);
4885       cache.bounds.ptr[1] = nvg__min(cache.bounds.ptr[1], p0.y);
4886       cache.bounds.ptr[2] = nvg__max(cache.bounds.ptr[2], p0.x);
4887       cache.bounds.ptr[3] = nvg__max(cache.bounds.ptr[3], p0.y);
4888       // advance
4889       p0 = p1++;
4890     }
4891   }
4892   version(nanovg_bench_flatten) {{
4893     timer.stop();
4894     auto xb = timer.toBuffer(tmbuf[]);
4895     import core.stdc.stdio : printf;
4896     printf("segment calculation time: [%.*s]\n", cast(uint)xb.length, xb.ptr);
4897   }}
4898 }
4899 
4900 int nvg__curveDivs (float r, float arc, float tol) nothrow @trusted @nogc {
4901   immutable float da = nvg__acosf(r/(r+tol))*2.0f;
4902   return nvg__max(2, cast(int)nvg__ceilf(arc/da));
4903 }
4904 
4905 void nvg__chooseBevel (int bevel, NVGpoint* p0, NVGpoint* p1, float w, float* x0, float* y0, float* x1, float* y1) nothrow @trusted @nogc {
4906   if (bevel) {
4907     *x0 = p1.x+p0.dy*w;
4908     *y0 = p1.y-p0.dx*w;
4909     *x1 = p1.x+p1.dy*w;
4910     *y1 = p1.y-p1.dx*w;
4911   } else {
4912     *x0 = p1.x+p1.dmx*w;
4913     *y0 = p1.y+p1.dmy*w;
4914     *x1 = p1.x+p1.dmx*w;
4915     *y1 = p1.y+p1.dmy*w;
4916   }
4917 }
4918 
4919 NVGVertex* nvg__roundJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, int ncap, float fringe) nothrow @trusted @nogc {
4920   float dlx0 = p0.dy;
4921   float dly0 = -p0.dx;
4922   float dlx1 = p1.dy;
4923   float dly1 = -p1.dx;
4924   //NVG_NOTUSED(fringe);
4925 
4926   if (p1.flags&PointFlag.Left) {
4927     float lx0 = void, ly0 = void, lx1 = void, ly1 = void;
4928     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1);
4929     immutable float a0 = nvg__atan2f(-dly0, -dlx0);
4930     float a1 = nvg__atan2f(-dly1, -dlx1);
4931     if (a1 > a0) a1 -= NVG_PI*2;
4932 
4933     nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4934     nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4935 
4936     int n = nvg__clamp(cast(int)nvg__ceilf(((a0-a1)/NVG_PI)*ncap), 2, ncap);
4937     for (int i = 0; i < n; ++i) {
4938       float u = i/cast(float)(n-1);
4939       float a = a0+u*(a1-a0);
4940       float rx = p1.x+nvg__cosf(a)*rw;
4941       float ry = p1.y+nvg__sinf(a)*rw;
4942       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4943       nvg__vset(dst, rx, ry, ru, 1); ++dst;
4944     }
4945 
4946     nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4947     nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4948 
4949   } else {
4950     float rx0 = void, ry0 = void, rx1 = void, ry1 = void;
4951     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1);
4952     immutable float a0 = nvg__atan2f(dly0, dlx0);
4953     float a1 = nvg__atan2f(dly1, dlx1);
4954     if (a1 < a0) a1 += NVG_PI*2;
4955 
4956     nvg__vset(dst, p1.x+dlx0*rw, p1.y+dly0*rw, lu, 1); ++dst;
4957     nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4958 
4959     int n = nvg__clamp(cast(int)nvg__ceilf(((a1-a0)/NVG_PI)*ncap), 2, ncap);
4960     for (int i = 0; i < n; i++) {
4961       float u = i/cast(float)(n-1);
4962       float a = a0+u*(a1-a0);
4963       float lx = p1.x+nvg__cosf(a)*lw;
4964       float ly = p1.y+nvg__sinf(a)*lw;
4965       nvg__vset(dst, lx, ly, lu, 1); ++dst;
4966       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4967     }
4968 
4969     nvg__vset(dst, p1.x+dlx1*rw, p1.y+dly1*rw, lu, 1); ++dst;
4970     nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
4971 
4972   }
4973   return dst;
4974 }
4975 
4976 NVGVertex* nvg__bevelJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, float fringe) nothrow @trusted @nogc {
4977   float rx0, ry0, rx1, ry1;
4978   float lx0, ly0, lx1, ly1;
4979   float dlx0 = p0.dy;
4980   float dly0 = -p0.dx;
4981   float dlx1 = p1.dy;
4982   float dly1 = -p1.dx;
4983   //NVG_NOTUSED(fringe);
4984 
4985   if (p1.flags&PointFlag.Left) {
4986     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1);
4987 
4988     nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4989     nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4990 
4991     if (p1.flags&PointFlag.Bevel) {
4992       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4993       nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4994 
4995       nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4996       nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4997     } else {
4998       rx0 = p1.x-p1.dmx*rw;
4999       ry0 = p1.y-p1.dmy*rw;
5000 
5001       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
5002       nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
5003 
5004       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
5005       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
5006 
5007       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
5008       nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
5009     }
5010 
5011     nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
5012     nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
5013 
5014   } else {
5015     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1);
5016 
5017     nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
5018     nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
5019 
5020     if (p1.flags&PointFlag.Bevel) {
5021       nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
5022       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
5023 
5024       nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
5025       nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
5026     } else {
5027       lx0 = p1.x+p1.dmx*lw;
5028       ly0 = p1.y+p1.dmy*lw;
5029 
5030       nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
5031       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
5032 
5033       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
5034       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
5035 
5036       nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
5037       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
5038     }
5039 
5040     nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
5041     nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
5042   }
5043 
5044   return dst;
5045 }
5046 
5047 NVGVertex* nvg__buttCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc {
5048   immutable float px = p.x-dx*d;
5049   immutable float py = p.y-dy*d;
5050   immutable float dlx = dy;
5051   immutable float dly = -dx;
5052   nvg__vset(dst, px+dlx*w-dx*aa, py+dly*w-dy*aa, 0, 0); ++dst;
5053   nvg__vset(dst, px-dlx*w-dx*aa, py-dly*w-dy*aa, 1, 0); ++dst;
5054   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5055   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5056   return dst;
5057 }
5058 
5059 NVGVertex* nvg__buttCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc {
5060   immutable float px = p.x+dx*d;
5061   immutable float py = p.y+dy*d;
5062   immutable float dlx = dy;
5063   immutable float dly = -dx;
5064   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5065   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5066   nvg__vset(dst, px+dlx*w+dx*aa, py+dly*w+dy*aa, 0, 0); ++dst;
5067   nvg__vset(dst, px-dlx*w+dx*aa, py-dly*w+dy*aa, 1, 0); ++dst;
5068   return dst;
5069 }
5070 
5071 NVGVertex* nvg__roundCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc {
5072   immutable float px = p.x;
5073   immutable float py = p.y;
5074   immutable float dlx = dy;
5075   immutable float dly = -dx;
5076   //NVG_NOTUSED(aa);
5077   immutable float ncpf = cast(float)(ncap-1);
5078   foreach (int i; 0..ncap) {
5079     float a = i/*/cast(float)(ncap-1)*//ncpf*NVG_PI;
5080     float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w;
5081     nvg__vset(dst, px-dlx*ax-dx*ay, py-dly*ax-dy*ay, 0, 1); ++dst;
5082     nvg__vset(dst, px, py, 0.5f, 1); ++dst;
5083   }
5084   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5085   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5086   return dst;
5087 }
5088 
5089 NVGVertex* nvg__roundCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc {
5090   immutable float px = p.x;
5091   immutable float py = p.y;
5092   immutable float dlx = dy;
5093   immutable float dly = -dx;
5094   //NVG_NOTUSED(aa);
5095   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5096   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5097   immutable float ncpf = cast(float)(ncap-1);
5098   foreach (int i; 0..ncap) {
5099     float a = i/*cast(float)(ncap-1)*//ncpf*NVG_PI;
5100     float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w;
5101     nvg__vset(dst, px, py, 0.5f, 1); ++dst;
5102     nvg__vset(dst, px-dlx*ax+dx*ay, py-dly*ax+dy*ay, 0, 1); ++dst;
5103   }
5104   return dst;
5105 }
5106 
5107 void nvg__calculateJoins (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5108   NVGpathCache* cache = ctx.cache;
5109   float iw = 0.0f;
5110 
5111   if (w > 0.0f) iw = 1.0f/w;
5112 
5113   // Calculate which joins needs extra vertices to append, and gather vertex count.
5114   foreach (int i; 0..cache.npaths) {
5115     NVGpath* path = &cache.paths[i];
5116     NVGpoint* pts = &cache.points[path.first];
5117     NVGpoint* p0 = &pts[path.count-1];
5118     NVGpoint* p1 = &pts[0];
5119     int nleft = 0;
5120 
5121     path.nbevel = 0;
5122 
5123     foreach (int j; 0..path.count) {
5124       //float dlx0, dly0, dlx1, dly1, dmr2, cross, limit;
5125       immutable float dlx0 = p0.dy;
5126       immutable float dly0 = -p0.dx;
5127       immutable float dlx1 = p1.dy;
5128       immutable float dly1 = -p1.dx;
5129       // Calculate extrusions
5130       p1.dmx = (dlx0+dlx1)*0.5f;
5131       p1.dmy = (dly0+dly1)*0.5f;
5132       immutable float dmr2 = p1.dmx*p1.dmx+p1.dmy*p1.dmy;
5133       if (dmr2 > 0.000001f) {
5134         float scale = 1.0f/dmr2;
5135         if (scale > 600.0f) scale = 600.0f;
5136         p1.dmx *= scale;
5137         p1.dmy *= scale;
5138       }
5139 
5140       // Clear flags, but keep the corner.
5141       p1.flags = (p1.flags&PointFlag.Corner) ? PointFlag.Corner : 0;
5142 
5143       // Keep track of left turns.
5144       immutable float cross = p1.dx*p0.dy-p0.dx*p1.dy;
5145       if (cross > 0.0f) {
5146         nleft++;
5147         p1.flags |= PointFlag.Left;
5148       }
5149 
5150       // Calculate if we should use bevel or miter for inner join.
5151       immutable float limit = nvg__max(1.01f, nvg__min(p0.len, p1.len)*iw);
5152       if ((dmr2*limit*limit) < 1.0f) p1.flags |= PointFlag.InnerBevelPR;
5153 
5154       // Check to see if the corner needs to be beveled.
5155       if (p1.flags&PointFlag.Corner) {
5156         if ((dmr2*miterLimit*miterLimit) < 1.0f || lineJoin == NVGLineCap.Bevel || lineJoin == NVGLineCap.Round) {
5157           p1.flags |= PointFlag.Bevel;
5158         }
5159       }
5160 
5161       if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) path.nbevel++;
5162 
5163       p0 = p1++;
5164     }
5165 
5166     path.convex = (nleft == path.count);
5167   }
5168 }
5169 
5170 void nvg__expandStroke (NVGContext ctx, float w, int lineCap, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5171   NVGpathCache* cache = ctx.cache;
5172   immutable float aa = ctx.fringeWidth;
5173   int ncap = nvg__curveDivs(w, NVG_PI, ctx.tessTol); // Calculate divisions per half circle.
5174 
5175   nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
5176 
5177   // Calculate max vertex usage.
5178   int cverts = 0;
5179   foreach (int i; 0..cache.npaths) {
5180     NVGpath* path = &cache.paths[i];
5181     immutable bool loop = path.closed;
5182     if (lineJoin == NVGLineCap.Round) {
5183       cverts += (path.count+path.nbevel*(ncap+2)+1)*2; // plus one for loop
5184     } else {
5185       cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop
5186     }
5187     if (!loop) {
5188       // space for caps
5189       if (lineCap == NVGLineCap.Round) {
5190         cverts += (ncap*2+2)*2;
5191       } else {
5192         cverts += (3+3)*2;
5193       }
5194     }
5195   }
5196 
5197   NVGVertex* verts = nvg__allocTempVerts(ctx, cverts);
5198   if (verts is null) return;
5199 
5200   foreach (int i; 0..cache.npaths) {
5201     NVGpath* path = &cache.paths[i];
5202     NVGpoint* pts = &cache.points[path.first];
5203     NVGpoint* p0;
5204     NVGpoint* p1;
5205     int s, e;
5206 
5207     path.fill = null;
5208     path.nfill = 0;
5209 
5210     // Calculate fringe or stroke
5211     immutable bool loop = path.closed;
5212     NVGVertex* dst = verts;
5213     path.stroke = dst;
5214 
5215     if (loop) {
5216       // Looping
5217       p0 = &pts[path.count-1];
5218       p1 = &pts[0];
5219       s = 0;
5220       e = path.count;
5221     } else {
5222       // Add cap
5223       p0 = &pts[0];
5224       p1 = &pts[1];
5225       s = 1;
5226       e = path.count-1;
5227     }
5228 
5229     if (!loop) {
5230       // Add cap
5231       float dx = p1.x-p0.x;
5232       float dy = p1.y-p0.y;
5233       nvg__normalize(&dx, &dy);
5234            if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa);
5235       else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa);
5236       else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa);
5237     }
5238 
5239     foreach (int j; s..e) {
5240       if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) {
5241         if (lineJoin == NVGLineCap.Round) {
5242           dst = nvg__roundJoin(dst, p0, p1, w, w, 0, 1, ncap, aa);
5243         } else {
5244           dst = nvg__bevelJoin(dst, p0, p1, w, w, 0, 1, aa);
5245         }
5246       } else {
5247         nvg__vset(dst, p1.x+(p1.dmx*w), p1.y+(p1.dmy*w), 0, 1); ++dst;
5248         nvg__vset(dst, p1.x-(p1.dmx*w), p1.y-(p1.dmy*w), 1, 1); ++dst;
5249       }
5250       p0 = p1++;
5251     }
5252 
5253     if (loop) {
5254       // Loop it
5255       nvg__vset(dst, verts[0].x, verts[0].y, 0, 1); ++dst;
5256       nvg__vset(dst, verts[1].x, verts[1].y, 1, 1); ++dst;
5257     } else {
5258       // Add cap
5259       float dx = p1.x-p0.x;
5260       float dy = p1.y-p0.y;
5261       nvg__normalize(&dx, &dy);
5262            if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa);
5263       else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa);
5264       else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa);
5265     }
5266 
5267     path.nstroke = cast(int)(dst-verts);
5268 
5269     verts = dst;
5270   }
5271 }
5272 
5273 void nvg__expandFill (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5274   NVGpathCache* cache = ctx.cache;
5275   immutable float aa = ctx.fringeWidth;
5276   bool fringe = (w > 0.0f);
5277 
5278   nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
5279 
5280   // Calculate max vertex usage.
5281   int cverts = 0;
5282   foreach (int i; 0..cache.npaths) {
5283     NVGpath* path = &cache.paths[i];
5284     cverts += path.count+path.nbevel+1;
5285     if (fringe) cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop
5286   }
5287 
5288   NVGVertex* verts = nvg__allocTempVerts(ctx, cverts);
5289   if (verts is null) return;
5290 
5291   bool convex = (cache.npaths == 1 && cache.paths[0].convex);
5292 
5293   foreach (int i; 0..cache.npaths) {
5294     NVGpath* path = &cache.paths[i];
5295     NVGpoint* pts = &cache.points[path.first];
5296 
5297     // Calculate shape vertices.
5298     immutable float woff = 0.5f*aa;
5299     NVGVertex* dst = verts;
5300     path.fill = dst;
5301 
5302     if (fringe) {
5303       // Looping
5304       NVGpoint* p0 = &pts[path.count-1];
5305       NVGpoint* p1 = &pts[0];
5306       foreach (int j; 0..path.count) {
5307         if (p1.flags&PointFlag.Bevel) {
5308           immutable float dlx0 = p0.dy;
5309           immutable float dly0 = -p0.dx;
5310           immutable float dlx1 = p1.dy;
5311           immutable float dly1 = -p1.dx;
5312           if (p1.flags&PointFlag.Left) {
5313             immutable float lx = p1.x+p1.dmx*woff;
5314             immutable float ly = p1.y+p1.dmy*woff;
5315             nvg__vset(dst, lx, ly, 0.5f, 1); ++dst;
5316           } else {
5317             immutable float lx0 = p1.x+dlx0*woff;
5318             immutable float ly0 = p1.y+dly0*woff;
5319             immutable float lx1 = p1.x+dlx1*woff;
5320             immutable float ly1 = p1.y+dly1*woff;
5321             nvg__vset(dst, lx0, ly0, 0.5f, 1); ++dst;
5322             nvg__vset(dst, lx1, ly1, 0.5f, 1); ++dst;
5323           }
5324         } else {
5325           nvg__vset(dst, p1.x+(p1.dmx*woff), p1.y+(p1.dmy*woff), 0.5f, 1); ++dst;
5326         }
5327         p0 = p1++;
5328       }
5329     } else {
5330       foreach (int j; 0..path.count) {
5331         nvg__vset(dst, pts[j].x, pts[j].y, 0.5f, 1);
5332         ++dst;
5333       }
5334     }
5335 
5336     path.nfill = cast(int)(dst-verts);
5337     verts = dst;
5338 
5339     // Calculate fringe
5340     if (fringe) {
5341       float lw = w+woff;
5342       immutable float rw = w-woff;
5343       float lu = 0;
5344       immutable float ru = 1;
5345       dst = verts;
5346       path.stroke = dst;
5347 
5348       // Create only half a fringe for convex shapes so that
5349       // the shape can be rendered without stenciling.
5350       if (convex) {
5351         lw = woff; // This should generate the same vertex as fill inset above.
5352         lu = 0.5f; // Set outline fade at middle.
5353       }
5354 
5355       // Looping
5356       NVGpoint* p0 = &pts[path.count-1];
5357       NVGpoint* p1 = &pts[0];
5358 
5359       foreach (int j; 0..path.count) {
5360         if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) {
5361           dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx.fringeWidth);
5362         } else {
5363           nvg__vset(dst, p1.x+(p1.dmx*lw), p1.y+(p1.dmy*lw), lu, 1); ++dst;
5364           nvg__vset(dst, p1.x-(p1.dmx*rw), p1.y-(p1.dmy*rw), ru, 1); ++dst;
5365         }
5366         p0 = p1++;
5367       }
5368 
5369       // Loop it
5370       nvg__vset(dst, verts[0].x, verts[0].y, lu, 1); ++dst;
5371       nvg__vset(dst, verts[1].x, verts[1].y, ru, 1); ++dst;
5372 
5373       path.nstroke = cast(int)(dst-verts);
5374       verts = dst;
5375     } else {
5376       path.stroke = null;
5377       path.nstroke = 0;
5378     }
5379   }
5380 }
5381 
5382 
5383 // ////////////////////////////////////////////////////////////////////////// //
5384 // Paths
5385 
5386 /// Clears the current path and sub-paths.
5387 /// Group: paths
5388 @scriptable
5389 public void beginPath (NVGContext ctx) nothrow @trusted @nogc {
5390   ctx.ncommands = 0;
5391   ctx.pathPickRegistered &= NVGPickKind.All; // reset "registered" flags
5392   nvg__clearPathCache(ctx);
5393 }
5394 
5395 public alias newPath = beginPath; /// Ditto.
5396 
5397 /// Starts new sub-path with specified point as first point.
5398 /// Group: paths
5399 @scriptable
5400 public void moveTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
5401   nvg__appendCommands(ctx, Command.MoveTo, x, y);
5402 }
5403 
5404 /// Starts new sub-path with specified point as first point.
5405 /// Arguments: [x, y]*
5406 /// Group: paths
5407 public void moveTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5408   enum ArgC = 2;
5409   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [moveTo] call");
5410   if (args.length < ArgC) return;
5411   nvg__appendCommands(ctx, Command.MoveTo, args[$-2..$]);
5412 }
5413 
5414 /// Adds line segment from the last point in the path to the specified point.
5415 /// Group: paths
5416 @scriptable
5417 public void lineTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
5418   nvg__appendCommands(ctx, Command.LineTo, x, y);
5419 }
5420 
5421 /// Adds line segment from the last point in the path to the specified point.
5422 /// Arguments: [x, y]*
5423 /// Group: paths
5424 public void lineTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5425   enum ArgC = 2;
5426   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [lineTo] call");
5427   if (args.length < ArgC) return;
5428   foreach (immutable idx; 0..args.length/ArgC) {
5429     nvg__appendCommands(ctx, Command.LineTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]);
5430   }
5431 }
5432 
5433 /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
5434 /// Group: paths
5435 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 {
5436   nvg__appendCommands(ctx, Command.BezierTo, c1x, c1y, c2x, c2y, x, y);
5437 }
5438 
5439 /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
5440 /// Arguments: [c1x, c1y, c2x, c2y, x, y]*
5441 /// Group: paths
5442 public void bezierTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5443   enum ArgC = 6;
5444   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [bezierTo] call");
5445   if (args.length < ArgC) return;
5446   foreach (immutable idx; 0..args.length/ArgC) {
5447     nvg__appendCommands(ctx, Command.BezierTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]);
5448   }
5449 }
5450 
5451 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
5452 /// Group: paths
5453 public void quadTo (NVGContext ctx, in float cx, in float cy, in float x, in float y) nothrow @trusted @nogc {
5454   immutable float x0 = ctx.commandx;
5455   immutable float y0 = ctx.commandy;
5456   nvg__appendCommands(ctx,
5457     Command.BezierTo,
5458     x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
5459     x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
5460     x, y,
5461   );
5462 }
5463 
5464 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
5465 /// Arguments: [cx, cy, x, y]*
5466 /// Group: paths
5467 public void quadTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5468   enum ArgC = 4;
5469   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [quadTo] call");
5470   if (args.length < ArgC) return;
5471   const(float)* aptr = args.ptr;
5472   foreach (immutable idx; 0..args.length/ArgC) {
5473     immutable float x0 = ctx.commandx;
5474     immutable float y0 = ctx.commandy;
5475     immutable float cx = *aptr++;
5476     immutable float cy = *aptr++;
5477     immutable float x = *aptr++;
5478     immutable float y = *aptr++;
5479     nvg__appendCommands(ctx,
5480       Command.BezierTo,
5481       x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
5482       x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
5483       x, y,
5484     );
5485   }
5486 }
5487 
5488 /// Adds an arc segment at the corner defined by the last path point, and two specified points.
5489 /// Group: paths
5490 public void arcTo (NVGContext ctx, in float x1, in float y1, in float x2, in float y2, in float radius) nothrow @trusted @nogc {
5491   if (ctx.ncommands == 0) return;
5492 
5493   immutable float x0 = ctx.commandx;
5494   immutable float y0 = ctx.commandy;
5495 
5496   // handle degenerate cases
5497   if (nvg__ptEquals(x0, y0, x1, y1, ctx.distTol) ||
5498       nvg__ptEquals(x1, y1, x2, y2, ctx.distTol) ||
5499       nvg__distPtSeg(x1, y1, x0, y0, x2, y2) < ctx.distTol*ctx.distTol ||
5500       radius < ctx.distTol)
5501   {
5502     ctx.lineTo(x1, y1);
5503     return;
5504   }
5505 
5506   // calculate tangential circle to lines (x0, y0)-(x1, y1) and (x1, y1)-(x2, y2)
5507   float dx0 = x0-x1;
5508   float dy0 = y0-y1;
5509   float dx1 = x2-x1;
5510   float dy1 = y2-y1;
5511   nvg__normalize(&dx0, &dy0);
5512   nvg__normalize(&dx1, &dy1);
5513   immutable float a = nvg__acosf(dx0*dx1+dy0*dy1);
5514   immutable float d = radius/nvg__tanf(a/2.0f);
5515 
5516   //printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d);
5517 
5518   if (d > 10000.0f) {
5519     ctx.lineTo(x1, y1);
5520     return;
5521   }
5522 
5523   float cx = void, cy = void, a0 = void, a1 = void;
5524   NVGWinding dir;
5525   if (nvg__cross(dx0, dy0, dx1, dy1) > 0.0f) {
5526     cx = x1+dx0*d+dy0*radius;
5527     cy = y1+dy0*d+-dx0*radius;
5528     a0 = nvg__atan2f(dx0, -dy0);
5529     a1 = nvg__atan2f(-dx1, dy1);
5530     dir = NVGWinding.CW;
5531     //printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
5532   } else {
5533     cx = x1+dx0*d+-dy0*radius;
5534     cy = y1+dy0*d+dx0*radius;
5535     a0 = nvg__atan2f(-dx0, dy0);
5536     a1 = nvg__atan2f(dx1, -dy1);
5537     dir = NVGWinding.CCW;
5538     //printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
5539   }
5540 
5541   ctx.arc(dir, cx, cy, radius, a0, a1); // first is line
5542 }
5543 
5544 
5545 /// Adds an arc segment at the corner defined by the last path point, and two specified points.
5546 /// Arguments: [x1, y1, x2, y2, radius]*
5547 /// Group: paths
5548 public void arcTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5549   enum ArgC = 5;
5550   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arcTo] call");
5551   if (args.length < ArgC) return;
5552   if (ctx.ncommands == 0) return;
5553   const(float)* aptr = args.ptr;
5554   foreach (immutable idx; 0..args.length/ArgC) {
5555     immutable float x0 = ctx.commandx;
5556     immutable float y0 = ctx.commandy;
5557     immutable float x1 = *aptr++;
5558     immutable float y1 = *aptr++;
5559     immutable float x2 = *aptr++;
5560     immutable float y2 = *aptr++;
5561     immutable float radius = *aptr++;
5562     ctx.arcTo(x1, y1, x2, y2, radius);
5563   }
5564 }
5565 
5566 /// Closes current sub-path with a line segment.
5567 /// Group: paths
5568 @scriptable
5569 public void closePath (NVGContext ctx) nothrow @trusted @nogc {
5570   nvg__appendCommands(ctx, Command.Close);
5571 }
5572 
5573 /// Sets the current sub-path winding, see NVGWinding and NVGSolidity.
5574 /// Group: paths
5575 public void pathWinding (NVGContext ctx, NVGWinding dir) nothrow @trusted @nogc {
5576   nvg__appendCommands(ctx, Command.Winding, cast(float)dir);
5577 }
5578 
5579 /// Ditto.
5580 public void pathWinding (NVGContext ctx, NVGSolidity dir) nothrow @trusted @nogc {
5581   nvg__appendCommands(ctx, Command.Winding, cast(float)dir);
5582 }
5583 
5584 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r,
5585  * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW).
5586  * Angles are specified in radians.
5587  *
5588  * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo
5589  *
5590  * Group: paths
5591  */
5592 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 {
5593   static assert(mode == "original" || mode == "move" || mode == "line");
5594 
5595   float[3+5*7+100] vals = void;
5596   //int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo);
5597   static if (mode == "original") {
5598     immutable int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo);
5599   } else static if (mode == "move") {
5600     enum move = Command.MoveTo;
5601   } else static if (mode == "line") {
5602     enum move = Command.LineTo;
5603   } else {
5604     static assert(0, "wtf?!");
5605   }
5606 
5607   // Clamp angles
5608   float da = a1-a0;
5609   if (dir == NVGWinding.CW) {
5610     if (nvg__absf(da) >= NVG_PI*2) {
5611       da = NVG_PI*2;
5612     } else {
5613       while (da < 0.0f) da += NVG_PI*2;
5614     }
5615   } else {
5616     if (nvg__absf(da) >= NVG_PI*2) {
5617       da = -NVG_PI*2;
5618     } else {
5619       while (da > 0.0f) da -= NVG_PI*2;
5620     }
5621   }
5622 
5623   // Split arc into max 90 degree segments.
5624   immutable int ndivs = nvg__max(1, nvg__min(cast(int)(nvg__absf(da)/(NVG_PI*0.5f)+0.5f), 5));
5625   immutable float hda = (da/cast(float)ndivs)/2.0f;
5626   float kappa = nvg__absf(4.0f/3.0f*(1.0f-nvg__cosf(hda))/nvg__sinf(hda));
5627 
5628   if (dir == NVGWinding.CCW) kappa = -kappa;
5629 
5630   int nvals = 0;
5631   float px = 0, py = 0, ptanx = 0, ptany = 0;
5632   foreach (int i; 0..ndivs+1) {
5633     immutable float a = a0+da*(i/cast(float)ndivs);
5634     immutable float dx = nvg__cosf(a);
5635     immutable float dy = nvg__sinf(a);
5636     immutable float x = cx+dx*r;
5637     immutable float y = cy+dy*r;
5638     immutable float tanx = -dy*r*kappa;
5639     immutable float tany = dx*r*kappa;
5640 
5641     if (i == 0) {
5642       if (vals.length-nvals < 3) {
5643         // flush
5644         nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5645         nvals = 0;
5646       }
5647       vals.ptr[nvals++] = cast(float)move;
5648       vals.ptr[nvals++] = x;
5649       vals.ptr[nvals++] = y;
5650     } else {
5651       if (vals.length-nvals < 7) {
5652         // flush
5653         nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5654         nvals = 0;
5655       }
5656       vals.ptr[nvals++] = Command.BezierTo;
5657       vals.ptr[nvals++] = px+ptanx;
5658       vals.ptr[nvals++] = py+ptany;
5659       vals.ptr[nvals++] = x-tanx;
5660       vals.ptr[nvals++] = y-tany;
5661       vals.ptr[nvals++] = x;
5662       vals.ptr[nvals++] = y;
5663     }
5664     px = x;
5665     py = y;
5666     ptanx = tanx;
5667     ptany = tany;
5668   }
5669 
5670   nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5671 }
5672 
5673 
5674 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r,
5675  * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW).
5676  * Angles are specified in radians.
5677  *
5678  * Arguments: [cx, cy, r, a0, a1]*
5679  *
5680  * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo
5681  *
5682  * Group: paths
5683  */
5684 public void arc(string mode="original") (NVGContext ctx, NVGWinding dir, in float[] args) nothrow @trusted @nogc {
5685   static assert(mode == "original" || mode == "move" || mode == "line");
5686   enum ArgC = 5;
5687   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arc] call");
5688   if (args.length < ArgC) return;
5689   const(float)* aptr = args.ptr;
5690   foreach (immutable idx; 0..args.length/ArgC) {
5691     immutable cx = *aptr++;
5692     immutable cy = *aptr++;
5693     immutable r = *aptr++;
5694     immutable a0 = *aptr++;
5695     immutable a1 = *aptr++;
5696     ctx.arc!mode(dir, cx, cy, r, a0, a1);
5697   }
5698 }
5699 
5700 /// Creates new rectangle shaped sub-path.
5701 /// Group: paths
5702 @scriptable
5703 public void rect (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc {
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 /// Creates new rectangle shaped sub-path.
5714 /// Arguments: [x, y, w, h]*
5715 /// Group: paths
5716 public void rect (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5717   enum ArgC = 4;
5718   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [rect] call");
5719   if (args.length < ArgC) return;
5720   const(float)* aptr = args.ptr;
5721   foreach (immutable idx; 0..args.length/ArgC) {
5722     immutable x = *aptr++;
5723     immutable y = *aptr++;
5724     immutable w = *aptr++;
5725     immutable h = *aptr++;
5726     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5727       Command.MoveTo, x, y,
5728       Command.LineTo, x, y+h,
5729       Command.LineTo, x+w, y+h,
5730       Command.LineTo, x+w, y,
5731       Command.Close,
5732     );
5733   }
5734 }
5735 
5736 /// Creates new rounded rectangle shaped sub-path.
5737 /// Group: paths
5738 @scriptable
5739 public void roundedRect (NVGContext ctx, in float x, in float y, in float w, in float h, in float radius) nothrow @trusted @nogc {
5740   ctx.roundedRectVarying(x, y, w, h, radius, radius, radius, radius);
5741 }
5742 
5743 /// Creates new rounded rectangle shaped sub-path.
5744 /// Arguments: [x, y, w, h, radius]*
5745 /// Group: paths
5746 public void roundedRect (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5747   enum ArgC = 5;
5748   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRect] call");
5749   if (args.length < ArgC) return;
5750   const(float)* aptr = args.ptr;
5751   foreach (immutable idx; 0..args.length/ArgC) {
5752     immutable x = *aptr++;
5753     immutable y = *aptr++;
5754     immutable w = *aptr++;
5755     immutable h = *aptr++;
5756     immutable r = *aptr++;
5757     ctx.roundedRectVarying(x, y, w, h, r, r, r, r);
5758   }
5759 }
5760 
5761 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it.
5762 /// Group: paths
5763 @scriptable
5764 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 {
5765   if (rw < 0.1f || rh < 0.1f) {
5766     rect(ctx, x, y, w, h);
5767   } else {
5768     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5769       Command.MoveTo, x+rw, y,
5770       Command.LineTo, x+w-rw, y,
5771       Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh,
5772       Command.LineTo, x+w, y+h-rh,
5773       Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h,
5774       Command.LineTo, x+rw, y+h,
5775       Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh,
5776       Command.LineTo, x, y+rh,
5777       Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y,
5778       Command.Close,
5779     );
5780   }
5781 }
5782 
5783 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it.
5784 /// Arguments: [x, y, w, h, rw, rh]*
5785 /// Group: paths
5786 public void roundedRectEllipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5787   enum ArgC = 6;
5788   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectEllipse] call");
5789   if (args.length < ArgC) return;
5790   const(float)* aptr = args.ptr;
5791   foreach (immutable idx; 0..args.length/ArgC) {
5792     immutable x = *aptr++;
5793     immutable y = *aptr++;
5794     immutable w = *aptr++;
5795     immutable h = *aptr++;
5796     immutable rw = *aptr++;
5797     immutable rh = *aptr++;
5798     if (rw < 0.1f || rh < 0.1f) {
5799       rect(ctx, x, y, w, h);
5800     } else {
5801       nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5802         Command.MoveTo, x+rw, y,
5803         Command.LineTo, x+w-rw, y,
5804         Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh,
5805         Command.LineTo, x+w, y+h-rh,
5806         Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h,
5807         Command.LineTo, x+rw, y+h,
5808         Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh,
5809         Command.LineTo, x, y+rh,
5810         Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y,
5811         Command.Close,
5812       );
5813     }
5814   }
5815 }
5816 
5817 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner.
5818 /// Group: paths
5819 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 {
5820   if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) {
5821     ctx.rect(x, y, w, h);
5822   } else {
5823     immutable float halfw = nvg__absf(w)*0.5f;
5824     immutable float halfh = nvg__absf(h)*0.5f;
5825     immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h);
5826     immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h);
5827     immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h);
5828     immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h);
5829     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5830       Command.MoveTo, x, y+ryTL,
5831       Command.LineTo, x, y+h-ryBL,
5832       Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h,
5833       Command.LineTo, x+w-rxBR, y+h,
5834       Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR,
5835       Command.LineTo, x+w, y+ryTR,
5836       Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y,
5837       Command.LineTo, x+rxTL, y,
5838       Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL,
5839       Command.Close,
5840     );
5841   }
5842 }
5843 
5844 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner.
5845 /// Arguments: [x, y, w, h, radTopLeft, radTopRight, radBottomRight, radBottomLeft]*
5846 /// Group: paths
5847 public void roundedRectVarying (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5848   enum ArgC = 8;
5849   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectVarying] call");
5850   if (args.length < ArgC) return;
5851   const(float)* aptr = args.ptr;
5852   foreach (immutable idx; 0..args.length/ArgC) {
5853     immutable x = *aptr++;
5854     immutable y = *aptr++;
5855     immutable w = *aptr++;
5856     immutable h = *aptr++;
5857     immutable radTopLeft = *aptr++;
5858     immutable radTopRight = *aptr++;
5859     immutable radBottomRight = *aptr++;
5860     immutable radBottomLeft = *aptr++;
5861     if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) {
5862       ctx.rect(x, y, w, h);
5863     } else {
5864       immutable float halfw = nvg__absf(w)*0.5f;
5865       immutable float halfh = nvg__absf(h)*0.5f;
5866       immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h);
5867       immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h);
5868       immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h);
5869       immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h);
5870       nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5871         Command.MoveTo, x, y+ryTL,
5872         Command.LineTo, x, y+h-ryBL,
5873         Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h,
5874         Command.LineTo, x+w-rxBR, y+h,
5875         Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR,
5876         Command.LineTo, x+w, y+ryTR,
5877         Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y,
5878         Command.LineTo, x+rxTL, y,
5879         Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL,
5880         Command.Close,
5881       );
5882     }
5883   }
5884 }
5885 
5886 /// Creates new ellipse shaped sub-path.
5887 /// Group: paths
5888 public void ellipse (NVGContext ctx, in float cx, in float cy, in float rx, in float ry) nothrow @trusted @nogc {
5889   nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5890     Command.MoveTo, cx-rx, cy,
5891     Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,
5892     Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,
5893     Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,
5894     Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,
5895     Command.Close,
5896   );
5897 }
5898 
5899 /// Creates new ellipse shaped sub-path.
5900 /// Arguments: [cx, cy, rx, ry]*
5901 /// Group: paths
5902 public void ellipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5903   enum ArgC = 4;
5904   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [ellipse] call");
5905   if (args.length < ArgC) return;
5906   const(float)* aptr = args.ptr;
5907   foreach (immutable idx; 0..args.length/ArgC) {
5908     immutable cx = *aptr++;
5909     immutable cy = *aptr++;
5910     immutable rx = *aptr++;
5911     immutable ry = *aptr++;
5912     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5913       Command.MoveTo, cx-rx, cy,
5914       Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,
5915       Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,
5916       Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,
5917       Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,
5918       Command.Close,
5919     );
5920   }
5921 }
5922 
5923 /// Creates new circle shaped sub-path.
5924 /// Group: paths
5925 public void circle (NVGContext ctx, in float cx, in float cy, in float r) nothrow @trusted @nogc {
5926   ctx.ellipse(cx, cy, r, r);
5927 }
5928 
5929 /// Creates new circle shaped sub-path.
5930 /// Arguments: [cx, cy, r]*
5931 /// Group: paths
5932 public void circle (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5933   enum ArgC = 3;
5934   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [circle] call");
5935   if (args.length < ArgC) return;
5936   const(float)* aptr = args.ptr;
5937   foreach (immutable idx; 0..args.length/ArgC) {
5938     immutable cx = *aptr++;
5939     immutable cy = *aptr++;
5940     immutable r = *aptr++;
5941     ctx.ellipse(cx, cy, r, r);
5942   }
5943 }
5944 
5945 // Debug function to dump cached path data.
5946 debug public void debugDumpPathCache (NVGContext ctx) nothrow @trusted @nogc {
5947   import core.stdc.stdio : printf;
5948   const(NVGpath)* path;
5949   printf("Dumping %d cached paths\n", ctx.cache.npaths);
5950   for (int i = 0; i < ctx.cache.npaths; ++i) {
5951     path = &ctx.cache.paths[i];
5952     printf("-Path %d\n", i);
5953     if (path.nfill) {
5954       printf("-fill: %d\n", path.nfill);
5955       for (int j = 0; j < path.nfill; ++j) printf("%f\t%f\n", path.fill[j].x, path.fill[j].y);
5956     }
5957     if (path.nstroke) {
5958       printf("-stroke: %d\n", path.nstroke);
5959       for (int j = 0; j < path.nstroke; ++j) printf("%f\t%f\n", path.stroke[j].x, path.stroke[j].y);
5960     }
5961   }
5962 }
5963 
5964 // Flatten path, prepare it for fill operation.
5965 void nvg__prepareFill (NVGContext ctx) nothrow @trusted @nogc {
5966   NVGpathCache* cache = ctx.cache;
5967   NVGstate* state = nvg__getState(ctx);
5968 
5969   nvg__flattenPaths!false(ctx);
5970 
5971   if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) {
5972     nvg__expandFill(ctx, ctx.fringeWidth, NVGLineCap.Miter, 2.4f);
5973   } else {
5974     nvg__expandFill(ctx, 0.0f, NVGLineCap.Miter, 2.4f);
5975   }
5976 
5977   cache.evenOddMode = state.evenOddMode;
5978   cache.fringeWidth = ctx.fringeWidth;
5979   cache.fillReady = true;
5980   cache.strokeReady = false;
5981   cache.clipmode = NVGClipMode.None;
5982 }
5983 
5984 // Flatten path, prepare it for stroke operation.
5985 void nvg__prepareStroke (NVGContext ctx) nothrow @trusted @nogc {
5986   NVGstate* state = nvg__getState(ctx);
5987   NVGpathCache* cache = ctx.cache;
5988 
5989   nvg__flattenPaths!true(ctx);
5990 
5991   immutable float scale = nvg__getAverageScale(state.xform);
5992   float strokeWidth = nvg__clamp(state.strokeWidth*scale, 0.0f, 200.0f);
5993 
5994   if (strokeWidth < ctx.fringeWidth) {
5995     // If the stroke width is less than pixel size, use alpha to emulate coverage.
5996     // Since coverage is area, scale by alpha*alpha.
5997     immutable float alpha = nvg__clamp(strokeWidth/ctx.fringeWidth, 0.0f, 1.0f);
5998     cache.strokeAlphaMul = alpha*alpha;
5999     strokeWidth = ctx.fringeWidth;
6000   } else {
6001     cache.strokeAlphaMul = 1.0f;
6002   }
6003   cache.strokeWidth = strokeWidth;
6004 
6005   if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) {
6006     nvg__expandStroke(ctx, strokeWidth*0.5f+ctx.fringeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit);
6007   } else {
6008     nvg__expandStroke(ctx, strokeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit);
6009   }
6010 
6011   cache.fringeWidth = ctx.fringeWidth;
6012   cache.fillReady = false;
6013   cache.strokeReady = true;
6014   cache.clipmode = NVGClipMode.None;
6015 }
6016 
6017 /// Fills the current path with current fill style.
6018 /// Group: paths
6019 @scriptable
6020 public void fill (NVGContext ctx) nothrow @trusted @nogc {
6021   NVGstate* state = nvg__getState(ctx);
6022 
6023   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) {
6024     ctx.pathPickRegistered |= NVGPickKind.Fill<<16;
6025     ctx.currFillHitId = ctx.pathPickId;
6026   }
6027 
6028   nvg__prepareFill(ctx);
6029 
6030   // apply global alpha
6031   NVGPaint fillPaint = state.fill;
6032   fillPaint.innerColor.a *= state.alpha;
6033   fillPaint.middleColor.a *= state.alpha;
6034   fillPaint.outerColor.a *= state.alpha;
6035 
6036   ctx.appendCurrentPathToCache(ctx.recset, state.fill);
6037 
6038   if (ctx.recblockdraw) return;
6039 
6040   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);
6041 
6042   // count triangles
6043   foreach (int i; 0..ctx.cache.npaths) {
6044     NVGpath* path = &ctx.cache.paths[i];
6045     ctx.fillTriCount += path.nfill-2;
6046     ctx.fillTriCount += path.nstroke-2;
6047     ctx.drawCallCount += 2;
6048   }
6049 }
6050 
6051 /// Fills the current path with current stroke style.
6052 /// Group: paths
6053 @scriptable
6054 public void stroke (NVGContext ctx) nothrow @trusted @nogc {
6055   NVGstate* state = nvg__getState(ctx);
6056 
6057   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) {
6058     ctx.pathPickRegistered |= NVGPickKind.Stroke<<16;
6059     ctx.currStrokeHitId = ctx.pathPickId;
6060   }
6061 
6062   nvg__prepareStroke(ctx);
6063 
6064   NVGpathCache* cache = ctx.cache;
6065 
6066   NVGPaint strokePaint = state.stroke;
6067   strokePaint.innerColor.a *= cache.strokeAlphaMul;
6068   strokePaint.middleColor.a *= cache.strokeAlphaMul;
6069   strokePaint.outerColor.a *= cache.strokeAlphaMul;
6070 
6071   // apply global alpha
6072   strokePaint.innerColor.a *= state.alpha;
6073   strokePaint.middleColor.a *= state.alpha;
6074   strokePaint.outerColor.a *= state.alpha;
6075 
6076   ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
6077 
6078   if (ctx.recblockdraw) return;
6079 
6080   ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths);
6081 
6082   // count triangles
6083   foreach (int i; 0..ctx.cache.npaths) {
6084     NVGpath* path = &ctx.cache.paths[i];
6085     ctx.strokeTriCount += path.nstroke-2;
6086     ++ctx.drawCallCount;
6087   }
6088 }
6089 
6090 /// Sets current path as clipping region.
6091 /// Group: clipping
6092 public void clip (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc {
6093   NVGstate* state = nvg__getState(ctx);
6094 
6095   if (aclipmode == NVGClipMode.None) return;
6096   if (ctx.recblockdraw) return; //???
6097 
6098   if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr);
6099 
6100   /*
6101   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) {
6102     ctx.pathPickRegistered |= NVGPickKind.Fill<<16;
6103     ctx.currFillHitId = ctx.pathPickId;
6104   }
6105   */
6106 
6107   nvg__prepareFill(ctx);
6108 
6109   // apply global alpha
6110   NVGPaint fillPaint = state.fill;
6111   fillPaint.innerColor.a *= state.alpha;
6112   fillPaint.middleColor.a *= state.alpha;
6113   fillPaint.outerColor.a *= state.alpha;
6114 
6115   //ctx.appendCurrentPathToCache(ctx.recset, state.fill);
6116 
6117   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);
6118 
6119   // count triangles
6120   foreach (int i; 0..ctx.cache.npaths) {
6121     NVGpath* path = &ctx.cache.paths[i];
6122     ctx.fillTriCount += path.nfill-2;
6123     ctx.fillTriCount += path.nstroke-2;
6124     ctx.drawCallCount += 2;
6125   }
6126 }
6127 
6128 /// Sets current path as clipping region.
6129 /// Group: clipping
6130 public alias clipFill = clip;
6131 
6132 /// Sets current path' stroke as clipping region.
6133 /// Group: clipping
6134 public void clipStroke (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc {
6135   NVGstate* state = nvg__getState(ctx);
6136 
6137   if (aclipmode == NVGClipMode.None) return;
6138   if (ctx.recblockdraw) return; //???
6139 
6140   if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr);
6141 
6142   /*
6143   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) {
6144     ctx.pathPickRegistered |= NVGPickKind.Stroke<<16;
6145     ctx.currStrokeHitId = ctx.pathPickId;
6146   }
6147   */
6148 
6149   nvg__prepareStroke(ctx);
6150 
6151   NVGpathCache* cache = ctx.cache;
6152 
6153   NVGPaint strokePaint = state.stroke;
6154   strokePaint.innerColor.a *= cache.strokeAlphaMul;
6155   strokePaint.middleColor.a *= cache.strokeAlphaMul;
6156   strokePaint.outerColor.a *= cache.strokeAlphaMul;
6157 
6158   // apply global alpha
6159   strokePaint.innerColor.a *= state.alpha;
6160   strokePaint.middleColor.a *= state.alpha;
6161   strokePaint.outerColor.a *= state.alpha;
6162 
6163   //ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
6164 
6165   ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, aclipmode, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths);
6166 
6167   // count triangles
6168   foreach (int i; 0..ctx.cache.npaths) {
6169     NVGpath* path = &ctx.cache.paths[i];
6170     ctx.strokeTriCount += path.nstroke-2;
6171     ++ctx.drawCallCount;
6172   }
6173 }
6174 
6175 
6176 // ////////////////////////////////////////////////////////////////////////// //
6177 // Picking API
6178 
6179 // most of the code is by Michael Wynne <mike@mikesspace.net>
6180 // https://github.com/memononen/nanovg/pull/230
6181 // https://github.com/MikeWW/nanovg
6182 
6183 /// Pick type query. Used in [hitTest] and [hitTestAll].
6184 /// Group: picking_api
6185 public enum NVGPickKind : ubyte {
6186   Fill = 0x01, ///
6187   Stroke = 0x02, ///
6188   All = 0x03, ///
6189 }
6190 
6191 /// Marks the fill of the current path as pickable with the specified id.
6192 /// Note that you can create and mark path without rasterizing it.
6193 /// Group: picking_api
6194 public void currFillHitId (NVGContext ctx, int id) nothrow @trusted @nogc {
6195   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6196   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/false);
6197   nvg__pickSceneInsert(ps, pp);
6198 }
6199 
6200 public alias currFillPickId = currFillHitId; /// Ditto.
6201 
6202 /// Marks the stroke of the current path as pickable with the specified id.
6203 /// Note that you can create and mark path without rasterizing it.
6204 /// Group: picking_api
6205 public void currStrokeHitId (NVGContext ctx, int id) nothrow @trusted @nogc {
6206   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6207   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/true);
6208   nvg__pickSceneInsert(ps, pp);
6209 }
6210 
6211 public alias currStrokePickId = currStrokeHitId; /// Ditto.
6212 
6213 // Marks the saved path set (fill) as pickable with the specified id.
6214 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!)
6215 // Group: picking_api
6216 /+
6217 public void pathSetFillHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc {
6218   if (svp is null) return;
6219   if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context");
6220   foreach (ref cp; svp.caches[0..svp.ncaches]) {
6221     NVGpickScene* ps = nvg__pickSceneGet(ctx);
6222     NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/false);
6223     nvg__pickSceneInsert(ps, pp);
6224   }
6225 }
6226 +/
6227 
6228 // Marks the saved path set (stroke) as pickable with the specified id.
6229 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!)
6230 // Group: picking_api
6231 /+
6232 public void pathSetStrokeHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc {
6233   if (svp is null) return;
6234   if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context");
6235   foreach (ref cp; svp.caches[0..svp.ncaches]) {
6236     NVGpickScene* ps = nvg__pickSceneGet(ctx);
6237     NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/true);
6238     nvg__pickSceneInsert(ps, pp);
6239   }
6240 }
6241 +/
6242 
6243 private template IsGoodHitTestDG(DG) {
6244   enum IsGoodHitTestDG =
6245     __traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); }) ||
6246     __traits(compiles, (){ DG dg; dg(cast(int)42, cast(int)666); });
6247 }
6248 
6249 private template IsGoodHitTestInternalDG(DG) {
6250   enum IsGoodHitTestInternalDG =
6251     __traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); }) ||
6252     __traits(compiles, (){ DG dg; NVGpickPath* pp; dg(pp); });
6253 }
6254 
6255 /// Call delegate [dg] for each path under the specified position (in no particular order).
6256 /// Returns the id of the path for which delegate [dg] returned true or [NVGNoPick].
6257 /// dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending).
6258 /// Group: picking_api
6259 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) {
6260   if (ctx.pickScene is null || ctx.pickScene.npaths == 0 || (kind&NVGPickKind.All) == 0) return -1;
6261 
6262   NVGpickScene* ps = ctx.pickScene;
6263   int levelwidth = 1<<(ps.nlevels-1);
6264   int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth);
6265   int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth);
6266   int npicked = 0;
6267 
6268   // if we are interested only in most-toplevel path, there is no reason to check paths with worser order.
6269   // but we cannot just get out on the first path found, 'cause we are using quad tree to speed up bounds
6270   // checking, so path walking order is not guaranteed.
6271   static if (bestOrder) {
6272     int lastBestOrder = int.min;
6273   }
6274 
6275   //{ import core.stdc.stdio; printf("npaths=%d\n", ps.npaths); }
6276   for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) {
6277     for (NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; pp !is null; pp = pp.next) {
6278       //{ 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); }
6279       static if (bestOrder) {
6280         // reject earlier paths
6281         if (pp.order <= lastBestOrder) continue; // not interesting
6282       }
6283       immutable uint kpx = kind&pp.flags&3;
6284       if (kpx == 0) continue; // not interesting
6285       if (!nvg__pickPathTestBounds(ctx, ps, pp, x, y)) continue; // not interesting
6286       //{ import core.stdc.stdio; printf("in bounds!\n"); }
6287       int hit = 0;
6288       if (kpx&NVGPickKind.Stroke) hit = nvg__pickPathStroke(ps, pp, x, y);
6289       if (!hit && (kpx&NVGPickKind.Fill)) hit = nvg__pickPath(ps, pp, x, y);
6290       if (!hit) continue;
6291       //{ import core.stdc.stdio; printf("  HIT!\n"); }
6292       static if (bestOrder) lastBestOrder = pp.order;
6293       static if (IsGoodHitTestDG!DG) {
6294         static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) {
6295           if (dg(pp.id, cast(int)pp.order)) return pp.id;
6296         } else {
6297           dg(pp.id, cast(int)pp.order);
6298         }
6299       } else {
6300         static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) {
6301           if (dg(pp)) return pp.id;
6302         } else {
6303           dg(pp);
6304         }
6305       }
6306     }
6307     cellx >>= 1;
6308     celly >>= 1;
6309     levelwidth >>= 1;
6310   }
6311 
6312   return -1;
6313 }
6314 
6315 /// Fills ids with a list of the top most hit ids (from bottom to top) under the specified position.
6316 /// Returns the slice of [ids].
6317 /// Group: picking_api
6318 public int[] hitTestAll (NVGContext ctx, in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc {
6319   if (ctx.pickScene is null || ids.length == 0) return ids[0..0];
6320 
6321   int npicked = 0;
6322   NVGpickScene* ps = ctx.pickScene;
6323 
6324   ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
6325     if (npicked == ps.cpicked) {
6326       int cpicked = ps.cpicked+ps.cpicked;
6327       NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked);
6328       if (picked is null) return true; // abort
6329       ps.cpicked = cpicked;
6330       ps.picked = picked;
6331     }
6332     ps.picked[npicked] = pp;
6333     ++npicked;
6334     return false; // go on
6335   });
6336 
6337   qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths);
6338 
6339   assert(npicked >= 0);
6340   if (npicked > ids.length) npicked = cast(int)ids.length;
6341   foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id;
6342 
6343   return ids[0..npicked];
6344 }
6345 
6346 /// Returns the id of the pickable shape containing x,y or [NVGNoPick] if no shape was found.
6347 /// Group: picking_api
6348 public int hitTest (NVGContext ctx, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc {
6349   if (ctx.pickScene is null) return -1;
6350 
6351   int bestOrder = int.min;
6352   int bestID = -1;
6353 
6354   ctx.hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) {
6355     if (pp.order > bestOrder) {
6356       bestOrder = pp.order;
6357       bestID = pp.id;
6358     }
6359   });
6360 
6361   return bestID;
6362 }
6363 
6364 /// Returns `true` if the path with the given id contains x,y.
6365 /// Group: picking_api
6366 public bool hitTestForId (NVGContext ctx, in int id, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc {
6367   if (ctx.pickScene is null || id == NVGNoPick) return false;
6368 
6369   bool res = false;
6370 
6371   ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) {
6372     if (pp.id == id) {
6373       res = true;
6374       return true; // stop
6375     }
6376     return false; // continue
6377   });
6378 
6379   return res;
6380 }
6381 
6382 /// Returns `true` if the given point is within the fill of the currently defined path.
6383 /// This operation can be done before rasterizing the current path.
6384 /// Group: picking_api
6385 public bool hitTestCurrFill (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
6386   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6387   int oldnpoints = ps.npoints;
6388   int oldnsegments = ps.nsegments;
6389   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/false);
6390   if (pp is null) return false; // oops
6391   scope(exit) {
6392     nvg__freePickPath(ps, pp);
6393     ps.npoints = oldnpoints;
6394     ps.nsegments = oldnsegments;
6395   }
6396   return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPath(ps, pp, x, y) : false);
6397 }
6398 
6399 public alias isPointInPath = hitTestCurrFill; /// Ditto.
6400 
6401 /// Returns `true` if the given point is within the stroke of the currently defined path.
6402 /// This operation can be done before rasterizing the current path.
6403 /// Group: picking_api
6404 public bool hitTestCurrStroke (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
6405   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6406   int oldnpoints = ps.npoints;
6407   int oldnsegments = ps.nsegments;
6408   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/true);
6409   if (pp is null) return false; // oops
6410   scope(exit) {
6411     nvg__freePickPath(ps, pp);
6412     ps.npoints = oldnpoints;
6413     ps.nsegments = oldnsegments;
6414   }
6415   return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPathStroke(ps, pp, x, y) : false);
6416 }
6417 
6418 
6419 nothrow @trusted @nogc {
6420 extern(C) {
6421   private alias _compare_fp_t = int function (const void*, const void*) nothrow @nogc;
6422   private extern(C) void qsort (scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc;
6423 
6424   extern(C) int nvg__comparePaths (const void* a, const void* b) {
6425     return (*cast(const(NVGpickPath)**)b).order-(*cast(const(NVGpickPath)**)a).order;
6426   }
6427 }
6428 
6429 enum NVGPickEPS = 0.0001f;
6430 
6431 // Segment flags
6432 enum NVGSegmentFlags {
6433   Corner = 1,
6434   Bevel = 2,
6435   InnerBevel = 4,
6436   Cap = 8,
6437   Endcap = 16,
6438 }
6439 
6440 // Path flags
6441 enum NVGPathFlags : ushort {
6442   Fill = NVGPickKind.Fill,
6443   Stroke = NVGPickKind.Stroke,
6444   Scissor = 0x80,
6445 }
6446 
6447 struct NVGsegment {
6448   int firstPoint; // Index into NVGpickScene.points
6449   short type; // NVG_LINETO or NVG_BEZIERTO
6450   short flags; // Flags relate to the corner between the prev segment and this one.
6451   float[4] bounds;
6452   float[2] startDir; // Direction at t == 0
6453   float[2] endDir; // Direction at t == 1
6454   float[2] miterDir; // Direction of miter of corner between the prev segment and this one.
6455 }
6456 
6457 struct NVGpickSubPath {
6458   short winding; // TODO: Merge to flag field
6459   bool closed; // TODO: Merge to flag field
6460 
6461   int firstSegment; // Index into NVGpickScene.segments
6462   int nsegments;
6463 
6464   float[4] bounds;
6465 
6466   NVGpickSubPath* next;
6467 }
6468 
6469 struct NVGpickPath {
6470   int id;
6471   short flags;
6472   short order;
6473   float strokeWidth;
6474   float miterLimit;
6475   short lineCap;
6476   short lineJoin;
6477   bool evenOddMode;
6478 
6479   float[4] bounds;
6480   int scissor; // Indexes into ps->points and defines scissor rect as XVec, YVec and Center
6481 
6482   NVGpickSubPath* subPaths;
6483   NVGpickPath* next;
6484   NVGpickPath* cellnext;
6485 }
6486 
6487 struct NVGpickScene {
6488   int npaths;
6489 
6490   NVGpickPath* paths; // Linked list of paths
6491   NVGpickPath* lastPath; // The last path in the paths linked list (the first path added)
6492   NVGpickPath* freePaths; // Linked list of free paths
6493 
6494   NVGpickSubPath* freeSubPaths; // Linked list of free sub paths
6495 
6496   int width;
6497   int height;
6498 
6499   // Points for all path sub paths.
6500   float* points;
6501   int npoints;
6502   int cpoints;
6503 
6504   // Segments for all path sub paths
6505   NVGsegment* segments;
6506   int nsegments;
6507   int csegments;
6508 
6509   // Implicit quadtree
6510   float xdim; // Width / (1 << nlevels)
6511   float ydim; // Height / (1 << nlevels)
6512   int ncells; // Total number of cells in all levels
6513   int nlevels;
6514   NVGpickPath*** levels; // Index: [Level][LevelY * LevelW + LevelX] Value: Linked list of paths
6515 
6516   // Temp storage for picking
6517   int cpicked;
6518   NVGpickPath** picked;
6519 }
6520 
6521 
6522 // bounds utilities
6523 void nvg__initBounds (ref float[4] bounds) {
6524   bounds.ptr[0] = bounds.ptr[1] = float.max;
6525   bounds.ptr[2] = bounds.ptr[3] = -float.max;
6526 }
6527 
6528 void nvg__expandBounds (ref float[4] bounds, const(float)* points, int npoints) {
6529   npoints *= 2;
6530   for (int i = 0; i < npoints; i += 2) {
6531     bounds.ptr[0] = nvg__min(bounds.ptr[0], points[i]);
6532     bounds.ptr[1] = nvg__min(bounds.ptr[1], points[i+1]);
6533     bounds.ptr[2] = nvg__max(bounds.ptr[2], points[i]);
6534     bounds.ptr[3] = nvg__max(bounds.ptr[3], points[i+1]);
6535   }
6536 }
6537 
6538 void nvg__unionBounds (ref float[4] bounds, const scope ref float[4] boundsB) {
6539   bounds.ptr[0] = nvg__min(bounds.ptr[0], boundsB.ptr[0]);
6540   bounds.ptr[1] = nvg__min(bounds.ptr[1], boundsB.ptr[1]);
6541   bounds.ptr[2] = nvg__max(bounds.ptr[2], boundsB.ptr[2]);
6542   bounds.ptr[3] = nvg__max(bounds.ptr[3], boundsB.ptr[3]);
6543 }
6544 
6545 void nvg__intersectBounds (ref float[4] bounds, const scope ref float[4] boundsB) {
6546   bounds.ptr[0] = nvg__max(boundsB.ptr[0], bounds.ptr[0]);
6547   bounds.ptr[1] = nvg__max(boundsB.ptr[1], bounds.ptr[1]);
6548   bounds.ptr[2] = nvg__min(boundsB.ptr[2], bounds.ptr[2]);
6549   bounds.ptr[3] = nvg__min(boundsB.ptr[3], bounds.ptr[3]);
6550 
6551   bounds.ptr[2] = nvg__max(bounds.ptr[0], bounds.ptr[2]);
6552   bounds.ptr[3] = nvg__max(bounds.ptr[1], bounds.ptr[3]);
6553 }
6554 
6555 bool nvg__pointInBounds (in float x, in float y, const scope ref float[4] bounds) {
6556   pragma(inline, true);
6557   return (x >= bounds.ptr[0] && x <= bounds.ptr[2] && y >= bounds.ptr[1] && y <= bounds.ptr[3]);
6558 }
6559 
6560 // building paths & sub paths
6561 int nvg__pickSceneAddPoints (NVGpickScene* ps, const(float)* xy, int n) {
6562   import core.stdc.string : memcpy;
6563   if (ps.npoints+n > ps.cpoints) {
6564     import core.stdc.stdlib : realloc;
6565     int cpoints = ps.npoints+n+(ps.cpoints<<1);
6566     float* points = cast(float*)realloc(ps.points, float.sizeof*2*cpoints);
6567     if (points is null) assert(0, "NanoVega: out of memory");
6568     ps.points = points;
6569     ps.cpoints = cpoints;
6570   }
6571   int i = ps.npoints;
6572   if (xy !is null) memcpy(&ps.points[i*2], xy, float.sizeof*2*n);
6573   ps.npoints += n;
6574   return i;
6575 }
6576 
6577 void nvg__pickSubPathAddSegment (NVGpickScene* ps, NVGpickSubPath* psp, int firstPoint, int type, short flags) {
6578   NVGsegment* seg = null;
6579   if (ps.nsegments == ps.csegments) {
6580     int csegments = 1+ps.csegments+(ps.csegments<<1);
6581     NVGsegment* segments = cast(NVGsegment*)realloc(ps.segments, NVGsegment.sizeof*csegments);
6582     if (segments is null) assert(0, "NanoVega: out of memory");
6583     ps.segments = segments;
6584     ps.csegments = csegments;
6585   }
6586 
6587   if (psp.firstSegment == -1) psp.firstSegment = ps.nsegments;
6588 
6589   seg = &ps.segments[ps.nsegments];
6590   ++ps.nsegments;
6591   seg.firstPoint = firstPoint;
6592   seg.type = cast(short)type;
6593   seg.flags = flags;
6594   ++psp.nsegments;
6595 
6596   nvg__segmentDir(ps, psp, seg, 0, seg.startDir);
6597   nvg__segmentDir(ps, psp, seg, 1, seg.endDir);
6598 }
6599 
6600 void nvg__segmentDir (NVGpickScene* ps, NVGpickSubPath* psp, NVGsegment* seg, float t, ref float[2] d) {
6601   const(float)* points = &ps.points[seg.firstPoint*2];
6602   immutable float x0 = points[0*2+0], x1 = points[1*2+0];
6603   immutable float y0 = points[0*2+1], y1 = points[1*2+1];
6604   switch (seg.type) {
6605     case Command.LineTo:
6606       d.ptr[0] = x1-x0;
6607       d.ptr[1] = y1-y0;
6608       nvg__normalize(&d.ptr[0], &d.ptr[1]);
6609       break;
6610     case Command.BezierTo:
6611       immutable float x2 = points[2*2+0];
6612       immutable float y2 = points[2*2+1];
6613       immutable float x3 = points[3*2+0];
6614       immutable float y3 = points[3*2+1];
6615 
6616       immutable float omt = 1.0f-t;
6617       immutable float omt2 = omt*omt;
6618       immutable float t2 = t*t;
6619 
6620       d.ptr[0] =
6621         3.0f*omt2*(x1-x0)+
6622         6.0f*omt*t*(x2-x1)+
6623         3.0f*t2*(x3-x2);
6624 
6625       d.ptr[1] =
6626         3.0f*omt2*(y1-y0)+
6627         6.0f*omt*t*(y2-y1)+
6628         3.0f*t2*(y3-y2);
6629 
6630       nvg__normalize(&d.ptr[0], &d.ptr[1]);
6631       break;
6632     default:
6633       break;
6634   }
6635 }
6636 
6637 void nvg__pickSubPathAddFillSupports (NVGpickScene* ps, NVGpickSubPath* psp) {
6638   if (psp.firstSegment == -1) return;
6639   NVGsegment* segments = &ps.segments[psp.firstSegment];
6640   for (int s = 0; s < psp.nsegments; ++s) {
6641     NVGsegment* seg = &segments[s];
6642     const(float)* points = &ps.points[seg.firstPoint*2];
6643     if (seg.type == Command.LineTo) {
6644       nvg__initBounds(seg.bounds);
6645       nvg__expandBounds(seg.bounds, points, 2);
6646     } else {
6647       nvg__bezierBounds(points, seg.bounds);
6648     }
6649   }
6650 }
6651 
6652 void nvg__pickSubPathAddStrokeSupports (NVGpickScene* ps, NVGpickSubPath* psp, float strokeWidth, int lineCap, int lineJoin, float miterLimit) {
6653   if (psp.firstSegment == -1) return;
6654   immutable bool closed = psp.closed;
6655   const(float)* points = ps.points;
6656   NVGsegment* seg = null;
6657   NVGsegment* segments = &ps.segments[psp.firstSegment];
6658   int nsegments = psp.nsegments;
6659   NVGsegment* prevseg = (closed ? &segments[psp.nsegments-1] : null);
6660 
6661   int ns = 0; // nsupports
6662   float[32] supportingPoints = void;
6663   int firstPoint, lastPoint;
6664 
6665   if (!closed) {
6666     segments[0].flags |= NVGSegmentFlags.Cap;
6667     segments[nsegments-1].flags |= NVGSegmentFlags.Endcap;
6668   }
6669 
6670   for (int s = 0; s < nsegments; ++s) {
6671     seg = &segments[s];
6672     nvg__initBounds(seg.bounds);
6673 
6674     firstPoint = seg.firstPoint*2;
6675     lastPoint = firstPoint+(seg.type == Command.LineTo ? 2 : 6);
6676 
6677     ns = 0;
6678 
6679     // First two supporting points are either side of the start point
6680     supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[1]*strokeWidth;
6681     supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.startDir.ptr[0]*strokeWidth;
6682 
6683     supportingPoints.ptr[ns++] = points[firstPoint]+seg.startDir.ptr[1]*strokeWidth;
6684     supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[0]*strokeWidth;
6685 
6686     // Second two supporting points are either side of the end point
6687     supportingPoints.ptr[ns++] = points[lastPoint]-seg.endDir.ptr[1]*strokeWidth;
6688     supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[0]*strokeWidth;
6689 
6690     supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[1]*strokeWidth;
6691     supportingPoints.ptr[ns++] = points[lastPoint+1]-seg.endDir.ptr[0]*strokeWidth;
6692 
6693     if ((seg.flags&NVGSegmentFlags.Corner) && prevseg !is null) {
6694       seg.miterDir.ptr[0] = 0.5f*(-prevseg.endDir.ptr[1]-seg.startDir.ptr[1]);
6695       seg.miterDir.ptr[1] = 0.5f*(prevseg.endDir.ptr[0]+seg.startDir.ptr[0]);
6696 
6697       immutable float M2 = seg.miterDir.ptr[0]*seg.miterDir.ptr[0]+seg.miterDir.ptr[1]*seg.miterDir.ptr[1];
6698 
6699       if (M2 > 0.000001f) {
6700         float scale = 1.0f/M2;
6701         if (scale > 600.0f) scale = 600.0f;
6702         seg.miterDir.ptr[0] *= scale;
6703         seg.miterDir.ptr[1] *= scale;
6704       }
6705 
6706       //NVG_PICK_DEBUG_VECTOR_SCALE(&points[firstPoint], seg.miterDir, 10);
6707 
6708       // Add an additional support at the corner on the other line
6709       supportingPoints.ptr[ns++] = points[firstPoint]-prevseg.endDir.ptr[1]*strokeWidth;
6710       supportingPoints.ptr[ns++] = points[firstPoint+1]+prevseg.endDir.ptr[0]*strokeWidth;
6711 
6712       if (lineJoin == NVGLineCap.Miter || lineJoin == NVGLineCap.Bevel) {
6713         // Set a corner as beveled if the join type is bevel or mitered and
6714         // miterLimit is hit.
6715         if (lineJoin == NVGLineCap.Bevel || (M2*miterLimit*miterLimit) < 1.0f) {
6716           seg.flags |= NVGSegmentFlags.Bevel;
6717         } else {
6718           // Corner is mitered - add miter point as a support
6719           supportingPoints.ptr[ns++] = points[firstPoint]+seg.miterDir.ptr[0]*strokeWidth;
6720           supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.miterDir.ptr[1]*strokeWidth;
6721         }
6722       } else if (lineJoin == NVGLineCap.Round) {
6723         // ... and at the midpoint of the corner arc
6724         float[2] vertexN = [ -seg.startDir.ptr[0]+prevseg.endDir.ptr[0], -seg.startDir.ptr[1]+prevseg.endDir.ptr[1] ];
6725         nvg__normalize(&vertexN[0], &vertexN[1]);
6726 
6727         supportingPoints.ptr[ns++] = points[firstPoint]+vertexN[0]*strokeWidth;
6728         supportingPoints.ptr[ns++] = points[firstPoint+1]+vertexN[1]*strokeWidth;
6729       }
6730     }
6731 
6732     if (seg.flags&NVGSegmentFlags.Cap) {
6733       switch (lineCap) {
6734         case NVGLineCap.Butt:
6735           // supports for butt already added
6736           break;
6737         case NVGLineCap.Square:
6738           // square cap supports are just the original two supports moved out along the direction
6739           supportingPoints.ptr[ns++] = supportingPoints.ptr[0]-seg.startDir.ptr[0]*strokeWidth;
6740           supportingPoints.ptr[ns++] = supportingPoints.ptr[1]-seg.startDir.ptr[1]*strokeWidth;
6741           supportingPoints.ptr[ns++] = supportingPoints.ptr[2]-seg.startDir.ptr[0]*strokeWidth;
6742           supportingPoints.ptr[ns++] = supportingPoints.ptr[3]-seg.startDir.ptr[1]*strokeWidth;
6743           break;
6744         case NVGLineCap.Round:
6745           // add one additional support for the round cap along the dir
6746           supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[0]*strokeWidth;
6747           supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[1]*strokeWidth;
6748           break;
6749         default:
6750           break;
6751       }
6752     }
6753 
6754     if (seg.flags&NVGSegmentFlags.Endcap) {
6755       // end supporting points, either side of line
6756       int end = 4;
6757       switch(lineCap) {
6758         case NVGLineCap.Butt:
6759           // supports for butt already added
6760           break;
6761         case NVGLineCap.Square:
6762           // square cap supports are just the original two supports moved out along the direction
6763           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+0]+seg.endDir.ptr[0]*strokeWidth;
6764           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+1]+seg.endDir.ptr[1]*strokeWidth;
6765           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+2]+seg.endDir.ptr[0]*strokeWidth;
6766           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+3]+seg.endDir.ptr[1]*strokeWidth;
6767           break;
6768         case NVGLineCap.Round:
6769           // add one additional support for the round cap along the dir
6770           supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[0]*strokeWidth;
6771           supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[1]*strokeWidth;
6772           break;
6773         default:
6774           break;
6775       }
6776     }
6777 
6778     nvg__expandBounds(seg.bounds, supportingPoints.ptr, ns/2);
6779 
6780     prevseg = seg;
6781   }
6782 }
6783 
6784 NVGpickPath* nvg__pickPathCreate (NVGContext context, const(float)[] acommands, int id, bool forStroke) {
6785   NVGpickScene* ps = nvg__pickSceneGet(context);
6786   if (ps is null) return null;
6787 
6788   int i = 0;
6789 
6790   int ncommands = cast(int)acommands.length;
6791   const(float)* commands = acommands.ptr;
6792 
6793   NVGpickPath* pp = null;
6794   NVGpickSubPath* psp = null;
6795   float[2] start = void;
6796   int firstPoint;
6797 
6798   //bool hasHoles = false;
6799   NVGpickSubPath* prev = null;
6800 
6801   float[8] points = void;
6802   float[2] inflections = void;
6803   int ninflections = 0;
6804 
6805   NVGstate* state = nvg__getState(context);
6806   float[4] totalBounds = void;
6807   NVGsegment* segments = null;
6808   const(NVGsegment)* seg = null;
6809   NVGpickSubPath *curpsp;
6810 
6811   pp = nvg__allocPickPath(ps);
6812   if (pp is null) return null;
6813 
6814   pp.id = id;
6815 
6816   bool hasPoints = false;
6817 
6818   void closeIt () {
6819     if (psp is null || !hasPoints) return;
6820     if (ps.points[(ps.npoints-1)*2] != start.ptr[0] || ps.points[(ps.npoints-1)*2+1] != start.ptr[1]) {
6821       firstPoint = nvg__pickSceneAddPoints(ps, start.ptr, 1);
6822       nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, Command.LineTo, NVGSegmentFlags.Corner);
6823     }
6824     psp.closed = true;
6825   }
6826 
6827   while (i < ncommands) {
6828     int cmd = cast(int)commands[i++];
6829     switch (cmd) {
6830       case Command.MoveTo: // one coordinate pair
6831         const(float)* tfxy = commands+i;
6832         i += 2;
6833 
6834         // new starting point
6835         start.ptr[0..2] = tfxy[0..2];
6836 
6837         // start a new path for each sub path to handle sub paths that intersect other sub paths
6838         prev = psp;
6839         psp = nvg__allocPickSubPath(ps);
6840         if (psp is null) { psp = prev; break; }
6841         psp.firstSegment = -1;
6842         psp.winding = NVGSolidity.Solid;
6843         psp.next = prev;
6844 
6845         nvg__pickSceneAddPoints(ps, tfxy, 1);
6846         hasPoints = true;
6847         break;
6848       case Command.LineTo: // one coordinate pair
6849         const(float)* tfxy = commands+i;
6850         i += 2;
6851         firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 1);
6852         nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner);
6853         hasPoints = true;
6854         break;
6855       case Command.BezierTo: // three coordinate pairs
6856         const(float)* tfxy = commands+i;
6857         i += 3*2;
6858 
6859         // Split the curve at it's dx==0 or dy==0 inflection points.
6860         // Thus:
6861         //    A horizontal line only ever interects the curves once.
6862         //  and
6863         //    Finding the closest point on any curve converges more reliably.
6864 
6865         // NOTE: We could just split on dy==0 here.
6866 
6867         memcpy(&points.ptr[0], &ps.points[(ps.npoints-1)*2], float.sizeof*2);
6868         memcpy(&points.ptr[2], tfxy, float.sizeof*2*3);
6869 
6870         ninflections = 0;
6871         nvg__bezierInflections(points.ptr, 1, &ninflections, inflections.ptr);
6872         nvg__bezierInflections(points.ptr, 0, &ninflections, inflections.ptr);
6873 
6874         if (ninflections) {
6875           float previnfl = 0;
6876           float[8] pointsA = void, pointsB = void;
6877 
6878           nvg__smallsort(inflections.ptr, ninflections);
6879 
6880           for (int infl = 0; infl < ninflections; ++infl) {
6881             if (nvg__absf(inflections.ptr[infl]-previnfl) < NVGPickEPS) continue;
6882 
6883             immutable float t = (inflections.ptr[infl]-previnfl)*(1.0f/(1.0f-previnfl));
6884 
6885             previnfl = inflections.ptr[infl];
6886 
6887             nvg__splitBezier(points.ptr, t, pointsA.ptr, pointsB.ptr);
6888 
6889             firstPoint = nvg__pickSceneAddPoints(ps, &pointsA.ptr[2], 3);
6890             nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, (infl == 0) ? NVGSegmentFlags.Corner : 0);
6891 
6892             memcpy(points.ptr, pointsB.ptr, float.sizeof*8);
6893           }
6894 
6895           firstPoint = nvg__pickSceneAddPoints(ps, &pointsB.ptr[2], 3);
6896           nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, 0);
6897         } else {
6898           firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 3);
6899           nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner);
6900         }
6901         hasPoints = true;
6902         break;
6903       case Command.Close:
6904         closeIt();
6905         break;
6906       case Command.Winding:
6907         psp.winding = cast(short)cast(int)commands[i];
6908         //if (psp.winding == NVGSolidity.Hole) hasHoles = true;
6909         i += 1;
6910         break;
6911       default:
6912         break;
6913     }
6914   }
6915 
6916   // force-close filled paths
6917   if (psp !is null && !forStroke && hasPoints && !psp.closed) closeIt();
6918 
6919   pp.flags = (forStroke ? NVGPathFlags.Stroke : NVGPathFlags.Fill);
6920   pp.subPaths = psp;
6921   pp.strokeWidth = state.strokeWidth*0.5f;
6922   pp.miterLimit = state.miterLimit;
6923   pp.lineCap = cast(short)state.lineCap;
6924   pp.lineJoin = cast(short)state.lineJoin;
6925   pp.evenOddMode = nvg__getState(context).evenOddMode;
6926 
6927   nvg__initBounds(totalBounds);
6928 
6929   for (curpsp = psp; curpsp; curpsp = curpsp.next) {
6930     if (forStroke) {
6931       nvg__pickSubPathAddStrokeSupports(ps, curpsp, pp.strokeWidth, pp.lineCap, pp.lineJoin, pp.miterLimit);
6932     } else {
6933       nvg__pickSubPathAddFillSupports(ps, curpsp);
6934     }
6935 
6936     if (curpsp.firstSegment == -1) continue;
6937     segments = &ps.segments[curpsp.firstSegment];
6938     nvg__initBounds(curpsp.bounds);
6939     for (int s = 0; s < curpsp.nsegments; ++s) {
6940       seg = &segments[s];
6941       //NVG_PICK_DEBUG_BOUNDS(seg.bounds);
6942       nvg__unionBounds(curpsp.bounds, seg.bounds);
6943     }
6944 
6945     nvg__unionBounds(totalBounds, curpsp.bounds);
6946   }
6947 
6948   // Store the scissor rect if present.
6949   if (state.scissor.extent.ptr[0] != -1.0f) {
6950     // Use points storage to store the scissor data
6951     pp.scissor = nvg__pickSceneAddPoints(ps, null, 4);
6952     float* scissor = &ps.points[pp.scissor*2];
6953 
6954     //memcpy(scissor, state.scissor.xform.ptr, 6*float.sizeof);
6955     scissor[0..6] = state.scissor.xform.mat[];
6956     memcpy(scissor+6, state.scissor.extent.ptr, 2*float.sizeof);
6957 
6958     pp.flags |= NVGPathFlags.Scissor;
6959   }
6960 
6961   memcpy(pp.bounds.ptr, totalBounds.ptr, float.sizeof*4);
6962 
6963   return pp;
6964 }
6965 
6966 
6967 // Struct management
6968 NVGpickPath* nvg__allocPickPath (NVGpickScene* ps) {
6969   NVGpickPath* pp = ps.freePaths;
6970   if (pp !is null) {
6971     ps.freePaths = pp.next;
6972   } else {
6973     pp = cast(NVGpickPath*)malloc(NVGpickPath.sizeof);
6974   }
6975   memset(pp, 0, NVGpickPath.sizeof);
6976   return pp;
6977 }
6978 
6979 // Put a pick path and any sub paths (back) to the free lists.
6980 void nvg__freePickPath (NVGpickScene* ps, NVGpickPath* pp) {
6981   // Add all sub paths to the sub path free list.
6982   // Finds the end of the path sub paths, links that to the current
6983   // sub path free list head and replaces the head ptr with the
6984   // head path sub path entry.
6985   NVGpickSubPath* psp = null;
6986   for (psp = pp.subPaths; psp !is null && psp.next !is null; psp = psp.next) {}
6987 
6988   if (psp) {
6989     psp.next = ps.freeSubPaths;
6990     ps.freeSubPaths = pp.subPaths;
6991   }
6992   pp.subPaths = null;
6993 
6994   // Add the path to the path freelist
6995   pp.next = ps.freePaths;
6996   ps.freePaths = pp;
6997   if (pp.next is null) ps.lastPath = pp;
6998 }
6999 
7000 NVGpickSubPath* nvg__allocPickSubPath (NVGpickScene* ps) {
7001   NVGpickSubPath* psp = ps.freeSubPaths;
7002   if (psp !is null) {
7003     ps.freeSubPaths = psp.next;
7004   } else {
7005     psp = cast(NVGpickSubPath*)malloc(NVGpickSubPath.sizeof);
7006     if (psp is null) return null;
7007   }
7008   memset(psp, 0, NVGpickSubPath.sizeof);
7009   return psp;
7010 }
7011 
7012 void nvg__returnPickSubPath (NVGpickScene* ps, NVGpickSubPath* psp) {
7013   psp.next = ps.freeSubPaths;
7014   ps.freeSubPaths = psp;
7015 }
7016 
7017 NVGpickScene* nvg__allocPickScene () {
7018   NVGpickScene* ps = cast(NVGpickScene*)malloc(NVGpickScene.sizeof);
7019   if (ps is null) return null;
7020   memset(ps, 0, NVGpickScene.sizeof);
7021   ps.nlevels = 5;
7022   return ps;
7023 }
7024 
7025 void nvg__deletePickScene (NVGpickScene* ps) {
7026   NVGpickPath* pp;
7027   NVGpickSubPath* psp;
7028 
7029   // Add all paths (and thus sub paths) to the free list(s).
7030   while (ps.paths !is null) {
7031     pp = ps.paths.next;
7032     nvg__freePickPath(ps, ps.paths);
7033     ps.paths = pp;
7034   }
7035 
7036   // Delete all paths
7037   while (ps.freePaths !is null) {
7038     pp = ps.freePaths;
7039     ps.freePaths = pp.next;
7040     while (pp.subPaths !is null) {
7041       psp = pp.subPaths;
7042       pp.subPaths = psp.next;
7043       free(psp);
7044     }
7045     free(pp);
7046   }
7047 
7048   // Delete all sub paths
7049   while (ps.freeSubPaths !is null) {
7050     psp = ps.freeSubPaths.next;
7051     free(ps.freeSubPaths);
7052     ps.freeSubPaths = psp;
7053   }
7054 
7055   ps.npoints = 0;
7056   ps.nsegments = 0;
7057 
7058   if (ps.levels !is null) {
7059     free(ps.levels[0]);
7060     free(ps.levels);
7061   }
7062 
7063   if (ps.picked !is null) free(ps.picked);
7064   if (ps.points !is null) free(ps.points);
7065   if (ps.segments !is null) free(ps.segments);
7066 
7067   free(ps);
7068 }
7069 
7070 NVGpickScene* nvg__pickSceneGet (NVGContext ctx) {
7071   if (ctx.pickScene is null) ctx.pickScene = nvg__allocPickScene();
7072   return ctx.pickScene;
7073 }
7074 
7075 
7076 // Applies Casteljau's algorithm to a cubic bezier for a given parameter t
7077 // points is 4 points (8 floats)
7078 // lvl1 is 3 points (6 floats)
7079 // lvl2 is 2 points (4 floats)
7080 // lvl3 is 1 point (2 floats)
7081 void nvg__casteljau (const(float)* points, float t, float* lvl1, float* lvl2, float* lvl3) {
7082   enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0;
7083   enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1;
7084 
7085   // Level 1
7086   lvl1[x0] = (points[x1]-points[x0])*t+points[x0];
7087   lvl1[y0] = (points[y1]-points[y0])*t+points[y0];
7088 
7089   lvl1[x1] = (points[x2]-points[x1])*t+points[x1];
7090   lvl1[y1] = (points[y2]-points[y1])*t+points[y1];
7091 
7092   lvl1[x2] = (points[x3]-points[x2])*t+points[x2];
7093   lvl1[y2] = (points[y3]-points[y2])*t+points[y2];
7094 
7095   // Level 2
7096   lvl2[x0] = (lvl1[x1]-lvl1[x0])*t+lvl1[x0];
7097   lvl2[y0] = (lvl1[y1]-lvl1[y0])*t+lvl1[y0];
7098 
7099   lvl2[x1] = (lvl1[x2]-lvl1[x1])*t+lvl1[x1];
7100   lvl2[y1] = (lvl1[y2]-lvl1[y1])*t+lvl1[y1];
7101 
7102   // Level 3
7103   lvl3[x0] = (lvl2[x1]-lvl2[x0])*t+lvl2[x0];
7104   lvl3[y0] = (lvl2[y1]-lvl2[y0])*t+lvl2[y0];
7105 }
7106 
7107 // Calculates a point on a bezier at point t.
7108 void nvg__bezierEval (const(float)* points, float t, ref float[2] tpoint) {
7109   immutable float omt = 1-t;
7110   immutable float omt3 = omt*omt*omt;
7111   immutable float omt2 = omt*omt;
7112   immutable float t3 = t*t*t;
7113   immutable float t2 = t*t;
7114 
7115   tpoint.ptr[0] =
7116     points[0]*omt3+
7117     points[2]*3.0f*omt2*t+
7118     points[4]*3.0f*omt*t2+
7119     points[6]*t3;
7120 
7121   tpoint.ptr[1] =
7122     points[1]*omt3+
7123     points[3]*3.0f*omt2*t+
7124     points[5]*3.0f*omt*t2+
7125     points[7]*t3;
7126 }
7127 
7128 // Splits a cubic bezier curve into two parts at point t.
7129 void nvg__splitBezier (const(float)* points, float t, float* pointsA, float* pointsB) {
7130   enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0;
7131   enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1;
7132 
7133   float[6] lvl1 = void;
7134   float[4] lvl2 = void;
7135   float[2] lvl3 = void;
7136 
7137   nvg__casteljau(points, t, lvl1.ptr, lvl2.ptr, lvl3.ptr);
7138 
7139   // First half
7140   pointsA[x0] = points[x0];
7141   pointsA[y0] = points[y0];
7142 
7143   pointsA[x1] = lvl1.ptr[x0];
7144   pointsA[y1] = lvl1.ptr[y0];
7145 
7146   pointsA[x2] = lvl2.ptr[x0];
7147   pointsA[y2] = lvl2.ptr[y0];
7148 
7149   pointsA[x3] = lvl3.ptr[x0];
7150   pointsA[y3] = lvl3.ptr[y0];
7151 
7152   // Second half
7153   pointsB[x0] = lvl3.ptr[x0];
7154   pointsB[y0] = lvl3.ptr[y0];
7155 
7156   pointsB[x1] = lvl2.ptr[x1];
7157   pointsB[y1] = lvl2.ptr[y1];
7158 
7159   pointsB[x2] = lvl1.ptr[x2];
7160   pointsB[y2] = lvl1.ptr[y2];
7161 
7162   pointsB[x3] = points[x3];
7163   pointsB[y3] = points[y3];
7164 }
7165 
7166 // Calculates the inflection points in coordinate coord (X = 0, Y = 1) of a cubic bezier.
7167 // Appends any found inflection points to the array inflections and increments *ninflections.
7168 // So finds the parameters where dx/dt or dy/dt is 0
7169 void nvg__bezierInflections (const(float)* points, int coord, int* ninflections, float* inflections) {
7170   immutable float v0 = points[0*2+coord], v1 = points[1*2+coord], v2 = points[2*2+coord], v3 = points[3*2+coord];
7171   float[2] t = void;
7172   int nvalid = *ninflections;
7173 
7174   immutable float a = 3.0f*( -v0+3.0f*v1-3.0f*v2+v3 );
7175   immutable float b = 6.0f*( v0-2.0f*v1+v2 );
7176   immutable float c = 3.0f*( v1-v0 );
7177 
7178   float d = b*b-4.0f*a*c;
7179   if (nvg__absf(d-0.0f) < NVGPickEPS) {
7180     // Zero or one root
7181     t.ptr[0] = -b/2.0f*a;
7182     if (t.ptr[0] > NVGPickEPS && t.ptr[0] < (1.0f-NVGPickEPS)) {
7183       inflections[nvalid] = t.ptr[0];
7184       ++nvalid;
7185     }
7186   } else if (d > NVGPickEPS) {
7187     // zero, one or two roots
7188     d = nvg__sqrtf(d);
7189 
7190     t.ptr[0] = (-b+d)/(2.0f*a);
7191     t.ptr[1] = (-b-d)/(2.0f*a);
7192 
7193     for (int i = 0; i < 2; ++i) {
7194       if (t.ptr[i] > NVGPickEPS && t.ptr[i] < (1.0f-NVGPickEPS)) {
7195         inflections[nvalid] = t.ptr[i];
7196         ++nvalid;
7197       }
7198     }
7199   } else {
7200     // zero roots
7201   }
7202 
7203   *ninflections = nvalid;
7204 }
7205 
7206 // Sort a small number of floats in ascending order (0 < n < 6)
7207 void nvg__smallsort (float* values, int n) {
7208   bool bSwapped = true;
7209   for (int j = 0; j < n-1 && bSwapped; ++j) {
7210     bSwapped = false;
7211     for (int i = 0; i < n-1; ++i) {
7212       if (values[i] > values[i+1]) {
7213         auto tmp = values[i];
7214         values[i] = values[i+1];
7215         values[i+1] = tmp;
7216       }
7217     }
7218   }
7219 }
7220 
7221 // Calculates the bounding rect of a given cubic bezier curve.
7222 void nvg__bezierBounds (const(float)* points, ref float[4] bounds) {
7223   float[4] inflections = void;
7224   int ninflections = 0;
7225   float[2] tpoint = void;
7226 
7227   nvg__initBounds(bounds);
7228 
7229   // Include start and end points in bounds
7230   nvg__expandBounds(bounds, &points[0], 1);
7231   nvg__expandBounds(bounds, &points[6], 1);
7232 
7233   // Calculate dx==0 and dy==0 inflection points and add them to the bounds
7234 
7235   nvg__bezierInflections(points, 0, &ninflections, inflections.ptr);
7236   nvg__bezierInflections(points, 1, &ninflections, inflections.ptr);
7237 
7238   foreach (immutable int i; 0..ninflections) {
7239     nvg__bezierEval(points, inflections[i], tpoint);
7240     nvg__expandBounds(bounds, tpoint.ptr, 1);
7241   }
7242 }
7243 
7244 // Checks to see if a line originating from x,y along the +ve x axis
7245 // intersects the given line (points[0],points[1]) -> (points[2], points[3]).
7246 // Returns `true` on intersection.
7247 // Horizontal lines are never hit.
7248 bool nvg__intersectLine (const(float)* points, float x, float y) {
7249   immutable float x1 = points[0];
7250   immutable float y1 = points[1];
7251   immutable float x2 = points[2];
7252   immutable float y2 = points[3];
7253   immutable float d = y2-y1;
7254   if (d > NVGPickEPS || d < -NVGPickEPS) {
7255     immutable float s = (x2-x1)/d;
7256     immutable float lineX = x1+(y-y1)*s;
7257     return (lineX > x);
7258   } else {
7259     return false;
7260   }
7261 }
7262 
7263 // Checks to see if a line originating from x,y along the +ve x axis intersects the given bezier.
7264 // It is assumed that the line originates from within the bounding box of
7265 // the bezier and that the curve has no dy=0 inflection points.
7266 // Returns the number of intersections found (which is either 1 or 0).
7267 int nvg__intersectBezier (const(float)* points, float x, float y) {
7268   immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0];
7269   immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1];
7270 
7271   if (y0 == y1 && y1 == y2 && y2 == y3) return 0;
7272 
7273   // Initial t guess
7274   float t = void;
7275        if (y3 != y0) t = (y-y0)/(y3-y0);
7276   else if (x3 != x0) t = (x-x0)/(x3-x0);
7277   else t = 0.5f;
7278 
7279   // A few Newton iterations
7280   for (int iter = 0; iter < 6; ++iter) {
7281     immutable float omt = 1-t;
7282     immutable float omt2 = omt*omt;
7283     immutable float t2 = t*t;
7284     immutable float omt3 = omt2*omt;
7285     immutable float t3 = t2*t;
7286 
7287     immutable float ty = y0*omt3 +
7288       y1*3.0f*omt2*t +
7289       y2*3.0f*omt*t2 +
7290       y3*t3;
7291 
7292     // Newton iteration
7293     immutable float dty = 3.0f*omt2*(y1-y0) +
7294       6.0f*omt*t*(y2-y1) +
7295       3.0f*t2*(y3-y2);
7296 
7297     // dty will never == 0 since:
7298     //  Either omt, omt2 are zero OR t2 is zero
7299     //  y0 != y1 != y2 != y3 (checked above)
7300     t = t-(ty-y)/dty;
7301   }
7302 
7303   {
7304     immutable float omt = 1-t;
7305     immutable float omt2 = omt*omt;
7306     immutable float t2 = t*t;
7307     immutable float omt3 = omt2*omt;
7308     immutable float t3 = t2*t;
7309 
7310     immutable float tx =
7311       x0*omt3+
7312       x1*3.0f*omt2*t+
7313       x2*3.0f*omt*t2+
7314       x3*t3;
7315 
7316     return (tx > x ? 1 : 0);
7317   }
7318 }
7319 
7320 // Finds the closest point on a line to a given point
7321 void nvg__closestLine (const(float)* points, float x, float y, float* closest, float* ot) {
7322   immutable float x1 = points[0];
7323   immutable float y1 = points[1];
7324   immutable float x2 = points[2];
7325   immutable float y2 = points[3];
7326   immutable float pqx = x2-x1;
7327   immutable float pqz = y2-y1;
7328   immutable float dx = x-x1;
7329   immutable float dz = y-y1;
7330   immutable float d = pqx*pqx+pqz*pqz;
7331   float t = pqx*dx+pqz*dz;
7332   if (d > 0) t /= d;
7333   if (t < 0) t = 0; else if (t > 1) t = 1;
7334   closest[0] = x1+t*pqx;
7335   closest[1] = y1+t*pqz;
7336   *ot = t;
7337 }
7338 
7339 // Finds the closest point on a curve for a given point (x,y).
7340 // Assumes that the curve has no dx==0 or dy==0 inflection points.
7341 void nvg__closestBezier (const(float)* points, float x, float y, float* closest, float *ot) {
7342   immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0];
7343   immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1];
7344 
7345   // This assumes that the curve has no dy=0 inflection points.
7346 
7347   // Initial t guess
7348   float t = 0.5f;
7349 
7350   // A few Newton iterations
7351   for (int iter = 0; iter < 6; ++iter) {
7352     immutable float omt = 1-t;
7353     immutable float omt2 = omt*omt;
7354     immutable float t2 = t*t;
7355     immutable float omt3 = omt2*omt;
7356     immutable float t3 = t2*t;
7357 
7358     immutable float ty =
7359       y0*omt3+
7360       y1*3.0f*omt2*t+
7361       y2*3.0f*omt*t2+
7362       y3*t3;
7363 
7364     immutable float tx =
7365       x0*omt3+
7366       x1*3.0f*omt2*t+
7367       x2*3.0f*omt*t2+
7368       x3*t3;
7369 
7370     // Newton iteration
7371     immutable float dty =
7372       3.0f*omt2*(y1-y0)+
7373       6.0f*omt*t*(y2-y1)+
7374       3.0f*t2*(y3-y2);
7375 
7376     immutable float ddty =
7377       6.0f*omt*(y2-2.0f*y1+y0)+
7378       6.0f*t*(y3-2.0f*y2+y1);
7379 
7380     immutable float dtx =
7381       3.0f*omt2*(x1-x0)+
7382       6.0f*omt*t*(x2-x1)+
7383       3.0f*t2*(x3-x2);
7384 
7385     immutable float ddtx =
7386       6.0f*omt*(x2-2.0f*x1+x0)+
7387       6.0f*t*(x3-2.0f*x2+x1);
7388 
7389     immutable float errorx = tx-x;
7390     immutable float errory = ty-y;
7391 
7392     immutable float n = errorx*dtx+errory*dty;
7393     if (n == 0) break;
7394 
7395     immutable float d = dtx*dtx+dty*dty+errorx*ddtx+errory*ddty;
7396     if (d != 0) t = t-n/d; else break;
7397   }
7398 
7399   t = nvg__max(0, nvg__min(1.0, t));
7400   *ot = t;
7401   {
7402     immutable float omt = 1-t;
7403     immutable float omt2 = omt*omt;
7404     immutable float t2 = t*t;
7405     immutable float omt3 = omt2*omt;
7406     immutable float t3 = t2*t;
7407 
7408     immutable float ty =
7409       y0*omt3+
7410       y1*3.0f*omt2*t+
7411       y2*3.0f*omt*t2+
7412       y3*t3;
7413 
7414     immutable float tx =
7415       x0*omt3+
7416       x1*3.0f*omt2*t+
7417       x2*3.0f*omt*t2+
7418       x3*t3;
7419 
7420     closest[0] = tx;
7421     closest[1] = ty;
7422   }
7423 }
7424 
7425 // Returns:
7426 //  1  If (x,y) is contained by the stroke of the path
7427 //  0  If (x,y) is not contained by the path.
7428 int nvg__pickSubPathStroke (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, float strokeWidth, int lineCap, int lineJoin) {
7429   if (!nvg__pointInBounds(x, y, psp.bounds)) return 0;
7430   if (psp.firstSegment == -1) return 0;
7431 
7432   float[2] closest = void;
7433   float[2] d = void;
7434   float t = void;
7435 
7436   // trace a line from x,y out along the positive x axis and count the number of intersections
7437   int nsegments = psp.nsegments;
7438   const(NVGsegment)* seg = ps.segments+psp.firstSegment;
7439   const(NVGsegment)* prevseg = (psp.closed ? &ps.segments[psp.firstSegment+nsegments-1] : null);
7440   immutable float strokeWidthSqd = strokeWidth*strokeWidth;
7441 
7442   for (int s = 0; s < nsegments; ++s, prevseg = seg, ++seg) {
7443     if (nvg__pointInBounds(x, y, seg.bounds)) {
7444       // Line potentially hits stroke.
7445       switch (seg.type) {
7446         case Command.LineTo:
7447           nvg__closestLine(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t);
7448           break;
7449         case Command.BezierTo:
7450           nvg__closestBezier(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t);
7451           break;
7452         default:
7453           continue;
7454       }
7455 
7456       d.ptr[0] = x-closest.ptr[0];
7457       d.ptr[1] = y-closest.ptr[1];
7458 
7459       if ((t >= NVGPickEPS && t <= 1.0f-NVGPickEPS) ||
7460           (seg.flags&(NVGSegmentFlags.Corner|NVGSegmentFlags.Cap|NVGSegmentFlags.Endcap)) == 0 ||
7461           (lineJoin == NVGLineCap.Round))
7462       {
7463         // Closest point is in the middle of the line/curve, at a rounded join/cap
7464         // or at a smooth join
7465         immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7466         if (distSqd < strokeWidthSqd) return 1;
7467       } else if ((t > 1.0f-NVGPickEPS && (seg.flags&NVGSegmentFlags.Endcap)) ||
7468                  (t < NVGPickEPS && (seg.flags&NVGSegmentFlags.Cap))) {
7469         switch (lineCap) {
7470           case NVGLineCap.Butt:
7471             immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7472             immutable float dirD = (t < NVGPickEPS ?
7473               -(d.ptr[0]*seg.startDir.ptr[0]+d.ptr[1]*seg.startDir.ptr[1]) :
7474                 d.ptr[0]*seg.endDir.ptr[0]+d.ptr[1]*seg.endDir.ptr[1]);
7475             if (dirD < -NVGPickEPS && distSqd < strokeWidthSqd) return 1;
7476             break;
7477           case NVGLineCap.Square:
7478             if (nvg__absf(d.ptr[0]) < strokeWidth && nvg__absf(d.ptr[1]) < strokeWidth) return 1;
7479             break;
7480           case NVGLineCap.Round:
7481             immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7482             if (distSqd < strokeWidthSqd) return 1;
7483             break;
7484           default:
7485             break;
7486         }
7487       } else if (seg.flags&NVGSegmentFlags.Corner) {
7488         // Closest point is at a corner
7489         const(NVGsegment)* seg0, seg1;
7490 
7491         if (t < NVGPickEPS) {
7492           seg0 = prevseg;
7493           seg1 = seg;
7494         } else {
7495           seg0 = seg;
7496           seg1 = (s == nsegments-1 ? &ps.segments[psp.firstSegment] : seg+1);
7497         }
7498 
7499         if (!(seg1.flags&NVGSegmentFlags.Bevel)) {
7500           immutable float prevNDist = -seg0.endDir.ptr[1]*d.ptr[0]+seg0.endDir.ptr[0]*d.ptr[1];
7501           immutable float curNDist = seg1.startDir.ptr[1]*d.ptr[0]-seg1.startDir.ptr[0]*d.ptr[1];
7502           if (nvg__absf(prevNDist) < strokeWidth && nvg__absf(curNDist) < strokeWidth) return 1;
7503         } else {
7504           d.ptr[0] -= -seg1.startDir.ptr[1]*strokeWidth;
7505           d.ptr[1] -= +seg1.startDir.ptr[0]*strokeWidth;
7506           if (seg1.miterDir.ptr[0]*d.ptr[0]+seg1.miterDir.ptr[1]*d.ptr[1] < 0) return 1;
7507         }
7508       }
7509     }
7510   }
7511 
7512   return 0;
7513 }
7514 
7515 // Returns:
7516 //   1  If (x,y) is contained by the path and the path is solid.
7517 //  -1  If (x,y) is contained by the path and the path is a hole.
7518 //   0  If (x,y) is not contained by the path.
7519 int nvg__pickSubPath (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, bool evenOddMode) {
7520   if (!nvg__pointInBounds(x, y, psp.bounds)) return 0;
7521   if (psp.firstSegment == -1) return 0;
7522 
7523   const(NVGsegment)* seg = &ps.segments[psp.firstSegment];
7524   int nsegments = psp.nsegments;
7525   int nintersections = 0;
7526 
7527   // trace a line from x,y out along the positive x axis and count the number of intersections
7528   for (int s = 0; s < nsegments; ++s, ++seg) {
7529     if ((seg.bounds.ptr[1]-NVGPickEPS) < y &&
7530         (seg.bounds.ptr[3]-NVGPickEPS) > y &&
7531         seg.bounds.ptr[2] > x)
7532     {
7533       // Line hits the box.
7534       switch (seg.type) {
7535         case Command.LineTo:
7536           if (seg.bounds.ptr[0] > x) {
7537             // line originates outside the box
7538             ++nintersections;
7539           } else {
7540             // line originates inside the box
7541             nintersections += nvg__intersectLine(&ps.points[seg.firstPoint*2], x, y);
7542           }
7543           break;
7544         case Command.BezierTo:
7545           if (seg.bounds.ptr[0] > x) {
7546             // line originates outside the box
7547             ++nintersections;
7548           } else {
7549             // line originates inside the box
7550             nintersections += nvg__intersectBezier(&ps.points[seg.firstPoint*2], x, y);
7551           }
7552           break;
7553         default:
7554           break;
7555       }
7556     }
7557   }
7558 
7559   if (evenOddMode) {
7560     return nintersections;
7561   } else {
7562     return (nintersections&1 ? (psp.winding == NVGSolidity.Solid ? 1 : -1) : 0);
7563   }
7564 }
7565 
7566 bool nvg__pickPath (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) {
7567   int pickCount = 0;
7568   const(NVGpickSubPath)* psp = pp.subPaths;
7569   while (psp !is null) {
7570     pickCount += nvg__pickSubPath(ps, psp, x, y, pp.evenOddMode);
7571     psp = psp.next;
7572   }
7573   return ((pp.evenOddMode ? pickCount&1 : pickCount) != 0);
7574 }
7575 
7576 bool nvg__pickPathStroke (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) {
7577   const(NVGpickSubPath)* psp = pp.subPaths;
7578   while (psp !is null) {
7579     if (nvg__pickSubPathStroke(ps, psp, x, y, pp.strokeWidth, pp.lineCap, pp.lineJoin)) return true;
7580     psp = psp.next;
7581   }
7582   return false;
7583 }
7584 
7585 bool nvg__pickPathTestBounds (NVGContext ctx, const NVGpickScene* ps, const NVGpickPath* pp, float x, float y) {
7586   if (nvg__pointInBounds(x, y, pp.bounds)) {
7587     //{ import core.stdc.stdio; printf("  (0): in bounds!\n"); }
7588     if (pp.flags&NVGPathFlags.Scissor) {
7589       const(float)* scissor = &ps.points[pp.scissor*2];
7590       // untransform scissor translation
7591       float stx = void, sty = void;
7592       ctx.gpuUntransformPoint(&stx, &sty, scissor[4], scissor[5]);
7593       immutable float rx = x-stx;
7594       immutable float ry = y-sty;
7595       //{ 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]); }
7596       if (nvg__absf((scissor[0]*rx)+(scissor[1]*ry)) > scissor[6] ||
7597           nvg__absf((scissor[2]*rx)+(scissor[3]*ry)) > scissor[7])
7598       {
7599         //{ import core.stdc.stdio; printf("    (1): scissor reject!\n"); }
7600         return false;
7601       }
7602     }
7603     return true;
7604   }
7605   return false;
7606 }
7607 
7608 int nvg__countBitsUsed (uint v) pure {
7609   pragma(inline, true);
7610   import core.bitop : bsr;
7611   return (v != 0 ? bsr(v)+1 : 0);
7612 }
7613 
7614 void nvg__pickSceneInsert (NVGpickScene* ps, NVGpickPath* pp) {
7615   if (ps is null || pp is null) return;
7616 
7617   int[4] cellbounds;
7618   int base = ps.nlevels-1;
7619   int level;
7620   int levelwidth;
7621   int levelshift;
7622   int levelx;
7623   int levely;
7624   NVGpickPath** cell = null;
7625 
7626   // Bit tricks for inserting into an implicit quadtree.
7627 
7628   // Calc bounds of path in cells at the lowest level
7629   cellbounds.ptr[0] = cast(int)(pp.bounds.ptr[0]/ps.xdim);
7630   cellbounds.ptr[1] = cast(int)(pp.bounds.ptr[1]/ps.ydim);
7631   cellbounds.ptr[2] = cast(int)(pp.bounds.ptr[2]/ps.xdim);
7632   cellbounds.ptr[3] = cast(int)(pp.bounds.ptr[3]/ps.ydim);
7633 
7634   // Find which bits differ between the min/max x/y coords
7635   cellbounds.ptr[0] ^= cellbounds.ptr[2];
7636   cellbounds.ptr[1] ^= cellbounds.ptr[3];
7637 
7638   // Use the number of bits used (countBitsUsed(x) == sizeof(int) * 8 - clz(x);
7639   // to calculate the level to insert at (the level at which the bounds fit in a single cell)
7640   level = nvg__min(base-nvg__countBitsUsed(cellbounds.ptr[0]), base-nvg__countBitsUsed(cellbounds.ptr[1]));
7641   if (level < 0) level = 0;
7642   //{ 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]); }
7643   //level = 0;
7644 
7645   // Find the correct cell in the chosen level, clamping to the edges.
7646   levelwidth = 1<<level;
7647   levelshift = (ps.nlevels-level)-1;
7648   levelx = nvg__clamp(cellbounds.ptr[2]>>levelshift, 0, levelwidth-1);
7649   levely = nvg__clamp(cellbounds.ptr[3]>>levelshift, 0, levelwidth-1);
7650 
7651   // Insert the path into the linked list at that cell.
7652   cell = &ps.levels[level][levely*levelwidth+levelx];
7653 
7654   pp.cellnext = *cell;
7655   *cell = pp;
7656 
7657   if (ps.paths is null) ps.lastPath = pp;
7658   pp.next = ps.paths;
7659   ps.paths = pp;
7660 
7661   // Store the order (depth) of the path for picking ops.
7662   pp.order = cast(short)ps.npaths;
7663   ++ps.npaths;
7664 }
7665 
7666 void nvg__pickBeginFrame (NVGContext ctx, int width, int height) {
7667   NVGpickScene* ps = nvg__pickSceneGet(ctx);
7668 
7669   //NVG_PICK_DEBUG_NEWFRAME();
7670 
7671   // Return all paths & sub paths from last frame to the free list
7672   while (ps.paths !is null) {
7673     NVGpickPath* pp = ps.paths.next;
7674     nvg__freePickPath(ps, ps.paths);
7675     ps.paths = pp;
7676   }
7677 
7678   ps.paths = null;
7679   ps.npaths = 0;
7680 
7681   // Store the screen metrics for the quadtree
7682   ps.width = width;
7683   ps.height = height;
7684 
7685   immutable float lowestSubDiv = cast(float)(1<<(ps.nlevels-1));
7686   ps.xdim = cast(float)width/lowestSubDiv;
7687   ps.ydim = cast(float)height/lowestSubDiv;
7688 
7689   // Allocate the quadtree if required.
7690   if (ps.levels is null) {
7691     int ncells = 1;
7692 
7693     ps.levels = cast(NVGpickPath***)malloc((NVGpickPath**).sizeof*ps.nlevels);
7694     for (int l = 0; l < ps.nlevels; ++l) {
7695       int leveldim = 1<<l;
7696       ncells += leveldim*leveldim;
7697     }
7698 
7699     ps.levels[0] = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ncells);
7700 
7701     int cell = 1;
7702     for (int l = 1; l < ps.nlevels; ++l) {
7703       ps.levels[l] = &ps.levels[0][cell];
7704       int leveldim = 1<<l;
7705       cell += leveldim*leveldim;
7706     }
7707 
7708     ps.ncells = ncells;
7709   }
7710   memset(ps.levels[0], 0, ps.ncells*(NVGpickPath*).sizeof);
7711 
7712   // Allocate temporary storage for nvgHitTestAll results if required.
7713   if (ps.picked is null) {
7714     ps.cpicked = 16;
7715     ps.picked = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ps.cpicked);
7716   }
7717 
7718   ps.npoints = 0;
7719   ps.nsegments = 0;
7720 }
7721 } // nothrow @trusted @nogc
7722 
7723 
7724 /// Return outline of the current path. Returned outline is not flattened.
7725 /// Group: paths
7726 public NVGPathOutline getCurrPathOutline (NVGContext ctx) nothrow @trusted @nogc {
7727   if (ctx is null || !ctx.contextAlive || ctx.ncommands == 0) return NVGPathOutline.init;
7728 
7729   auto res = NVGPathOutline.createNew();
7730 
7731   const(float)[] acommands = ctx.commands[0..ctx.ncommands];
7732   int ncommands = cast(int)acommands.length;
7733   const(float)* commands = acommands.ptr;
7734 
7735   float cx = 0, cy = 0;
7736   float[2] start = void;
7737   float[4] totalBounds = [float.max, float.max, -float.max, -float.max];
7738   float[8] bcp = void; // bezier curve points; used to calculate bounds
7739 
7740   void addToBounds (in float x, in float y) nothrow @trusted @nogc {
7741     totalBounds.ptr[0] = nvg__min(totalBounds.ptr[0], x);
7742     totalBounds.ptr[1] = nvg__min(totalBounds.ptr[1], y);
7743     totalBounds.ptr[2] = nvg__max(totalBounds.ptr[2], x);
7744     totalBounds.ptr[3] = nvg__max(totalBounds.ptr[3], y);
7745   }
7746 
7747   bool hasPoints = false;
7748 
7749   void closeIt () nothrow @trusted @nogc {
7750     if (!hasPoints) return;
7751     if (cx != start.ptr[0] || cy != start.ptr[1]) {
7752       res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo);
7753       res.ds.putArgs(start[]);
7754       cx = start.ptr[0];
7755       cy = start.ptr[1];
7756       addToBounds(cx, cy);
7757     }
7758   }
7759 
7760   int i = 0;
7761   while (i < ncommands) {
7762     int cmd = cast(int)commands[i++];
7763     switch (cmd) {
7764       case Command.MoveTo: // one coordinate pair
7765         const(float)* tfxy = commands+i;
7766         i += 2;
7767         // add command
7768         res.ds.putCommand(NVGPathOutline.Command.Kind.MoveTo);
7769         res.ds.putArgs(tfxy[0..2]);
7770         // new starting point
7771         start.ptr[0..2] = tfxy[0..2];
7772         cx = tfxy[0];
7773         cy = tfxy[0];
7774         addToBounds(cx, cy);
7775         hasPoints = true;
7776         break;
7777       case Command.LineTo: // one coordinate pair
7778         const(float)* tfxy = commands+i;
7779         i += 2;
7780         // add command
7781         res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo);
7782         res.ds.putArgs(tfxy[0..2]);
7783         cx = tfxy[0];
7784         cy = tfxy[0];
7785         addToBounds(cx, cy);
7786         hasPoints = true;
7787         break;
7788       case Command.BezierTo: // three coordinate pairs
7789         const(float)* tfxy = commands+i;
7790         i += 3*2;
7791         // add command
7792         res.ds.putCommand(NVGPathOutline.Command.Kind.BezierTo);
7793         res.ds.putArgs(tfxy[0..6]);
7794         // bounds
7795         bcp.ptr[0] = cx;
7796         bcp.ptr[1] = cy;
7797         bcp.ptr[2..8] = tfxy[0..6];
7798         nvg__bezierBounds(bcp.ptr, totalBounds);
7799         cx = tfxy[4];
7800         cy = tfxy[5];
7801         hasPoints = true;
7802         break;
7803       case Command.Close:
7804         closeIt();
7805         hasPoints = false;
7806         break;
7807       case Command.Winding:
7808         //psp.winding = cast(short)cast(int)commands[i];
7809         i += 1;
7810         break;
7811       default:
7812         break;
7813     }
7814   }
7815 
7816   res.ds.bounds[] = totalBounds[];
7817   return res;
7818 }
7819 
7820 
7821 // ////////////////////////////////////////////////////////////////////////// //
7822 // Text
7823 
7824 /** Creates font by loading it from the disk from specified file name.
7825  * Returns handle to the font or FONS_INVALID (aka -1) on error.
7826  * Use "fontname:noaa" as [name] to turn off antialiasing (if font driver supports that).
7827  *
7828  * On POSIX systems it is possible to use fontconfig font names too.
7829  * `:noaa` in font path is still allowed, but it must be the last option.
7830  *
7831  * Group: text_api
7832  */
7833 public int createFont (NVGContext ctx, const(char)[] name, const(char)[] path) nothrow @trusted {
7834   return ctx.fs.addFont(name, path, ctx.params.fontAA);
7835 }
7836 
7837 /** Creates font by loading it from the specified memory chunk.
7838  * Returns handle to the font or FONS_INVALID (aka -1) on error.
7839  * Won't free data on error.
7840  *
7841  * Group: text_api
7842  */
7843 public int createFontMem (NVGContext ctx, const(char)[] name, ubyte* data, int ndata, bool freeData) nothrow @trusted @nogc {
7844   return ctx.fs.addFontMem(name, data, ndata, freeData, ctx.params.fontAA);
7845 }
7846 
7847 /// Add fonts from another context.
7848 /// This is more effective than reloading fonts, 'cause font data will be shared.
7849 /// Group: text_api
7850 public void addFontsFrom (NVGContext ctx, NVGContext source) nothrow @trusted @nogc {
7851   if (ctx is null || source is null) return;
7852   ctx.fs.addFontsFrom(source.fs);
7853 }
7854 
7855 /// Adds the font with id `fallback` onto the font with id `base` for characters
7856 /// that are not found in `base`'s character set. May be called multiple times,
7857 /// up to an internal maximum count per font.
7858 /// Group: text_api
7859 /// Returns: `true` if successful, `false` if not
7860 public bool addFallbackFont (NVGContext ctx, int base, int fallback) nothrow @trusted @nogc {
7861   if (ctx is null) return false;
7862   return ctx.fs.addFallbackFont(base, fallback);
7863 }
7864 
7865 /// Finds a loaded font of specified name, and returns handle to it, or FONS_INVALID (aka -1) if the font is not found.
7866 /// Group: text_api
7867 public int findFont (NVGContext ctx, const(char)[] name) nothrow @trusted @nogc {
7868   pragma(inline, true);
7869   return (name.length == 0 ? FONS_INVALID : ctx.fs.getFontByName(name));
7870 }
7871 
7872 /// Sets the font size of current text style.
7873 /// Group: text_api
7874 public void fontSize (NVGContext ctx, float size) nothrow @trusted @nogc {
7875   pragma(inline, true);
7876   nvg__getState(ctx).fontSize = size;
7877 }
7878 
7879 /// Gets the font size of current text style.
7880 /// Group: text_api
7881 public float fontSize (NVGContext ctx) nothrow @trusted @nogc {
7882   pragma(inline, true);
7883   return nvg__getState(ctx).fontSize;
7884 }
7885 
7886 /// Sets the blur of current text style.
7887 /// Group: text_api
7888 public void fontBlur (NVGContext ctx, float blur) nothrow @trusted @nogc {
7889   pragma(inline, true);
7890   nvg__getState(ctx).fontBlur = blur;
7891 }
7892 
7893 /// Gets the blur of current text style.
7894 /// Group: text_api
7895 public float fontBlur (NVGContext ctx) nothrow @trusted @nogc {
7896   pragma(inline, true);
7897   return nvg__getState(ctx).fontBlur;
7898 }
7899 
7900 /// Sets the letter spacing of current text style.
7901 /// Group: text_api
7902 public void textLetterSpacing (NVGContext ctx, float spacing) nothrow @trusted @nogc {
7903   pragma(inline, true);
7904   nvg__getState(ctx).letterSpacing = spacing;
7905 }
7906 
7907 /// Gets the letter spacing of current text style.
7908 /// Group: text_api
7909 public float textLetterSpacing (NVGContext ctx) nothrow @trusted @nogc {
7910   pragma(inline, true);
7911   return nvg__getState(ctx).letterSpacing;
7912 }
7913 
7914 /// Sets the proportional line height of current text style. The line height is specified as multiple of font size.
7915 /// Group: text_api
7916 public void textLineHeight (NVGContext ctx, float lineHeight) nothrow @trusted @nogc {
7917   pragma(inline, true);
7918   nvg__getState(ctx).lineHeight = lineHeight;
7919 }
7920 
7921 /// Gets the proportional line height of current text style. The line height is specified as multiple of font size.
7922 /// Group: text_api
7923 public float textLineHeight (NVGContext ctx) nothrow @trusted @nogc {
7924   pragma(inline, true);
7925   return nvg__getState(ctx).lineHeight;
7926 }
7927 
7928 /// Sets the text align of current text style, see [NVGTextAlign] for options.
7929 /// Group: text_api
7930 public void textAlign (NVGContext ctx, NVGTextAlign talign) nothrow @trusted @nogc {
7931   pragma(inline, true);
7932   nvg__getState(ctx).textAlign = talign;
7933 }
7934 
7935 /// Ditto.
7936 public void textAlign (NVGContext ctx, NVGTextAlign.H h) nothrow @trusted @nogc {
7937   pragma(inline, true);
7938   nvg__getState(ctx).textAlign.horizontal = h;
7939 }
7940 
7941 /// Ditto.
7942 public void textAlign (NVGContext ctx, NVGTextAlign.V v) nothrow @trusted @nogc {
7943   pragma(inline, true);
7944   nvg__getState(ctx).textAlign.vertical = v;
7945 }
7946 
7947 /// Ditto.
7948 public void textAlign (NVGContext ctx, NVGTextAlign.H h, NVGTextAlign.V v) nothrow @trusted @nogc {
7949   pragma(inline, true);
7950   nvg__getState(ctx).textAlign.reset(h, v);
7951 }
7952 
7953 /// Ditto.
7954 public void textAlign (NVGContext ctx, NVGTextAlign.V v, NVGTextAlign.H h) nothrow @trusted @nogc {
7955   pragma(inline, true);
7956   nvg__getState(ctx).textAlign.reset(h, v);
7957 }
7958 
7959 /// Gets the text align of current text style, see [NVGTextAlign] for options.
7960 /// Group: text_api
7961 public NVGTextAlign textAlign (NVGContext ctx) nothrow @trusted @nogc {
7962   pragma(inline, true);
7963   return nvg__getState(ctx).textAlign;
7964 }
7965 
7966 /// Sets the font face based on specified id of current text style.
7967 /// Group: text_api
7968 public void fontFaceId (NVGContext ctx, int font) nothrow @trusted @nogc {
7969   pragma(inline, true);
7970   nvg__getState(ctx).fontId = font;
7971 }
7972 
7973 /// Gets the font face based on specified id of current text style.
7974 /// Group: text_api
7975 public int fontFaceId (NVGContext ctx) nothrow @trusted @nogc {
7976   pragma(inline, true);
7977   return nvg__getState(ctx).fontId;
7978 }
7979 
7980 /** Sets the font face based on specified name of current text style.
7981  *
7982  * The underlying implementation is using O(1) data structure to lookup
7983  * font names, so you probably should use this function instead of [fontFaceId]
7984  * to make your code more robust and less error-prone.
7985  *
7986  * Group: text_api
7987  */
7988 public void fontFace (NVGContext ctx, const(char)[] font) nothrow @trusted @nogc {
7989   pragma(inline, true);
7990   nvg__getState(ctx).fontId = ctx.fs.getFontByName(font);
7991 }
7992 
7993 static if (is(typeof(&fons__nvg__toPath))) {
7994   public enum NanoVegaHasCharToPath = true; ///
7995 } else {
7996   public enum NanoVegaHasCharToPath = false; ///
7997 }
7998 
7999 /// Adds glyph outlines to the current path. Vertical 0 is baseline.
8000 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead.
8001 /// Returns `false` if there is no such glyph, or current font is not scalable.
8002 /// Group: text_api
8003 public bool charToPath (NVGContext ctx, dchar dch, float[] bounds=null) nothrow @trusted @nogc {
8004   NVGstate* state = nvg__getState(ctx);
8005   ctx.fs.fontId = state.fontId;
8006   return ctx.fs.toPath(ctx, dch, bounds);
8007 }
8008 
8009 static if (is(typeof(&fons__nvg__bounds))) {
8010   public enum NanoVegaHasCharPathBounds = true; ///
8011 } else {
8012   public enum NanoVegaHasCharPathBounds = false; ///
8013 }
8014 
8015 /// Returns bounds of the glyph outlines. Vertical 0 is baseline.
8016 /// The glyph is not scaled in any way.
8017 /// Returns `false` if there is no such glyph, or current font is not scalable.
8018 /// Group: text_api
8019 public bool charPathBounds (NVGContext ctx, dchar dch, float[] bounds) nothrow @trusted @nogc {
8020   NVGstate* state = nvg__getState(ctx);
8021   ctx.fs.fontId = state.fontId;
8022   return ctx.fs.getPathBounds(dch, bounds);
8023 }
8024 
8025 /** [charOutline] will return [NVGPathOutline].
8026 
8027  some usage samples:
8028 
8029  ---
8030     float[4] bounds = void;
8031 
8032     nvg.scale(0.5, 0.5);
8033     nvg.translate(500, 800);
8034     nvg.evenOddFill;
8035 
8036     nvg.newPath();
8037     nvg.charToPath('&', bounds[]);
8038     conwriteln(bounds[]);
8039     nvg.fillPaint(nvg.linearGradient(0, 0, 600, 600, NVGColor("#f70"), NVGColor("#ff0")));
8040     nvg.strokeColor(NVGColor("#0f0"));
8041     nvg.strokeWidth = 3;
8042     nvg.fill();
8043     nvg.stroke();
8044     // glyph bounds
8045     nvg.newPath();
8046     nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
8047     nvg.strokeColor(NVGColor("#00f"));
8048     nvg.stroke();
8049 
8050     nvg.newPath();
8051     nvg.charToPath('g', bounds[]);
8052     conwriteln(bounds[]);
8053     nvg.fill();
8054     nvg.strokeColor(NVGColor("#0f0"));
8055     nvg.stroke();
8056     // glyph bounds
8057     nvg.newPath();
8058     nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
8059     nvg.strokeColor(NVGColor("#00f"));
8060     nvg.stroke();
8061 
8062     nvg.newPath();
8063     nvg.moveTo(0, 0);
8064     nvg.lineTo(600, 0);
8065     nvg.strokeColor(NVGColor("#0ff"));
8066     nvg.stroke();
8067 
8068     if (auto ol = nvg.charOutline('Q')) {
8069       scope(exit) ol.kill();
8070       nvg.newPath();
8071       conwriteln("==== length: ", ol.length, " ====");
8072       foreach (const ref cmd; ol.commands) {
8073         //conwriteln("  ", cmd.code, ": ", cmd.args[]);
8074         assert(cmd.valid);
8075         final switch (cmd.code) {
8076           case cmd.Kind.MoveTo: nvg.moveTo(cmd.args[0], cmd.args[1]); break;
8077           case cmd.Kind.LineTo: nvg.lineTo(cmd.args[0], cmd.args[1]); break;
8078           case cmd.Kind.QuadTo: nvg.quadTo(cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]); break;
8079           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;
8080         }
8081       }
8082       nvg.strokeColor(NVGColor("#f00"));
8083       nvg.stroke();
8084     }
8085  ---
8086 
8087  Group: text_api
8088  */
8089 public struct NVGPathOutline {
8090 private nothrow @trusted @nogc:
8091   struct DataStore {
8092     uint rc; // refcount
8093     ubyte* data;
8094     uint used;
8095     uint size;
8096     uint ccount; // number of commands
8097     float[4] bounds = 0; /// outline bounds
8098   nothrow @trusted @nogc:
8099     void putBytes (const(void)[] b) {
8100       if (b.length == 0) return;
8101       if (b.length >= int.max/8) assert(0, "NanoVega: out of memory");
8102       if (int.max/8-used < b.length) assert(0, "NanoVega: out of memory");
8103       if (used+cast(uint)b.length > size) {
8104         import core.stdc.stdlib : realloc;
8105         uint newsz = size;
8106         while (newsz < used+cast(uint)b.length) newsz = (newsz == 0 ? 1024 : newsz < 32768 ? newsz*2 : newsz+8192);
8107         assert(used+cast(uint)b.length <= newsz);
8108         data = cast(ubyte*)realloc(data, newsz);
8109         if (data is null) assert(0, "NanoVega: out of memory");
8110         size = newsz;
8111       }
8112       import core.stdc.string : memcpy;
8113       memcpy(data+used, b.ptr, b.length);
8114       used += cast(uint)b.length;
8115     }
8116     void putCommand (ubyte cmd) { pragma(inline, true); ++ccount; putBytes((&cmd)[0..1]); }
8117     void putArgs (const(float)[] f...) { pragma(inline, true); putBytes(f[]); }
8118   }
8119 
8120   static void incRef (DataStore* ds) {
8121     pragma(inline, true);
8122     if (ds !is null) {
8123       ++ds.rc;
8124       //{ import core.stdc.stdio; printf("ods(%p): incref: newrc=%u\n", ds, ds.rc); }
8125     }
8126   }
8127 
8128   static void decRef (DataStore* ds) {
8129     version(aliced) pragma(inline, true);
8130     if (ds !is null) {
8131       //{ import core.stdc.stdio; printf("ods(%p): decref: newrc=%u\n", ds, ds.rc-1); }
8132       if (--ds.rc == 0) {
8133         import core.stdc.stdlib : free;
8134         import core.stdc.string : memset;
8135         if (ds.data !is null) free(ds.data);
8136         memset(ds, 0, DataStore.sizeof); // just in case
8137         free(ds);
8138         //{ import core.stdc.stdio; printf("  ods(%p): killed.\n"); }
8139       }
8140     }
8141   }
8142 
8143 private:
8144   static NVGPathOutline createNew () {
8145     import core.stdc.stdlib : malloc;
8146     import core.stdc.string : memset;
8147     auto ds = cast(DataStore*)malloc(DataStore.sizeof);
8148     if (ds is null) assert(0, "NanoVega: out of memory");
8149     memset(ds, 0, DataStore.sizeof);
8150     ds.rc = 1;
8151     NVGPathOutline res;
8152     res.dsaddr = cast(usize)ds;
8153     return res;
8154   }
8155 
8156 private:
8157   usize dsaddr; // fool GC
8158 
8159   @property inout(DataStore)* ds () inout pure { pragma(inline, true); return cast(DataStore*)dsaddr; }
8160 
8161 public:
8162   /// commands
8163   static struct Command {
8164     ///
8165     enum Kind : ubyte {
8166       MoveTo, ///
8167       LineTo, ///
8168       QuadTo, ///
8169       BezierTo, ///
8170       End, /// no more commands (this command is not `valid`!)
8171 
8172     }
8173     Kind code; ///
8174     const(float)[] args; ///
8175     @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (code >= Kind.min && code < Kind.End && args.length >= 2); } ///
8176 
8177     static uint arglen (Kind code) pure nothrow @safe @nogc {
8178       pragma(inline, true);
8179       return
8180         code == Kind.MoveTo || code == Kind.LineTo ? 2 :
8181         code == Kind.QuadTo ? 4 :
8182         code == Kind.BezierTo ? 6 :
8183         0;
8184     }
8185 
8186     /// perform NanoVega command with stored data.
8187     void perform (NVGContext ctx) const nothrow @trusted @nogc {
8188       if (ctx is null) return;
8189       final switch (code) {
8190         case Kind.MoveTo: if (args.length > 1) ctx.moveTo(args.ptr[0..2]); break;
8191         case Kind.LineTo: if (args.length > 1) ctx.lineTo(args.ptr[0..2]); break;
8192         case Kind.QuadTo: if (args.length > 3) ctx.quadTo(args.ptr[0..4]); break;
8193         case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(args.ptr[0..6]); break;
8194         case Kind.End: break;
8195       }
8196     }
8197 
8198     /// perform NanoVega command with stored data, transforming points with [xform] transformation matrix.
8199     void perform() (NVGContext ctx, const scope auto ref NVGMatrix xform) const nothrow @trusted @nogc {
8200       if (ctx is null || !valid) return;
8201       float[6] pts = void;
8202       pts[0..args.length] = args[];
8203       foreach (immutable pidx; 0..args.length/2) xform.point(pts.ptr[pidx*2+0], pts.ptr[pidx*2+1]);
8204       final switch (code) {
8205         case Kind.MoveTo: if (args.length > 1) ctx.moveTo(pts.ptr[0..2]); break;
8206         case Kind.LineTo: if (args.length > 1) ctx.lineTo(pts.ptr[0..2]); break;
8207         case Kind.QuadTo: if (args.length > 3) ctx.quadTo(pts.ptr[0..4]); break;
8208         case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(pts.ptr[0..6]); break;
8209         case Kind.End: break;
8210       }
8211     }
8212   }
8213 
8214 public:
8215   /// Create new path with quadratic bezier (first command is MoveTo, second command is QuadTo).
8216   static NVGPathOutline createNewQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) {
8217     auto res = createNew();
8218     res.ds.putCommand(Command.Kind.MoveTo);
8219     res.ds.putArgs(x0, y0);
8220     res.ds.putCommand(Command.Kind.QuadTo);
8221     res.ds.putArgs(cx, cy, x, y);
8222     return res;
8223   }
8224 
8225   /// Create new path with cubic bezier (first command is MoveTo, second command is BezierTo).
8226   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) {
8227     auto res = createNew();
8228     res.ds.putCommand(Command.Kind.MoveTo);
8229     res.ds.putArgs(x1, y1);
8230     res.ds.putCommand(Command.Kind.BezierTo);
8231     res.ds.putArgs(x2, y2, x3, y3, x4, y4);
8232     return res;
8233   }
8234 
8235 public:
8236   this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); }
8237   ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); }
8238 
8239   void opAssign() (const scope auto ref NVGPathOutline a) {
8240     incRef(cast(DataStore*)a.dsaddr);
8241     decRef(cast(DataStore*)dsaddr);
8242     dsaddr = a.dsaddr;
8243   }
8244 
8245   /// Clear storage.
8246   void clear () {
8247     pragma(inline, true);
8248     decRef(ds);
8249     dsaddr = 0;
8250   }
8251 
8252   /// Is this outline empty?
8253   @property empty () const pure { pragma(inline, true); return (dsaddr == 0 || ds.ccount == 0); }
8254 
8255   /// Returns number of commands in outline.
8256   @property int length () const pure { pragma(inline, true); return (dsaddr ? ds.ccount : 0); }
8257 
8258   /// Returns "flattened" path. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8259   NVGPathOutline flatten () const { pragma(inline, true); return flattenInternal(null); }
8260 
8261   /// Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8262   NVGPathOutline flatten() (const scope auto ref NVGMatrix mt) const { pragma(inline, true); return flattenInternal(&mt); }
8263 
8264   // Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8265   private NVGPathOutline flattenInternal (scope NVGMatrix* tfm) const {
8266     import core.stdc.string : memset;
8267 
8268     NVGPathOutline res;
8269     if (dsaddr == 0 || ds.ccount == 0) { res = this; return res; } // nothing to do
8270 
8271     // check if we need to flatten the path
8272     if (tfm is null) {
8273       bool dowork = false;
8274       foreach (const ref cs; commands) {
8275         if (cs.code != Command.Kind.MoveTo && cs.code != Command.Kind.LineTo) {
8276           dowork = true;
8277           break;
8278         }
8279       }
8280       if (!dowork) { res = this; return res; } // nothing to do
8281     }
8282 
8283     NVGcontextinternal ctx;
8284     memset(&ctx, 0, ctx.sizeof);
8285     ctx.cache = nvg__allocPathCache();
8286     scope(exit) {
8287       import core.stdc.stdlib : free;
8288       nvg__deletePathCache(ctx.cache);
8289     }
8290 
8291     ctx.tessTol = 0.25f;
8292     ctx.angleTol = 0; // 0 -- angle tolerance for McSeem Bezier rasterizer
8293     ctx.cuspLimit = 0; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps)
8294     ctx.distTol = 0.01f;
8295     ctx.tesselatortype = NVGTesselation.DeCasteljau;
8296 
8297     nvg__addPath(&ctx); // we need this for `nvg__addPoint()`
8298 
8299     // has some curves or transformations, convert path
8300     res = createNew();
8301     float[8] args = void;
8302 
8303     res.ds.bounds = [float.max, float.max, -float.max, -float.max];
8304 
8305     float lastX = float.max, lastY = float.max;
8306     bool lastWasMove = false;
8307 
8308     void addPoint (float x, float y, Command.Kind cmd=Command.Kind.LineTo) nothrow @trusted @nogc {
8309       if (tfm !is null) tfm.point(x, y);
8310       bool isMove = (cmd == Command.Kind.MoveTo);
8311       if (isMove) {
8312         // moveto
8313         if (lastWasMove && nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return;
8314       } else {
8315         // lineto
8316         if (nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return;
8317       }
8318       lastWasMove = isMove;
8319       lastX = x;
8320       lastY = y;
8321       res.ds.putCommand(cmd);
8322       res.ds.putArgs(x, y);
8323       res.ds.bounds.ptr[0] = nvg__min(res.ds.bounds.ptr[0], x);
8324       res.ds.bounds.ptr[1] = nvg__min(res.ds.bounds.ptr[1], y);
8325       res.ds.bounds.ptr[2] = nvg__max(res.ds.bounds.ptr[2], x);
8326       res.ds.bounds.ptr[3] = nvg__max(res.ds.bounds.ptr[3], y);
8327     }
8328 
8329     // sorry for this pasta
8330     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 {
8331       ctx.cache.npoints = 0;
8332       if (ctx.tesselatortype == NVGTesselation.DeCasteljau) {
8333         nvg__tesselateBezier(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner);
8334       } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) {
8335         nvg__tesselateBezierMcSeem(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner);
8336       } else {
8337         nvg__tesselateBezierAFD(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, PointFlag.Corner);
8338       }
8339       // add generated points
8340       foreach (const ref pt; ctx.cache.points[0..ctx.cache.npoints]) addPoint(pt.x, pt.y);
8341     }
8342 
8343     void flattenQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) {
8344       flattenBezier(
8345         x0, y0,
8346         x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
8347         x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
8348         x, y,
8349         0,
8350       );
8351     }
8352 
8353     float cx = 0, cy = 0;
8354     foreach (const ref cs; commands) {
8355       switch (cs.code) {
8356         case Command.Kind.LineTo:
8357         case Command.Kind.MoveTo:
8358           addPoint(cs.args[0], cs.args[1], cs.code);
8359           cx = cs.args[0];
8360           cy = cs.args[1];
8361           break;
8362         case Command.Kind.QuadTo:
8363           flattenQuad(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3]);
8364           cx = cs.args[2];
8365           cy = cs.args[3];
8366           break;
8367         case Command.Kind.BezierTo:
8368           flattenBezier(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3], cs.args[4], cs.args[5], 0);
8369           cx = cs.args[4];
8370           cy = cs.args[5];
8371           break;
8372         default:
8373           break;
8374       }
8375     }
8376 
8377     return res;
8378   }
8379 
8380   /// Returns forward range with all glyph commands.
8381   auto commands () const nothrow @trusted @nogc {
8382     static struct Range {
8383     private nothrow @trusted @nogc:
8384       usize dsaddr;
8385       uint cpos; // current position in data
8386       uint cleft; // number of commands left
8387       @property const(ubyte)* data () inout pure { pragma(inline, true); return (dsaddr ? (cast(DataStore*)dsaddr).data : null); }
8388     public:
8389       this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); }
8390       ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); }
8391       void opAssign() (const scope auto ref Range a) {
8392         incRef(cast(DataStore*)a.dsaddr);
8393         decRef(cast(DataStore*)dsaddr);
8394         dsaddr = a.dsaddr;
8395         cpos = a.cpos;
8396         cleft = a.cleft;
8397       }
8398       float[4] bounds () const pure { float[4] res = 0; pragma(inline, true); if (dsaddr) res[] = (cast(DataStore*)dsaddr).bounds[]; return res; } /// outline bounds
8399       @property bool empty () const pure { pragma(inline, true); return (cleft == 0); }
8400       @property int length () const pure { pragma(inline, true); return cleft; }
8401       @property Range save () const { pragma(inline, true); Range res = this; return res; }
8402       @property Command front () const {
8403         Command res = void;
8404         if (cleft > 0) {
8405           res.code = cast(Command.Kind)data[cpos];
8406           switch (res.code) {
8407             case Command.Kind.MoveTo:
8408             case Command.Kind.LineTo:
8409               res.args = (cast(const(float*))(data+cpos+1))[0..1*2];
8410               break;
8411             case Command.Kind.QuadTo:
8412               res.args = (cast(const(float*))(data+cpos+1))[0..2*2];
8413               break;
8414             case Command.Kind.BezierTo:
8415               res.args = (cast(const(float*))(data+cpos+1))[0..3*2];
8416               break;
8417             default:
8418               res.code = Command.Kind.End;
8419               res.args = null;
8420               break;
8421           }
8422         } else {
8423           res.code = Command.Kind.End;
8424           res.args = null;
8425         }
8426         return res;
8427       }
8428       void popFront () {
8429         if (cleft <= 1) { cleft = 0; return; } // don't waste time skipping last command
8430         --cleft;
8431         switch (data[cpos]) {
8432           case Command.Kind.MoveTo:
8433           case Command.Kind.LineTo:
8434             cpos += 1+1*2*cast(uint)float.sizeof;
8435             break;
8436           case Command.Kind.QuadTo:
8437             cpos += 1+2*2*cast(uint)float.sizeof;
8438             break;
8439           case Command.Kind.BezierTo:
8440             cpos += 1+3*2*cast(uint)float.sizeof;
8441             break;
8442           default:
8443             cleft = 0;
8444             break;
8445         }
8446       }
8447     }
8448     if (dsaddr) {
8449       incRef(cast(DataStore*)dsaddr); // range anchors it
8450       return Range(dsaddr, 0, ds.ccount);
8451     } else {
8452       return Range.init;
8453     }
8454   }
8455 }
8456 
8457 public alias NVGGlyphOutline = NVGPathOutline; /// For backwards compatibility.
8458 
8459 /// Destroy glyph outiline and free allocated memory.
8460 /// Group: text_api
8461 public void kill (ref NVGPathOutline ol) nothrow @trusted @nogc {
8462   pragma(inline, true);
8463   ol.clear();
8464 }
8465 
8466 static if (is(typeof(&fons__nvg__toOutline))) {
8467   public enum NanoVegaHasCharOutline = true; ///
8468 } else {
8469   public enum NanoVegaHasCharOutline = false; ///
8470 }
8471 
8472 /// Returns glyph outlines as array of commands. Vertical 0 is baseline.
8473 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead.
8474 /// Returns `null` if there is no such glyph, or current font is not scalable.
8475 /// Group: text_api
8476 public NVGPathOutline charOutline (NVGContext ctx, dchar dch) nothrow @trusted @nogc {
8477   import core.stdc.stdlib : malloc;
8478   import core.stdc.string : memcpy;
8479   NVGstate* state = nvg__getState(ctx);
8480   ctx.fs.fontId = state.fontId;
8481   auto oline = NVGPathOutline.createNew();
8482   if (!ctx.fs.toOutline(dch, oline.ds)) oline.clear();
8483   return oline;
8484 }
8485 
8486 
8487 float nvg__quantize (float a, float d) pure nothrow @safe @nogc {
8488   pragma(inline, true);
8489   return (cast(int)(a/d+0.5f))*d;
8490 }
8491 
8492 float nvg__getFontScale (NVGstate* state) /*pure*/ nothrow @safe @nogc {
8493   pragma(inline, true);
8494   return nvg__min(nvg__quantize(nvg__getAverageScale(state.xform), 0.01f), 4.0f);
8495 }
8496 
8497 void nvg__flushTextTexture (NVGContext ctx) nothrow @trusted @nogc {
8498   int[4] dirty = void;
8499   if (ctx.fs.validateTexture(dirty.ptr)) {
8500     auto fontImage = &ctx.fontImages[ctx.fontImageIdx];
8501     // Update texture
8502     if (fontImage.valid) {
8503       int iw, ih;
8504       const(ubyte)* data = ctx.fs.getTextureData(&iw, &ih);
8505       int x = dirty[0];
8506       int y = dirty[1];
8507       int w = dirty[2]-dirty[0];
8508       int h = dirty[3]-dirty[1];
8509       ctx.params.renderUpdateTexture(ctx.params.userPtr, fontImage.id, x, y, w, h, data);
8510     }
8511   }
8512 }
8513 
8514 bool nvg__allocTextAtlas (NVGContext ctx) nothrow @trusted @nogc {
8515   int iw, ih;
8516   nvg__flushTextTexture(ctx);
8517   if (ctx.fontImageIdx >= NVG_MAX_FONTIMAGES-1) return false;
8518   // if next fontImage already have a texture
8519   if (ctx.fontImages[ctx.fontImageIdx+1].valid) {
8520     ctx.imageSize(ctx.fontImages[ctx.fontImageIdx+1], iw, ih);
8521   } else {
8522     // calculate the new font image size and create it
8523     ctx.imageSize(ctx.fontImages[ctx.fontImageIdx], iw, ih);
8524     if (iw > ih) ih *= 2; else iw *= 2;
8525     if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE;
8526     ctx.fontImages[ctx.fontImageIdx+1].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, iw, ih, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null);
8527     if (ctx.fontImages[ctx.fontImageIdx+1].id > 0) {
8528       ctx.fontImages[ctx.fontImageIdx+1].ctx = ctx;
8529       ctx.nvg__imageIncRef(ctx.fontImages[ctx.fontImageIdx+1].id, false); // don't increment driver refcount
8530     }
8531   }
8532   ++ctx.fontImageIdx;
8533   ctx.fs.resetAtlas(iw, ih);
8534   return true;
8535 }
8536 
8537 void nvg__renderText (NVGContext ctx, NVGVertex* verts, int nverts) nothrow @trusted @nogc {
8538   NVGstate* state = nvg__getState(ctx);
8539   NVGPaint paint = state.fill;
8540 
8541   // Render triangles.
8542   paint.image = ctx.fontImages[ctx.fontImageIdx];
8543 
8544   // Apply global alpha
8545   paint.innerColor.a *= state.alpha;
8546   paint.middleColor.a *= state.alpha;
8547   paint.outerColor.a *= state.alpha;
8548 
8549   ctx.params.renderTriangles(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &paint, &state.scissor, verts, nverts);
8550 
8551   ++ctx.drawCallCount;
8552   ctx.textTriCount += nverts/3;
8553 }
8554 
8555 /// Draws text string at specified location. Returns next x position.
8556 /// Group: text_api
8557 public float text(T) (NVGContext ctx, float x, float y, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8558   NVGstate* state = nvg__getState(ctx);
8559   FONSTextIter!T iter, prevIter;
8560   FONSQuad q;
8561   NVGVertex* verts;
8562   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8563   float invscale = 1.0f/scale;
8564   int cverts = 0;
8565   int nverts = 0;
8566 
8567   if (state.fontId == FONS_INVALID) return x;
8568   if (str.length == 0) return x;
8569 
8570   ctx.fs.size = state.fontSize*scale;
8571   ctx.fs.spacing = state.letterSpacing*scale;
8572   ctx.fs.blur = state.fontBlur*scale;
8573   ctx.fs.textAlign = state.textAlign;
8574   ctx.fs.fontId = state.fontId;
8575 
8576   cverts = nvg__max(2, cast(int)(str.length))*6; // conservative estimate
8577   verts = nvg__allocTempVerts(ctx, cverts);
8578   if (verts is null) return x;
8579 
8580   if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Required)) return x;
8581   prevIter = iter;
8582   while (iter.next(q)) {
8583     float[4*2] c = void;
8584     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8585       if (nverts != 0) {
8586         // TODO: add back-end bit to do this just once per frame
8587         nvg__flushTextTexture(ctx);
8588         nvg__renderText(ctx, verts, nverts);
8589         nverts = 0;
8590       }
8591       if (!nvg__allocTextAtlas(ctx)) break; // no memory :(
8592       iter = prevIter;
8593       iter.next(q); // try again
8594       if (iter.prevGlyphIndex < 0) {
8595         // still can not find glyph, try replacement
8596         iter = prevIter;
8597         if (!iter.getDummyChar(q)) break;
8598       }
8599     }
8600     prevIter = iter;
8601     // transform corners
8602     state.xform.point(&c[0], &c[1], q.x0*invscale, q.y0*invscale);
8603     state.xform.point(&c[2], &c[3], q.x1*invscale, q.y0*invscale);
8604     state.xform.point(&c[4], &c[5], q.x1*invscale, q.y1*invscale);
8605     state.xform.point(&c[6], &c[7], q.x0*invscale, q.y1*invscale);
8606     // create triangles
8607     if (nverts+6 <= cverts) {
8608       nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts;
8609       nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts;
8610       nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); ++nverts;
8611       nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts;
8612       nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); ++nverts;
8613       nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts;
8614     }
8615   }
8616 
8617   // TODO: add back-end bit to do this just once per frame
8618   if (nverts > 0) {
8619     nvg__flushTextTexture(ctx);
8620     nvg__renderText(ctx, verts, nverts);
8621   }
8622 
8623   return iter.nextx/scale;
8624 }
8625 
8626 /** Draws multi-line text string at specified location wrapped at the specified width.
8627  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8628  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8629  *
8630  * Group: text_api
8631  */
8632 public void textBox(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8633   NVGstate* state = nvg__getState(ctx);
8634   if (state.fontId == FONS_INVALID) return;
8635 
8636   NVGTextRow!T[2] rows;
8637   auto oldAlign = state.textAlign;
8638   scope(exit) state.textAlign = oldAlign;
8639   auto halign = state.textAlign.horizontal;
8640   float lineh = 0;
8641 
8642   ctx.textMetrics(null, null, &lineh);
8643   state.textAlign.horizontal = NVGTextAlign.H.Left;
8644   for (;;) {
8645     auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]);
8646     //{ import core.stdc.stdio : printf; printf("slen=%u; rlen=%u; bw=%f\n", cast(uint)str.length, cast(uint)rres.length, cast(double)breakRowWidth); }
8647     if (rres.length == 0) break;
8648     foreach (ref row; rres) {
8649       final switch (halign) {
8650         case NVGTextAlign.H.Left: ctx.text(x, y, row.row); break;
8651         case NVGTextAlign.H.Center: ctx.text(x+breakRowWidth*0.5f-row.width*0.5f, y, row.row); break;
8652         case NVGTextAlign.H.Right: ctx.text(x+breakRowWidth-row.width, y, row.row); break;
8653       }
8654       y += lineh*state.lineHeight;
8655     }
8656     str = rres[$-1].rest;
8657   }
8658 }
8659 
8660 private template isGoodPositionDelegate(DG) {
8661   private DG dg;
8662   static if (is(typeof({ NVGGlyphPosition pos; bool res = dg(pos); })) ||
8663              is(typeof({ NVGGlyphPosition pos; dg(pos); })))
8664     enum isGoodPositionDelegate = true;
8665   else
8666     enum isGoodPositionDelegate = false;
8667 }
8668 
8669 /** Calculates the glyph x positions of the specified text.
8670  * Measured values are returned in local coordinate space.
8671  *
8672  * Group: text_api
8673  */
8674 public NVGGlyphPosition[] textGlyphPositions(T) (NVGContext ctx, float x, float y, const(T)[] str, NVGGlyphPosition[] positions) nothrow @trusted @nogc
8675 if (isAnyCharType!T)
8676 {
8677   if (str.length == 0 || positions.length == 0) return positions[0..0];
8678   usize posnum;
8679   auto len = ctx.textGlyphPositions(x, y, str, (const scope ref NVGGlyphPosition pos) {
8680     positions.ptr[posnum++] = pos;
8681     return (posnum < positions.length);
8682   });
8683   return positions[0..len];
8684 }
8685 
8686 /// Ditto.
8687 public int textGlyphPositions(T, DG) (NVGContext ctx, float x, float y, const(T)[] str, scope DG dg)
8688 if (isAnyCharType!T && isGoodPositionDelegate!DG)
8689 {
8690   import std.traits : ReturnType;
8691   static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true;
8692 
8693   NVGstate* state = nvg__getState(ctx);
8694   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8695   float invscale = 1.0f/scale;
8696   FONSTextIter!T iter, prevIter;
8697   FONSQuad q;
8698   int npos = 0;
8699 
8700   if (str.length == 0) return 0;
8701 
8702   ctx.fs.size = state.fontSize*scale;
8703   ctx.fs.spacing = state.letterSpacing*scale;
8704   ctx.fs.blur = state.fontBlur*scale;
8705   ctx.fs.textAlign = state.textAlign;
8706   ctx.fs.fontId = state.fontId;
8707 
8708   if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Optional)) return npos;
8709   prevIter = iter;
8710   while (iter.next(q)) {
8711     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8712       if (!nvg__allocTextAtlas(ctx)) break; // no memory
8713       iter = prevIter;
8714       iter.next(q); // try again
8715       if (iter.prevGlyphIndex < 0) {
8716         // still can not find glyph, try replacement
8717         iter = prevIter;
8718         if (!iter.getDummyChar(q)) break;
8719       }
8720     }
8721     prevIter = iter;
8722     NVGGlyphPosition position = void; //WARNING!
8723     position.strpos = cast(usize)(iter.stringp-str.ptr);
8724     position.x = iter.x*invscale;
8725     position.minx = nvg__min(iter.x, q.x0)*invscale;
8726     position.maxx = nvg__max(iter.nextx, q.x1)*invscale;
8727     ++npos;
8728     static if (RetBool) { if (!dg(position)) return npos; } else dg(position);
8729   }
8730 
8731   return npos;
8732 }
8733 
8734 private template isGoodRowDelegate(CT, DG) {
8735   private DG dg;
8736   static if (is(typeof({ NVGTextRow!CT row; bool res = dg(row); })) ||
8737              is(typeof({ NVGTextRow!CT row; dg(row); })))
8738     enum isGoodRowDelegate = true;
8739   else
8740     enum isGoodRowDelegate = false;
8741 }
8742 
8743 /** Breaks the specified text into lines.
8744  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8745  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8746  *
8747  * Group: text_api
8748  */
8749 public NVGTextRow!T[] textBreakLines(T) (NVGContext ctx, const(T)[] str, float breakRowWidth, NVGTextRow!T[] rows) nothrow @trusted @nogc
8750 if (isAnyCharType!T)
8751 {
8752   if (rows.length == 0) return rows;
8753   if (rows.length > int.max-1) rows = rows[0..int.max-1];
8754   int nrow = 0;
8755   auto count = ctx.textBreakLines(str, breakRowWidth, (const scope ref NVGTextRow!T row) {
8756     rows[nrow++] = row;
8757     return (nrow < rows.length);
8758   });
8759   return rows[0..count];
8760 }
8761 
8762 /** Breaks the specified text into lines.
8763  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8764  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8765  * Returns number of rows.
8766  *
8767  * Group: text_api
8768  */
8769 public int textBreakLines(T, DG) (NVGContext ctx, const(T)[] str, float breakRowWidth, scope DG dg)
8770 if (isAnyCharType!T && isGoodRowDelegate!(T, DG))
8771 {
8772   import std.traits : ReturnType;
8773   static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true;
8774 
8775   enum NVGcodepointType : int {
8776     Space,
8777     NewLine,
8778     Char,
8779   }
8780 
8781   NVGstate* state = nvg__getState(ctx);
8782   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8783   float invscale = 1.0f/scale;
8784   FONSTextIter!T iter, prevIter;
8785   FONSQuad q;
8786   int nrows = 0;
8787   float rowStartX = 0;
8788   float rowWidth = 0;
8789   float rowMinX = 0;
8790   float rowMaxX = 0;
8791   int rowStart = 0;
8792   int rowEnd = 0;
8793   int wordStart = 0;
8794   float wordStartX = 0;
8795   float wordMinX = 0;
8796   int breakEnd = 0;
8797   float breakWidth = 0;
8798   float breakMaxX = 0;
8799   int type = NVGcodepointType.Space, ptype = NVGcodepointType.Space;
8800   uint pcodepoint = 0;
8801 
8802   if (state.fontId == FONS_INVALID) return 0;
8803   if (str.length == 0 || dg is null) return 0;
8804 
8805   ctx.fs.size = state.fontSize*scale;
8806   ctx.fs.spacing = state.letterSpacing*scale;
8807   ctx.fs.blur = state.fontBlur*scale;
8808   ctx.fs.textAlign = state.textAlign;
8809   ctx.fs.fontId = state.fontId;
8810 
8811   breakRowWidth *= scale;
8812 
8813   enum Phase {
8814     Normal, // searching for breaking point
8815     SkipBlanks, // skip leading blanks
8816   }
8817   Phase phase = Phase.SkipBlanks; // don't skip blanks on first line
8818 
8819   if (!iter.setup(ctx.fs, 0, 0, str, FONSBitmapFlag.Optional)) return 0;
8820   prevIter = iter;
8821   while (iter.next(q)) {
8822     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8823       if (!nvg__allocTextAtlas(ctx)) break; // no memory
8824       iter = prevIter;
8825       iter.next(q); // try again
8826       if (iter.prevGlyphIndex < 0) {
8827         // still can not find glyph, try replacement
8828         iter = prevIter;
8829         if (!iter.getDummyChar(q)) break;
8830       }
8831     }
8832     prevIter = iter;
8833     switch (iter.codepoint) {
8834       case 9: // \t
8835       case 11: // \v
8836       case 12: // \f
8837       case 32: // space
8838       case 0x00a0: // NBSP
8839         type = NVGcodepointType.Space;
8840         break;
8841       case 10: // \n
8842         type = (pcodepoint == 13 ? NVGcodepointType.Space : NVGcodepointType.NewLine);
8843         break;
8844       case 13: // \r
8845         type = (pcodepoint == 10 ? NVGcodepointType.Space : NVGcodepointType.NewLine);
8846         break;
8847       case 0x0085: // NEL
8848       case 0x2028: // Line Separator
8849       case 0x2029: // Paragraph Separator
8850         type = NVGcodepointType.NewLine;
8851         break;
8852       default:
8853         type = NVGcodepointType.Char;
8854         break;
8855     }
8856     if (phase == Phase.SkipBlanks) {
8857       // fix row start
8858       rowStart = cast(int)(iter.stringp-str.ptr);
8859       rowEnd = rowStart;
8860       rowStartX = iter.x;
8861       rowWidth = iter.nextx-rowStartX; // q.x1-rowStartX;
8862       rowMinX = q.x0-rowStartX;
8863       rowMaxX = q.x1-rowStartX;
8864       wordStart = rowStart;
8865       wordStartX = iter.x;
8866       wordMinX = q.x0-rowStartX;
8867       breakEnd = rowStart;
8868       breakWidth = 0.0;
8869       breakMaxX = 0.0;
8870       if (type == NVGcodepointType.Space) continue;
8871       phase = Phase.Normal;
8872     }
8873 
8874     if (type == NVGcodepointType.NewLine) {
8875       // always handle new lines
8876       NVGTextRow!T row;
8877       row.string = str;
8878       row.start = rowStart;
8879       row.end = rowEnd;
8880       row.width = rowWidth*invscale;
8881       row.minx = rowMinX*invscale;
8882       row.maxx = rowMaxX*invscale;
8883       ++nrows;
8884       static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8885       phase = Phase.SkipBlanks;
8886     } else {
8887       float nextWidth = iter.nextx-rowStartX;
8888       // track last non-white space character
8889       if (type == NVGcodepointType.Char) {
8890         rowEnd = cast(int)(iter.nextp-str.ptr);
8891         rowWidth = iter.nextx-rowStartX;
8892         rowMaxX = q.x1-rowStartX;
8893       }
8894       // track last end of a word
8895       if (ptype == NVGcodepointType.Char && type == NVGcodepointType.Space) {
8896         breakEnd = cast(int)(iter.stringp-str.ptr);
8897         breakWidth = rowWidth;
8898         breakMaxX = rowMaxX;
8899       }
8900       // track last beginning of a word
8901       if (ptype == NVGcodepointType.Space && type == NVGcodepointType.Char) {
8902         wordStart = cast(int)(iter.stringp-str.ptr);
8903         wordStartX = iter.x;
8904         wordMinX = q.x0-rowStartX;
8905       }
8906       // break to new line when a character is beyond break width
8907       if (type == NVGcodepointType.Char && nextWidth > breakRowWidth) {
8908         // the run length is too long, need to break to new line
8909         NVGTextRow!T row;
8910         row.string = str;
8911         if (breakEnd == rowStart) {
8912           // the current word is longer than the row length, just break it from here
8913           row.start = rowStart;
8914           row.end = cast(int)(iter.stringp-str.ptr);
8915           row.width = rowWidth*invscale;
8916           row.minx = rowMinX*invscale;
8917           row.maxx = rowMaxX*invscale;
8918           ++nrows;
8919           static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8920           rowStartX = iter.x;
8921           rowStart = cast(int)(iter.stringp-str.ptr);
8922           rowEnd = cast(int)(iter.nextp-str.ptr);
8923           rowWidth = iter.nextx-rowStartX;
8924           rowMinX = q.x0-rowStartX;
8925           rowMaxX = q.x1-rowStartX;
8926           wordStart = rowStart;
8927           wordStartX = iter.x;
8928           wordMinX = q.x0-rowStartX;
8929         } else {
8930           // break the line from the end of the last word, and start new line from the beginning of the new
8931           //{ import core.stdc.stdio : printf; printf("rowStart=%u; rowEnd=%u; breakEnd=%u; len=%u\n", rowStart, rowEnd, breakEnd, cast(uint)str.length); }
8932           row.start = rowStart;
8933           row.end = breakEnd;
8934           row.width = breakWidth*invscale;
8935           row.minx = rowMinX*invscale;
8936           row.maxx = breakMaxX*invscale;
8937           ++nrows;
8938           static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8939           rowStartX = wordStartX;
8940           rowStart = wordStart;
8941           rowEnd = cast(int)(iter.nextp-str.ptr);
8942           rowWidth = iter.nextx-rowStartX;
8943           rowMinX = wordMinX;
8944           rowMaxX = q.x1-rowStartX;
8945           // no change to the word start
8946         }
8947         // set null break point
8948         breakEnd = rowStart;
8949         breakWidth = 0.0;
8950         breakMaxX = 0.0;
8951       }
8952     }
8953 
8954     pcodepoint = iter.codepoint;
8955     ptype = type;
8956   }
8957 
8958   // break the line from the end of the last word, and start new line from the beginning of the new
8959   if (phase != Phase.SkipBlanks && rowStart < str.length) {
8960     //{ import core.stdc.stdio : printf; printf("  rowStart=%u; len=%u\n", rowStart, cast(uint)str.length); }
8961     NVGTextRow!T row;
8962     row.string = str;
8963     row.start = rowStart;
8964     row.end = cast(int)str.length;
8965     row.width = rowWidth*invscale;
8966     row.minx = rowMinX*invscale;
8967     row.maxx = rowMaxX*invscale;
8968     ++nrows;
8969     static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8970   }
8971 
8972   return nrows;
8973 }
8974 
8975 /** Returns iterator which you can use to calculate text bounds and advancement.
8976  * This is usable when you need to do some text layouting with wrapping, to avoid
8977  * guesswork ("will advancement for this space stay the same?"), and Schlemiel's
8978  * algorithm. Note that you can copy the returned struct to save iterator state.
8979  *
8980  * You can check if iterator is valid with [valid] property, put new chars with
8981  * [put] method, get current advance with [advance] property, and current
8982  * bounds with `getBounds(ref float[4] bounds)` method.
8983  *
8984  * $(WARNING Don't change font parameters while iterating! Or use [restoreFont] method.)
8985  *
8986  * Group: text_api
8987  */
8988 public struct TextBoundsIterator {
8989 private:
8990   NVGContext ctx;
8991   FONSTextBoundsIterator fsiter; // fontstash iterator
8992   float scale, invscale, xscaled, yscaled;
8993   // font settings
8994   float fsSize, fsSpacing, fsBlur;
8995   int fsFontId;
8996   NVGTextAlign fsAlign;
8997 
8998 public:
8999   /// Setups iteration. Takes current font parameters from the given NanoVega context.
9000   this (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { reset(actx, ax, ay); }
9001 
9002   /// Resets iteration. Takes current font parameters from the given NanoVega context.
9003   void reset (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc {
9004     fsiter = fsiter.init;
9005     this = this.init;
9006     if (actx is null) return;
9007     NVGstate* state = nvg__getState(actx);
9008     if (state is null) return;
9009     if (state.fontId == FONS_INVALID) { ctx = null; return; }
9010 
9011     ctx = actx;
9012     scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9013     invscale = 1.0f/scale;
9014 
9015     fsSize = state.fontSize*scale;
9016     fsSpacing = state.letterSpacing*scale;
9017     fsBlur = state.fontBlur*scale;
9018     fsAlign = state.textAlign;
9019     fsFontId = state.fontId;
9020     restoreFont();
9021 
9022     xscaled = ax*scale;
9023     yscaled = ay*scale;
9024     fsiter.reset(ctx.fs, xscaled, yscaled);
9025   }
9026 
9027   /// Restart iteration. Will not restore font.
9028   void restart () nothrow @trusted @nogc {
9029     if (ctx !is null) fsiter.reset(ctx.fs, xscaled, yscaled);
9030   }
9031 
9032   /// Restore font settings for the context.
9033   void restoreFont () nothrow @trusted @nogc {
9034     if (ctx !is null) {
9035       ctx.fs.size = fsSize;
9036       ctx.fs.spacing = fsSpacing;
9037       ctx.fs.blur = fsBlur;
9038       ctx.fs.textAlign = fsAlign;
9039       ctx.fs.fontId = fsFontId;
9040     }
9041   }
9042 
9043   /// Is this iterator valid?
9044   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null); }
9045 
9046   /// Add chars.
9047   void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { pragma(inline, true); if (ctx !is null) fsiter.put(str[]); }
9048 
9049   /// Returns current advance
9050   @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null ? fsiter.advance*invscale : 0); }
9051 
9052   /// Returns current text bounds.
9053   void getBounds (ref float[4] bounds) nothrow @trusted @nogc {
9054     if (ctx !is null) {
9055       fsiter.getBounds(bounds);
9056       ctx.fs.getLineBounds(yscaled, &bounds[1], &bounds[3]);
9057       bounds[0] *= invscale;
9058       bounds[1] *= invscale;
9059       bounds[2] *= invscale;
9060       bounds[3] *= invscale;
9061     } else {
9062       bounds[] = 0;
9063     }
9064   }
9065 
9066   /// Returns current horizontal text bounds.
9067   void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc {
9068     if (ctx !is null) {
9069       fsiter.getHBounds(xmin, xmax);
9070       xmin *= invscale;
9071       xmax *= invscale;
9072     }
9073   }
9074 
9075   /// Returns current vertical text bounds.
9076   void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc {
9077     if (ctx !is null) {
9078       //fsiter.getVBounds(ymin, ymax);
9079       ctx.fs.getLineBounds(yscaled, &ymin, &ymax);
9080       ymin *= invscale;
9081       ymax *= invscale;
9082     }
9083   }
9084 }
9085 
9086 /// Returns font line height (without line spacing), measured in local coordinate space.
9087 /// Group: text_api
9088 public float textFontHeight (NVGContext ctx) nothrow @trusted @nogc {
9089   float res = void;
9090   ctx.textMetrics(null, null, &res);
9091   return res;
9092 }
9093 
9094 /// Returns font ascender (positive), measured in local coordinate space.
9095 /// Group: text_api
9096 public float textFontAscender (NVGContext ctx) nothrow @trusted @nogc {
9097   float res = void;
9098   ctx.textMetrics(&res, null, null);
9099   return res;
9100 }
9101 
9102 /// Returns font descender (negative), measured in local coordinate space.
9103 /// Group: text_api
9104 public float textFontDescender (NVGContext ctx) nothrow @trusted @nogc {
9105   float res = void;
9106   ctx.textMetrics(null, &res, null);
9107   return res;
9108 }
9109 
9110 /** Measures the specified text string. Returns horizontal and vertical sizes of the measured text.
9111  * Measured values are returned in local coordinate space.
9112  *
9113  * Group: text_api
9114  */
9115 public void textExtents(T) (NVGContext ctx, const(T)[] str, float *w, float *h) nothrow @trusted @nogc if (isAnyCharType!T) {
9116   float[4] bnd = void;
9117   ctx.textBounds(0, 0, str, bnd[]);
9118   if (!ctx.fs.getFontAA(nvg__getState(ctx).fontId)) {
9119     if (w !is null) *w = nvg__lrintf(bnd.ptr[2]-bnd.ptr[0]);
9120     if (h !is null) *h = nvg__lrintf(bnd.ptr[3]-bnd.ptr[1]);
9121   } else {
9122     if (w !is null) *w = bnd.ptr[2]-bnd.ptr[0];
9123     if (h !is null) *h = bnd.ptr[3]-bnd.ptr[1];
9124   }
9125 }
9126 
9127 /** Measures the specified text string. Returns horizontal size of the measured text.
9128  * Measured values are returned in local coordinate space.
9129  *
9130  * Group: text_api
9131  */
9132 public float textWidth(T) (NVGContext ctx, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
9133   float w = void;
9134   ctx.textExtents(str, &w, null);
9135   return w;
9136 }
9137 
9138 /** Measures the specified text string. Parameter bounds should be a float[4],
9139  * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax]
9140  * Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
9141  * Measured values are returned in local coordinate space.
9142  *
9143  * Group: text_api
9144  */
9145 public float textBounds(T) (NVGContext ctx, float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc
9146 if (isAnyCharType!T)
9147 {
9148   NVGstate* state = nvg__getState(ctx);
9149 
9150   if (state.fontId == FONS_INVALID) {
9151     bounds[] = 0;
9152     return 0;
9153   }
9154 
9155   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9156   ctx.fs.size = state.fontSize*scale;
9157   ctx.fs.spacing = state.letterSpacing*scale;
9158   ctx.fs.blur = state.fontBlur*scale;
9159   ctx.fs.textAlign = state.textAlign;
9160   ctx.fs.fontId = state.fontId;
9161 
9162   float[4] b = void;
9163   immutable float width = ctx.fs.getTextBounds(x*scale, y*scale, str, b[]);
9164   immutable float invscale = 1.0f/scale;
9165   if (bounds.length) {
9166     // use line bounds for height
9167     ctx.fs.getLineBounds(y*scale, b.ptr+1, b.ptr+3);
9168     if (bounds.length > 0) bounds.ptr[0] = b.ptr[0]*invscale;
9169     if (bounds.length > 1) bounds.ptr[1] = b.ptr[1]*invscale;
9170     if (bounds.length > 2) bounds.ptr[2] = b.ptr[2]*invscale;
9171     if (bounds.length > 3) bounds.ptr[3] = b.ptr[3]*invscale;
9172   }
9173   return width*invscale;
9174 }
9175 
9176 /// Ditto.
9177 public void textBoxBounds(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str, float[] bounds) if (isAnyCharType!T) {
9178   NVGstate* state = nvg__getState(ctx);
9179   NVGTextRow!T[2] rows;
9180   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9181   float invscale = 1.0f/scale;
9182   float lineh = 0, rminy = 0, rmaxy = 0;
9183   float minx, miny, maxx, maxy;
9184 
9185   if (state.fontId == FONS_INVALID) {
9186     bounds[] = 0;
9187     return;
9188   }
9189 
9190   auto oldAlign = state.textAlign;
9191   scope(exit) state.textAlign = oldAlign;
9192   auto halign = state.textAlign.horizontal;
9193 
9194   ctx.textMetrics(null, null, &lineh);
9195   state.textAlign.horizontal = NVGTextAlign.H.Left;
9196 
9197   minx = maxx = x;
9198   miny = maxy = y;
9199 
9200   ctx.fs.size = state.fontSize*scale;
9201   ctx.fs.spacing = state.letterSpacing*scale;
9202   ctx.fs.blur = state.fontBlur*scale;
9203   ctx.fs.textAlign = state.textAlign;
9204   ctx.fs.fontId = state.fontId;
9205   ctx.fs.getLineBounds(0, &rminy, &rmaxy);
9206   rminy *= invscale;
9207   rmaxy *= invscale;
9208 
9209   for (;;) {
9210     auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]);
9211     if (rres.length == 0) break;
9212     foreach (ref row; rres) {
9213       float rminx, rmaxx, dx = 0;
9214       // horizontal bounds
9215       final switch (halign) {
9216         case NVGTextAlign.H.Left: dx = 0; break;
9217         case NVGTextAlign.H.Center: dx = breakRowWidth*0.5f-row.width*0.5f; break;
9218         case NVGTextAlign.H.Right: dx = breakRowWidth-row.width; break;
9219       }
9220       rminx = x+row.minx+dx;
9221       rmaxx = x+row.maxx+dx;
9222       minx = nvg__min(minx, rminx);
9223       maxx = nvg__max(maxx, rmaxx);
9224       // vertical bounds
9225       miny = nvg__min(miny, y+rminy);
9226       maxy = nvg__max(maxy, y+rmaxy);
9227       y += lineh*state.lineHeight;
9228     }
9229     str = rres[$-1].rest;
9230   }
9231 
9232   if (bounds.length) {
9233     if (bounds.length > 0) bounds.ptr[0] = minx;
9234     if (bounds.length > 1) bounds.ptr[1] = miny;
9235     if (bounds.length > 2) bounds.ptr[2] = maxx;
9236     if (bounds.length > 3) bounds.ptr[3] = maxy;
9237   }
9238 }
9239 
9240 /// Returns the vertical metrics based on the current text style. Measured values are returned in local coordinate space.
9241 /// Group: text_api
9242 public void textMetrics (NVGContext ctx, float* ascender, float* descender, float* lineh) nothrow @trusted @nogc {
9243   NVGstate* state = nvg__getState(ctx);
9244 
9245   if (state.fontId == FONS_INVALID) {
9246     if (ascender !is null) *ascender *= 0;
9247     if (descender !is null) *descender *= 0;
9248     if (lineh !is null) *lineh *= 0;
9249     return;
9250   }
9251 
9252   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9253   immutable float invscale = 1.0f/scale;
9254 
9255   ctx.fs.size = state.fontSize*scale;
9256   ctx.fs.spacing = state.letterSpacing*scale;
9257   ctx.fs.blur = state.fontBlur*scale;
9258   ctx.fs.textAlign = state.textAlign;
9259   ctx.fs.fontId = state.fontId;
9260 
9261   ctx.fs.getVertMetrics(ascender, descender, lineh);
9262   if (ascender !is null) *ascender *= invscale;
9263   if (descender !is null) *descender *= invscale;
9264   if (lineh !is null) *lineh *= invscale;
9265 }
9266 
9267 
9268 // ////////////////////////////////////////////////////////////////////////// //
9269 // fontstash
9270 // ////////////////////////////////////////////////////////////////////////// //
9271 import core.stdc.stdlib : malloc, realloc, free;
9272 import core.stdc.string : memset, memcpy, strncpy, strcmp, strlen;
9273 import core.stdc.stdio : FILE, fopen, fclose, fseek, ftell, fread, SEEK_END, SEEK_SET;
9274 
9275 public:
9276 // welcome to version hell!
9277 version(nanovg_force_stb_ttf) {
9278 } else {
9279   version(nanovg_force_detect) {} else version(nanovg_use_freetype) { version = nanovg_use_freetype_ii; }
9280 }
9281 version(nanovg_ignore_iv_stb_ttf) enum nanovg_ignore_iv_stb_ttf = true; else enum nanovg_ignore_iv_stb_ttf = false;
9282 //version(nanovg_ignore_mono);
9283 
9284 version(nanovg_force_stb_ttf) {
9285   private enum NanoVegaForceFreeType = false;
9286 } else {
9287   version (nanovg_builtin_freetype_bindings) {
9288     version(Posix) {
9289       private enum NanoVegaForceFreeType = true;
9290     } else {
9291       private enum NanoVegaForceFreeType = false;
9292     }
9293   } else {
9294     version(Posix) {
9295       private enum NanoVegaForceFreeType = true;
9296     } else {
9297       private enum NanoVegaForceFreeType = false;
9298     }
9299   }
9300 }
9301 
9302 version(nanovg_use_freetype_ii) {
9303   enum NanoVegaIsUsingSTBTTF = false;
9304   //pragma(msg, "iv.freetype: forced");
9305 } else {
9306   static if (NanoVegaForceFreeType) {
9307     enum NanoVegaIsUsingSTBTTF = false;
9308   } else {
9309     static if (!nanovg_ignore_iv_stb_ttf && __traits(compiles, { import iv.stb.ttf; })) {
9310       import iv.stb.ttf;
9311       enum NanoVegaIsUsingSTBTTF = true;
9312       version(nanovg_report_stb_ttf) pragma(msg, "iv.stb.ttf");
9313     } else static if (__traits(compiles, { import arsd.ttf; })) {
9314       import arsd.ttf;
9315       enum NanoVegaIsUsingSTBTTF = true;
9316       version(nanovg_report_stb_ttf) pragma(msg, "arsd.ttf");
9317     } else static if (__traits(compiles, { import stb_truetype; })) {
9318       import stb_truetype;
9319       enum NanoVegaIsUsingSTBTTF = true;
9320       version(nanovg_report_stb_ttf) pragma(msg, "stb_truetype");
9321     } else static if (__traits(compiles, { import iv.freetype; })) {
9322       version (nanovg_builtin_freetype_bindings) {
9323         enum NanoVegaIsUsingSTBTTF = false;
9324         version = nanovg_builtin_freetype_bindings;
9325       } else {
9326         import iv.freetype;
9327         enum NanoVegaIsUsingSTBTTF = false;
9328       }
9329       version(nanovg_report_stb_ttf) pragma(msg, "freetype");
9330     } else {
9331       static assert(0, "no stb_ttf/iv.freetype found!");
9332     }
9333   }
9334 }
9335 
9336 
9337 // ////////////////////////////////////////////////////////////////////////// //
9338 //version = nanovg_ft_mono;
9339 
9340 /// Invald font id.
9341 /// Group: font_stash
9342 public enum FONS_INVALID = -1;
9343 
9344 public enum FONSBitmapFlag : uint {
9345   Required = 0,
9346   Optional = 1,
9347 }
9348 
9349 public enum FONSError : int {
9350   NoError = 0,
9351   AtlasFull = 1, // Font atlas is full.
9352   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.
9353   StatesOverflow = 3, // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES.
9354   StatesUnderflow = 4, // Trying to pop too many states fonsPopState().
9355 }
9356 
9357 /// Initial parameters for new FontStash.
9358 /// Group: font_stash
9359 public struct FONSParams {
9360   enum Flag : uint {
9361     ZeroTopLeft    = 0U, // default
9362     ZeroBottomLeft = 1U,
9363   }
9364   int width, height;
9365   Flag flags = Flag.ZeroTopLeft;
9366   void* userPtr;
9367   bool function (void* uptr, int width, int height) nothrow @trusted @nogc renderCreate;
9368   int function (void* uptr, int width, int height) nothrow @trusted @nogc renderResize;
9369   void function (void* uptr, int* rect, const(ubyte)* data) nothrow @trusted @nogc renderUpdate;
9370   void function (void* uptr) nothrow @trusted @nogc renderDelete;
9371   @property bool isZeroTopLeft () const pure nothrow @trusted @nogc { pragma(inline, true); return ((flags&Flag.ZeroBottomLeft) == 0); }
9372 }
9373 
9374 //TODO: document this
9375 public struct FONSQuad {
9376   float x0=0, y0=0, s0=0, t0=0;
9377   float x1=0, y1=0, s1=0, t1=0;
9378 }
9379 
9380 //TODO: document this
9381 public struct FONSTextIter(CT) if (isAnyCharType!CT) {
9382   alias CharType = CT;
9383   float x=0, y=0, nextx=0, nexty=0, scale=0, spacing=0;
9384   uint codepoint;
9385   short isize, iblur;
9386   FONSContext stash;
9387   FONSfont* font;
9388   int prevGlyphIndex;
9389   const(CT)* s; // string
9390   const(CT)* n; // next
9391   const(CT)* e; // end
9392   FONSBitmapFlag bitmapOption;
9393   static if (is(CT == char)) {
9394     uint utf8state;
9395   }
9396 
9397   this (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { setup(astash, ax, ay, astr, abitmapOption); }
9398   ~this () nothrow @trusted @nogc { pragma(inline, true); static if (is(CT == char)) utf8state = 0; s = n = e = null; }
9399 
9400   @property const(CT)* stringp () const pure nothrow @trusted @nogc { pragma(inline, true); return s; }
9401   @property const(CT)* nextp () const pure nothrow @trusted @nogc { pragma(inline, true); return n; }
9402   @property const(CT)* endp () const pure nothrow @trusted @nogc { pragma(inline, true); return e; }
9403 
9404   bool setup (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc {
9405     import core.stdc.string : memset;
9406 
9407     memset(&this, 0, this.sizeof);
9408     if (astash is null) return false;
9409 
9410     FONSstate* state = astash.getState;
9411 
9412     if (state.font < 0 || state.font >= astash.nfonts) return false;
9413     font = astash.fonts[state.font];
9414     if (font is null || font.fdata is null) return false;
9415 
9416     isize = cast(short)(state.size*10.0f);
9417     iblur = cast(short)state.blur;
9418     scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
9419 
9420     // align horizontally
9421     if (state.talign.left) {
9422       // empty
9423     } else if (state.talign.right) {
9424       immutable float width = astash.getTextBounds(ax, ay, astr, null);
9425       ax -= width;
9426     } else if (state.talign.center) {
9427       immutable float width = astash.getTextBounds(ax, ay, astr, null);
9428       ax -= width*0.5f;
9429     }
9430 
9431     // align vertically
9432     ay += astash.getVertAlign(font, state.talign, isize);
9433 
9434     x = nextx = ax;
9435     y = nexty = ay;
9436     spacing = state.spacing;
9437 
9438     if (astr.ptr is null) {
9439            static if (is(CharType == char)) astr = "";
9440       else static if (is(CharType == wchar)) astr = ""w;
9441       else static if (is(CharType == dchar)) astr = ""d;
9442       else static assert(0, "wtf?!");
9443     }
9444     s = astr.ptr;
9445     n = astr.ptr;
9446     e = astr.ptr+astr.length;
9447 
9448     codepoint = 0;
9449     prevGlyphIndex = -1;
9450     bitmapOption = abitmapOption;
9451     stash = astash;
9452 
9453     return true;
9454   }
9455 
9456   bool getDummyChar (ref FONSQuad quad) nothrow @trusted @nogc {
9457     if (stash is null || font is null) return false;
9458     // get glyph and quad
9459     x = nextx;
9460     y = nexty;
9461     FONSglyph* glyph = stash.getGlyph(font, 0xFFFD, isize, iblur, bitmapOption);
9462     if (glyph !is null) {
9463       stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9464       prevGlyphIndex = glyph.index;
9465       return true;
9466     } else {
9467       prevGlyphIndex = -1;
9468       return false;
9469     }
9470   }
9471 
9472   bool next (ref FONSQuad quad) nothrow @trusted @nogc {
9473     if (stash is null || font is null) return false;
9474     FONSglyph* glyph = null;
9475     static if (is(CharType == char)) {
9476       const(char)* str = this.n;
9477       this.s = this.n;
9478       if (str is this.e) return false;
9479       const(char)* e = this.e;
9480       for (; str !is e; ++str) {
9481         /*if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;*/
9482         mixin(DecUtfMixin!("this.utf8state", "this.codepoint", "*cast(const(ubyte)*)str"));
9483         if (utf8state) continue;
9484         ++str; // 'cause we'll break anyway
9485         // get glyph and quad
9486         x = nextx;
9487         y = nexty;
9488         glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption);
9489         if (glyph !is null) {
9490           stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9491           prevGlyphIndex = glyph.index;
9492         } else {
9493           prevGlyphIndex = -1;
9494         }
9495         break;
9496       }
9497       this.n = str;
9498     } else {
9499       const(CharType)* str = this.n;
9500       this.s = this.n;
9501       if (str is this.e) return false;
9502       codepoint = cast(uint)(*str++);
9503       if (codepoint > dchar.max) codepoint = 0xFFFD;
9504       // get glyph and quad
9505       x = nextx;
9506       y = nexty;
9507       glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption);
9508       if (glyph !is null) {
9509         stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9510         prevGlyphIndex = glyph.index;
9511       } else {
9512         prevGlyphIndex = -1;
9513       }
9514       this.n = str;
9515     }
9516     return true;
9517   }
9518 }
9519 
9520 
9521 // ////////////////////////////////////////////////////////////////////////// //
9522 //static if (!HasAST) version = nanovg_use_freetype_ii_x;
9523 
9524 /*version(nanovg_use_freetype_ii_x)*/ static if (!NanoVegaIsUsingSTBTTF) {
9525 version(nanovg_builtin_freetype_bindings) {
9526 pragma(lib, "freetype");
9527 private extern(C) nothrow @trusted @nogc {
9528 private import core.stdc.config : c_long, c_ulong;
9529 alias FT_Pos = c_long;
9530 // config/ftconfig.h
9531 alias FT_Int16 = short;
9532 alias FT_UInt16 = ushort;
9533 alias FT_Int32 = int;
9534 alias FT_UInt32 = uint;
9535 alias FT_Fast = int;
9536 alias FT_UFast = uint;
9537 alias FT_Int64 = long;
9538 alias FT_Uint64 = ulong;
9539 // fttypes.h
9540 alias FT_Bool = ubyte;
9541 alias FT_FWord = short;
9542 alias FT_UFWord = ushort;
9543 alias FT_Char = char;
9544 alias FT_Byte = ubyte;
9545 alias FT_Bytes = FT_Byte*;
9546 alias FT_Tag = FT_UInt32;
9547 alias FT_String = char;
9548 alias FT_Short = short;
9549 alias FT_UShort = ushort;
9550 alias FT_Int = int;
9551 alias FT_UInt = uint;
9552 alias FT_Long = c_long;
9553 alias FT_ULong = c_ulong;
9554 alias FT_F2Dot14 = short;
9555 alias FT_F26Dot6 = c_long;
9556 alias FT_Fixed = c_long;
9557 alias FT_Error = int;
9558 alias FT_Pointer = void*;
9559 alias FT_Offset = usize;
9560 alias FT_PtrDist = ptrdiff_t;
9561 
9562 struct FT_UnitVector {
9563   FT_F2Dot14 x;
9564   FT_F2Dot14 y;
9565 }
9566 
9567 struct FT_Matrix {
9568   FT_Fixed xx, xy;
9569   FT_Fixed yx, yy;
9570 }
9571 
9572 struct FT_Data {
9573   const(FT_Byte)* pointer;
9574   FT_Int length;
9575 }
9576 alias FT_Face = FT_FaceRec*;
9577 struct FT_FaceRec {
9578   FT_Long num_faces;
9579   FT_Long face_index;
9580   FT_Long face_flags;
9581   FT_Long style_flags;
9582   FT_Long num_glyphs;
9583   FT_String* family_name;
9584   FT_String* style_name;
9585   FT_Int num_fixed_sizes;
9586   FT_Bitmap_Size* available_sizes;
9587   FT_Int num_charmaps;
9588   FT_CharMap* charmaps;
9589   FT_Generic generic;
9590   FT_BBox bbox;
9591   FT_UShort units_per_EM;
9592   FT_Short ascender;
9593   FT_Short descender;
9594   FT_Short height;
9595   FT_Short max_advance_width;
9596   FT_Short max_advance_height;
9597   FT_Short underline_position;
9598   FT_Short underline_thickness;
9599   FT_GlyphSlot glyph;
9600   FT_Size size;
9601   FT_CharMap charmap;
9602   FT_Driver driver;
9603   FT_Memory memory;
9604   FT_Stream stream;
9605   FT_ListRec sizes_list;
9606   FT_Generic autohint;
9607   void* extensions;
9608   FT_Face_Internal internal;
9609 }
9610 struct FT_Bitmap_Size {
9611   FT_Short height;
9612   FT_Short width;
9613   FT_Pos size;
9614   FT_Pos x_ppem;
9615   FT_Pos y_ppem;
9616 }
9617 alias FT_CharMap = FT_CharMapRec*;
9618 struct FT_CharMapRec {
9619   FT_Face face;
9620   FT_Encoding encoding;
9621   FT_UShort platform_id;
9622   FT_UShort encoding_id;
9623 }
9624 extern(C) nothrow @nogc { alias FT_Generic_Finalizer = void function (void* object); }
9625 struct FT_Generic {
9626   void* data;
9627   FT_Generic_Finalizer finalizer;
9628 }
9629 struct FT_Vector {
9630   FT_Pos x;
9631   FT_Pos y;
9632 }
9633 struct FT_BBox {
9634   FT_Pos xMin, yMin;
9635   FT_Pos xMax, yMax;
9636 }
9637 alias FT_Pixel_Mode = int;
9638 enum {
9639   FT_PIXEL_MODE_NONE = 0,
9640   FT_PIXEL_MODE_MONO,
9641   FT_PIXEL_MODE_GRAY,
9642   FT_PIXEL_MODE_GRAY2,
9643   FT_PIXEL_MODE_GRAY4,
9644   FT_PIXEL_MODE_LCD,
9645   FT_PIXEL_MODE_LCD_V,
9646   FT_PIXEL_MODE_MAX
9647 }
9648 struct FT_Bitmap {
9649   uint rows;
9650   uint width;
9651   int pitch;
9652   ubyte* buffer;
9653   ushort num_grays;
9654   ubyte pixel_mode;
9655   ubyte palette_mode;
9656   void* palette;
9657 }
9658 struct FT_Outline {
9659   short n_contours;
9660   short n_points;
9661   FT_Vector* points;
9662   byte* tags;
9663   short* contours;
9664   int flags;
9665 }
9666 alias FT_GlyphSlot = FT_GlyphSlotRec*;
9667 struct FT_GlyphSlotRec {
9668   FT_Library library;
9669   FT_Face face;
9670   FT_GlyphSlot next;
9671   FT_UInt reserved;
9672   FT_Generic generic;
9673   FT_Glyph_Metrics metrics;
9674   FT_Fixed linearHoriAdvance;
9675   FT_Fixed linearVertAdvance;
9676   FT_Vector advance;
9677   FT_Glyph_Format format;
9678   FT_Bitmap bitmap;
9679   FT_Int bitmap_left;
9680   FT_Int bitmap_top;
9681   FT_Outline outline;
9682   FT_UInt num_subglyphs;
9683   FT_SubGlyph subglyphs;
9684   void* control_data;
9685   c_long control_len;
9686   FT_Pos lsb_delta;
9687   FT_Pos rsb_delta;
9688   void* other;
9689   FT_Slot_Internal internal;
9690 }
9691 alias FT_Size = FT_SizeRec*;
9692 struct FT_SizeRec {
9693   FT_Face face;
9694   FT_Generic generic;
9695   FT_Size_Metrics metrics;
9696   FT_Size_Internal internal;
9697 }
9698 alias FT_Encoding = FT_Tag;
9699 alias FT_Face_Internal = void*;
9700 alias FT_Driver = void*;
9701 alias FT_Memory = void*;
9702 alias FT_Stream = void*;
9703 alias FT_Library = void*;
9704 alias FT_SubGlyph = void*;
9705 alias FT_Slot_Internal = void*;
9706 alias FT_Size_Internal = void*;
9707 alias FT_ListNode = FT_ListNodeRec*;
9708 alias FT_List = FT_ListRec*;
9709 struct FT_ListNodeRec {
9710   FT_ListNode prev;
9711   FT_ListNode next;
9712   void* data;
9713 }
9714 struct FT_ListRec {
9715   FT_ListNode head;
9716   FT_ListNode tail;
9717 }
9718 struct FT_Glyph_Metrics {
9719   FT_Pos width;
9720   FT_Pos height;
9721   FT_Pos horiBearingX;
9722   FT_Pos horiBearingY;
9723   FT_Pos horiAdvance;
9724   FT_Pos vertBearingX;
9725   FT_Pos vertBearingY;
9726   FT_Pos vertAdvance;
9727 }
9728 alias FT_Glyph_Format = FT_Tag;
9729 FT_Tag FT_MAKE_TAG (char x1, char x2, char x3, char x4) pure nothrow @safe @nogc {
9730   pragma(inline, true);
9731   return cast(FT_UInt32)((x1<<24)|(x2<<16)|(x3<<8)|x4);
9732 }
9733 enum : FT_Tag {
9734   FT_GLYPH_FORMAT_NONE = 0,
9735   FT_GLYPH_FORMAT_COMPOSITE = FT_MAKE_TAG('c','o','m','p'),
9736   FT_GLYPH_FORMAT_BITMAP = FT_MAKE_TAG('b','i','t','s'),
9737   FT_GLYPH_FORMAT_OUTLINE = FT_MAKE_TAG('o','u','t','l'),
9738   FT_GLYPH_FORMAT_PLOTTER = FT_MAKE_TAG('p','l','o','t'),
9739 }
9740 struct FT_Size_Metrics {
9741   FT_UShort x_ppem;
9742   FT_UShort y_ppem;
9743 
9744   FT_Fixed x_scale;
9745   FT_Fixed y_scale;
9746 
9747   FT_Pos ascender;
9748   FT_Pos descender;
9749   FT_Pos height;
9750   FT_Pos max_advance;
9751 }
9752 enum FT_LOAD_DEFAULT = 0x0U;
9753 enum FT_LOAD_NO_SCALE = 1U<<0;
9754 enum FT_LOAD_NO_HINTING = 1U<<1;
9755 enum FT_LOAD_RENDER = 1U<<2;
9756 enum FT_LOAD_NO_BITMAP = 1U<<3;
9757 enum FT_LOAD_VERTICAL_LAYOUT = 1U<<4;
9758 enum FT_LOAD_FORCE_AUTOHINT = 1U<<5;
9759 enum FT_LOAD_CROP_BITMAP = 1U<<6;
9760 enum FT_LOAD_PEDANTIC = 1U<<7;
9761 enum FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1U<<9;
9762 enum FT_LOAD_NO_RECURSE = 1U<<10;
9763 enum FT_LOAD_IGNORE_TRANSFORM = 1U<<11;
9764 enum FT_LOAD_MONOCHROME = 1U<<12;
9765 enum FT_LOAD_LINEAR_DESIGN = 1U<<13;
9766 enum FT_LOAD_NO_AUTOHINT = 1U<<15;
9767 enum FT_LOAD_COLOR = 1U<<20;
9768 enum FT_LOAD_COMPUTE_METRICS = 1U<<21;
9769 enum FT_FACE_FLAG_KERNING = 1U<<6;
9770 alias FT_Kerning_Mode = int;
9771 enum /*FT_Kerning_Mode*/ {
9772   FT_KERNING_DEFAULT = 0,
9773   FT_KERNING_UNFITTED,
9774   FT_KERNING_UNSCALED
9775 }
9776 extern(C) nothrow @nogc {
9777   alias FT_Outline_MoveToFunc = int function (const(FT_Vector)*, void*);
9778   alias FT_Outline_LineToFunc = int function (const(FT_Vector)*, void*);
9779   alias FT_Outline_ConicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, void*);
9780   alias FT_Outline_CubicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, const(FT_Vector)*, void*);
9781 }
9782 struct FT_Outline_Funcs {
9783   FT_Outline_MoveToFunc move_to;
9784   FT_Outline_LineToFunc line_to;
9785   FT_Outline_ConicToFunc conic_to;
9786   FT_Outline_CubicToFunc cubic_to;
9787   int shift;
9788   FT_Pos delta;
9789 }
9790 
9791 FT_Error FT_Init_FreeType (FT_Library*);
9792 FT_Error FT_New_Memory_Face (FT_Library, const(FT_Byte)*, FT_Long, FT_Long, FT_Face*);
9793 FT_UInt FT_Get_Char_Index (FT_Face, FT_ULong);
9794 FT_Error FT_Set_Pixel_Sizes (FT_Face, FT_UInt, FT_UInt);
9795 FT_Error FT_Load_Glyph (FT_Face, FT_UInt, FT_Int32);
9796 FT_Error FT_Get_Advance (FT_Face, FT_UInt, FT_Int32, FT_Fixed*);
9797 FT_Error FT_Get_Kerning (FT_Face, FT_UInt, FT_UInt, FT_UInt, FT_Vector*);
9798 void FT_Outline_Get_CBox (const(FT_Outline)*, FT_BBox*);
9799 FT_Error FT_Outline_Decompose (FT_Outline*, const(FT_Outline_Funcs)*, void*);
9800 }
9801 } else version(bindbc) {
9802   import bindbc.freetype;
9803   alias FT_KERNING_DEFAULT = FT_Kerning_Mode.FT_KERNING_DEFAULT;
9804   alias FT_KERNING_UNFITTED = FT_Kerning_Mode.FT_KERNING_UNFITTED;
9805   alias FT_KERNING_UNSCALED = FT_Kerning_Mode.FT_KERNING_UNSCALED;
9806 } else {
9807   import iv.freetype;
9808 }
9809 
9810 struct FONSttFontImpl {
9811   FT_Face font;
9812   bool mono; // no aa?
9813 }
9814 
9815 __gshared FT_Library ftLibrary;
9816 
9817 int fons__tt_init (FONSContext context) nothrow @trusted @nogc {
9818   FT_Error ftError;
9819   //FONS_NOTUSED(context);
9820   ftError = FT_Init_FreeType(&ftLibrary);
9821   return (ftError == 0);
9822 }
9823 
9824 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc {
9825   font.mono = v;
9826 }
9827 
9828 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc {
9829   return font.mono;
9830 }
9831 
9832 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc {
9833   FT_Error ftError;
9834   //font.font.userdata = stash;
9835   ftError = FT_New_Memory_Face(ftLibrary, cast(const(FT_Byte)*)data, dataSize, 0, &font.font);
9836   return ftError == 0;
9837 }
9838 
9839 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc {
9840   *ascent = font.font.ascender;
9841   *descent = font.font.descender;
9842   *lineGap = font.font.height-(*ascent - *descent);
9843 }
9844 
9845 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc {
9846   return size/(font.font.ascender-font.font.descender);
9847 }
9848 
9849 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc {
9850   return FT_Get_Char_Index(font.font, codepoint);
9851 }
9852 
9853 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 {
9854   FT_Error ftError;
9855   FT_GlyphSlot ftGlyph;
9856   //version(nanovg_ignore_mono) enum exflags = 0;
9857   //else version(nanovg_ft_mono) enum exflags = FT_LOAD_MONOCHROME; else enum exflags = 0;
9858   uint exflags = (font.mono ? FT_LOAD_MONOCHROME : 0);
9859   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)));
9860   if (ftError) return 0;
9861   ftError = FT_Load_Glyph(font.font, glyph, FT_LOAD_RENDER|/*FT_LOAD_NO_AUTOHINT|*/exflags);
9862   if (ftError) return 0;
9863   ftError = FT_Get_Advance(font.font, glyph, FT_LOAD_NO_SCALE|/*FT_LOAD_NO_AUTOHINT|*/exflags, cast(FT_Fixed*)advance);
9864   if (ftError) return 0;
9865   ftGlyph = font.font.glyph;
9866   *lsb = cast(int)ftGlyph.metrics.horiBearingX;
9867   *x0 = ftGlyph.bitmap_left;
9868   *x1 = *x0+ftGlyph.bitmap.width;
9869   *y0 = -ftGlyph.bitmap_top;
9870   *y1 = *y0+ftGlyph.bitmap.rows;
9871   return 1;
9872 }
9873 
9874 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc {
9875   FT_GlyphSlot ftGlyph = font.font.glyph;
9876   //FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap
9877   //version(nanovg_ignore_mono) enum RenderAA = true;
9878   //else version(nanovg_ft_mono) enum RenderAA = false;
9879   //else enum RenderAA = true;
9880   if (font.mono) {
9881     auto src = ftGlyph.bitmap.buffer;
9882     auto dst = output;
9883     auto spt = ftGlyph.bitmap.pitch;
9884     if (spt < 0) spt = -spt;
9885     foreach (int y; 0..ftGlyph.bitmap.rows) {
9886       ubyte count = 0, b = 0;
9887       auto s = src;
9888       auto d = dst;
9889       foreach (int x; 0..ftGlyph.bitmap.width) {
9890         if (count-- == 0) { count = 7; b = *s++; } else b <<= 1;
9891         *d++ = (b&0x80 ? 255 : 0);
9892       }
9893       src += spt;
9894       dst += outStride;
9895     }
9896   } else {
9897     auto src = ftGlyph.bitmap.buffer;
9898     auto dst = output;
9899     auto spt = ftGlyph.bitmap.pitch;
9900     if (spt < 0) spt = -spt;
9901     foreach (int y; 0..ftGlyph.bitmap.rows) {
9902       import core.stdc.string : memcpy;
9903       //dst[0..ftGlyph.bitmap.width] = src[0..ftGlyph.bitmap.width];
9904       memcpy(dst, src, ftGlyph.bitmap.width);
9905       src += spt;
9906       dst += outStride;
9907     }
9908   }
9909 }
9910 
9911 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc {
9912   FT_Vector ftKerning;
9913   version(none) {
9914     // fitted kerning
9915     FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning);
9916     //{ import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d\n", glyph1, glyph2, ftKerning.x, ftKerning.y); }
9917     return cast(int)ftKerning.x; // round up and convert to integer
9918   } else {
9919     // unfitted kerning
9920     //FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNFITTED, &ftKerning);
9921     if (glyph1 <= 0 || glyph2 <= 0 || (font.font.face_flags&FT_FACE_FLAG_KERNING) == 0) return 0;
9922     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;
9923     if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning)) return 0;
9924     version(none) {
9925       if (ftKerning.x) {
9926         //{ import core.stdc.stdio : printf; printf("has kerning: %u\n", cast(uint)(font.font.face_flags&FT_FACE_FLAG_KERNING)); }
9927         { import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d (size=%g)\n", glyph1, glyph2, ftKerning.x, ftKerning.y, cast(double)size); }
9928       }
9929     }
9930     version(none) {
9931       FT_Vector kk;
9932       if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNSCALED, &kk)) assert(0, "wtf?!");
9933       auto kadvfrac = FT_MulFix(kk.x, font.font.size.metrics.x_scale); // 1/64 of pixel
9934       //return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
9935       //assert(ftKerning.x == kadvfrac);
9936       if (ftKerning.x || kadvfrac) {
9937         { 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); }
9938       }
9939       //return cast(int)(kadvfrac+(kadvfrac < 0 ? -31 : 32)>>6); // round up and convert to integer
9940       return kadvfrac/64.0f;
9941     }
9942     //return cast(int)(ftKerning.x+(ftKerning.x < 0 ? -31 : 32)>>6); // round up and convert to integer
9943     return ftKerning.x/64.0f;
9944   }
9945 }
9946 
9947 extern(C) nothrow @trusted @nogc {
9948   static struct OutlinerData {
9949     @disable this (this);
9950     void opAssign() (const scope auto ref OutlinerData a) { static assert(0, "no copies!"); }
9951     NVGContext vg;
9952     NVGPathOutline.DataStore* ol;
9953     FT_BBox outlineBBox;
9954   nothrow @trusted @nogc:
9955     static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; }
9956     static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
9957   }
9958 
9959   int fons__nvg__moveto_cb (const(FT_Vector)* to, void* user) {
9960     auto odata = cast(OutlinerData*)user;
9961     if (odata.vg !is null) odata.vg.moveTo(odata.transx(to.x), odata.transy(to.y));
9962     if (odata.ol !is null) {
9963       odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo);
9964       odata.ol.putArgs(odata.transx(to.x));
9965       odata.ol.putArgs(odata.transy(to.y));
9966     }
9967     return 0;
9968   }
9969 
9970   int fons__nvg__lineto_cb (const(FT_Vector)* to, void* user) {
9971     auto odata = cast(OutlinerData*)user;
9972     if (odata.vg !is null) odata.vg.lineTo(odata.transx(to.x), odata.transy(to.y));
9973     if (odata.ol !is null) {
9974       odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo);
9975       odata.ol.putArgs(odata.transx(to.x));
9976       odata.ol.putArgs(odata.transy(to.y));
9977     }
9978     return 0;
9979   }
9980 
9981   int fons__nvg__quadto_cb (const(FT_Vector)* c1, const(FT_Vector)* to, void* user) {
9982     auto odata = cast(OutlinerData*)user;
9983     if (odata.vg !is null) odata.vg.quadTo(odata.transx(c1.x), odata.transy(c1.y), odata.transx(to.x), odata.transy(to.y));
9984     if (odata.ol !is null) {
9985       odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo);
9986       odata.ol.putArgs(odata.transx(c1.x));
9987       odata.ol.putArgs(odata.transy(c1.y));
9988       odata.ol.putArgs(odata.transx(to.x));
9989       odata.ol.putArgs(odata.transy(to.y));
9990     }
9991     return 0;
9992   }
9993 
9994   int fons__nvg__cubicto_cb (const(FT_Vector)* c1, const(FT_Vector)* c2, const(FT_Vector)* to, void* user) {
9995     auto odata = cast(OutlinerData*)user;
9996     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));
9997     if (odata.ol !is null) {
9998       odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo);
9999       odata.ol.putArgs(odata.transx(c1.x));
10000       odata.ol.putArgs(odata.transy(c1.y));
10001       odata.ol.putArgs(odata.transx(c2.x));
10002       odata.ol.putArgs(odata.transy(c2.y));
10003       odata.ol.putArgs(odata.transx(to.x));
10004       odata.ol.putArgs(odata.transy(to.y));
10005     }
10006     return 0;
10007   }
10008 }
10009 
10010 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc {
10011   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10012 
10013   FT_Outline_Funcs funcs;
10014   funcs.move_to = &fons__nvg__moveto_cb;
10015   funcs.line_to = &fons__nvg__lineto_cb;
10016   funcs.conic_to = &fons__nvg__quadto_cb;
10017   funcs.cubic_to = &fons__nvg__cubicto_cb;
10018 
10019   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
10020   if (err) { bounds[] = 0; return false; }
10021   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; }
10022 
10023   FT_Outline outline = font.font.glyph.outline;
10024 
10025   OutlinerData odata;
10026   odata.vg = vg;
10027   FT_Outline_Get_CBox(&outline, &odata.outlineBBox);
10028 
10029   err = FT_Outline_Decompose(&outline, &funcs, &odata);
10030   if (err) { bounds[] = 0; return false; }
10031   if (bounds.length > 0) bounds.ptr[0] = odata.outlineBBox.xMin;
10032   if (bounds.length > 1) bounds.ptr[1] = -odata.outlineBBox.yMax;
10033   if (bounds.length > 2) bounds.ptr[2] = odata.outlineBBox.xMax;
10034   if (bounds.length > 3) bounds.ptr[3] = -odata.outlineBBox.yMin;
10035   return true;
10036 }
10037 
10038 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
10039   FT_Outline_Funcs funcs;
10040   funcs.move_to = &fons__nvg__moveto_cb;
10041   funcs.line_to = &fons__nvg__lineto_cb;
10042   funcs.conic_to = &fons__nvg__quadto_cb;
10043   funcs.cubic_to = &fons__nvg__cubicto_cb;
10044 
10045   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
10046   if (err) return false;
10047   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) return false;
10048 
10049   FT_Outline outline = font.font.glyph.outline;
10050 
10051   OutlinerData odata;
10052   odata.ol = ol;
10053   FT_Outline_Get_CBox(&outline, &odata.outlineBBox);
10054 
10055   err = FT_Outline_Decompose(&outline, &funcs, &odata);
10056   if (err) return false;
10057   ol.bounds.ptr[0] = odata.outlineBBox.xMin;
10058   ol.bounds.ptr[1] = -odata.outlineBBox.yMax;
10059   ol.bounds.ptr[2] = odata.outlineBBox.xMax;
10060   ol.bounds.ptr[3] = -odata.outlineBBox.yMin;
10061   return true;
10062 }
10063 
10064 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc {
10065   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10066 
10067   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
10068   if (err) return false;
10069   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; }
10070 
10071   FT_Outline outline = font.font.glyph.outline;
10072   FT_BBox outlineBBox;
10073   FT_Outline_Get_CBox(&outline, &outlineBBox);
10074   if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin;
10075   if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax;
10076   if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax;
10077   if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin;
10078   return true;
10079 }
10080 
10081 
10082 } else {
10083 // ////////////////////////////////////////////////////////////////////////// //
10084 // sorry
10085 import std.traits : isFunctionPointer, isDelegate;
10086 private auto assumeNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) {
10087   import std.traits;
10088   enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
10089   return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
10090 }
10091 
10092 private auto forceNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) {
10093   try {
10094     return assumeNoThrowNoGC(t)();
10095   } catch (Exception e) {
10096     assert(0, "OOPS!");
10097   }
10098 }
10099 
10100 struct FONSttFontImpl {
10101   stbtt_fontinfo font;
10102   bool mono; // no aa?
10103 }
10104 
10105 int fons__tt_init (FONSContext context) nothrow @trusted @nogc {
10106   return 1;
10107 }
10108 
10109 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc {
10110   font.mono = v;
10111 }
10112 
10113 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc {
10114   return font.mono;
10115 }
10116 
10117 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc {
10118   int stbError;
10119   font.font.userdata = context;
10120   forceNoThrowNoGC({ stbError = stbtt_InitFont(&font.font, data, 0); });
10121   return stbError;
10122 }
10123 
10124 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc {
10125   forceNoThrowNoGC({ stbtt_GetFontVMetrics(&font.font, ascent, descent, lineGap); });
10126 }
10127 
10128 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc {
10129   float res = void;
10130   forceNoThrowNoGC({ res = stbtt_ScaleForPixelHeight(&font.font, size); });
10131   return res;
10132 }
10133 
10134 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc {
10135   int res;
10136   forceNoThrowNoGC({ res = stbtt_FindGlyphIndex(&font.font, codepoint); });
10137   return res;
10138 }
10139 
10140 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 {
10141   forceNoThrowNoGC({ stbtt_GetGlyphHMetrics(&font.font, glyph, advance, lsb); });
10142   forceNoThrowNoGC({ stbtt_GetGlyphBitmapBox(&font.font, glyph, scale, scale, x0, y0, x1, y1); });
10143   return 1;
10144 }
10145 
10146 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc {
10147   forceNoThrowNoGC({ stbtt_MakeGlyphBitmap(&font.font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); });
10148 }
10149 
10150 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc {
10151   // FUnits -> pixels: pointSize * resolution / (72 points per inch * units_per_em)
10152   // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#converting
10153   float res = void;
10154   forceNoThrowNoGC({
10155     res = stbtt_GetGlyphKernAdvance(&font.font, glyph1, glyph2);
10156     res *= stbtt_ScaleForPixelHeight(&font.font, size);
10157   });
10158   /*
10159   if (res != 0) {
10160     { 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)); }
10161   }
10162   */
10163   //k8: dunno if this is right; i guess it isn't but...
10164   return res;
10165 }
10166 
10167 // old arsd.ttf sux! ;-)
10168 static if (is(typeof(STBTT_vcubic))) {
10169 
10170 static struct OutlinerData {
10171   @disable this (this);
10172   void opAssign() (const scope auto ref OutlinerData a) { static assert(0, "no copies!"); }
10173   NVGPathOutline.DataStore* ol;
10174 nothrow @trusted @nogc:
10175   static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; }
10176   static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
10177 }
10178 
10179 
10180 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc {
10181   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10182 
10183   bool okflag = false;
10184 
10185   forceNoThrowNoGC({
10186     int x0, y0, x1, y1;
10187     if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10188       bounds[] = 0;
10189       return;
10190     }
10191 
10192     if (bounds.length > 0) bounds.ptr[0] = x0;
10193     if (bounds.length > 1) bounds.ptr[1] = -y1;
10194     if (bounds.length > 2) bounds.ptr[2] = x1;
10195     if (bounds.length > 3) bounds.ptr[3] = -y0;
10196 
10197     static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
10198 
10199     stbtt_vertex* verts = null;
10200     scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); }
10201     int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts);
10202     if (vcount < 1) return;
10203 
10204     foreach (const ref vt; verts[0..vcount]) {
10205       switch (vt.type) {
10206         case STBTT_vmove: vg.moveTo(vt.x, transy(vt.y)); break;
10207         case STBTT_vline: vg.lineTo(vt.x, transy(vt.y)); break;
10208         case STBTT_vcurve: vg.quadTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy)); break;
10209         case STBTT_vcubic: vg.bezierTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy), vt.cx1, transy(vt.cy1)); break;
10210         default:
10211       }
10212     }
10213 
10214     okflag = true;
10215   });
10216 
10217   return okflag;
10218 }
10219 
10220 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
10221   bool okflag = false;
10222 
10223   forceNoThrowNoGC({
10224     int x0, y0, x1, y1;
10225 
10226     if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10227       ol.bounds[] = 0;
10228       return;
10229     }
10230 
10231     ol.bounds.ptr[0] = x0;
10232     ol.bounds.ptr[1] = -y1;
10233     ol.bounds.ptr[2] = x1;
10234     ol.bounds.ptr[3] = -y0;
10235 
10236     stbtt_vertex* verts = null;
10237     scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); }
10238     int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts);
10239     if (vcount < 1) return;
10240 
10241     OutlinerData odata;
10242     odata.ol = ol;
10243 
10244     foreach (const ref vt; verts[0..vcount]) {
10245       switch (vt.type) {
10246         case STBTT_vmove:
10247           odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo);
10248           odata.ol.putArgs(odata.transx(vt.x));
10249           odata.ol.putArgs(odata.transy(vt.y));
10250           break;
10251         case STBTT_vline:
10252           odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo);
10253           odata.ol.putArgs(odata.transx(vt.x));
10254           odata.ol.putArgs(odata.transy(vt.y));
10255           break;
10256         case STBTT_vcurve:
10257           odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo);
10258           odata.ol.putArgs(odata.transx(vt.x));
10259           odata.ol.putArgs(odata.transy(vt.y));
10260           odata.ol.putArgs(odata.transx(vt.cx));
10261           odata.ol.putArgs(odata.transy(vt.cy));
10262           break;
10263         case STBTT_vcubic:
10264           odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo);
10265           odata.ol.putArgs(odata.transx(vt.x));
10266           odata.ol.putArgs(odata.transy(vt.y));
10267           odata.ol.putArgs(odata.transx(vt.cx));
10268           odata.ol.putArgs(odata.transy(vt.cy));
10269           odata.ol.putArgs(odata.transx(vt.cx1));
10270           odata.ol.putArgs(odata.transy(vt.cy1));
10271           break;
10272         default:
10273       }
10274     }
10275 
10276     okflag = true;
10277   });
10278 
10279   return okflag;
10280 }
10281 
10282 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc {
10283   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10284 
10285   bool okflag = false;
10286 
10287   forceNoThrowNoGC({
10288     int x0, y0, x1, y1;
10289     if (stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10290       if (bounds.length > 0) bounds.ptr[0] = x0;
10291       if (bounds.length > 1) bounds.ptr[1] = -y1;
10292       if (bounds.length > 2) bounds.ptr[2] = x1;
10293       if (bounds.length > 3) bounds.ptr[3] = -y0;
10294       okflag = true;
10295     } else {
10296       bounds[] = 0;
10297     }
10298   });
10299 
10300   return okflag;
10301 }
10302 
10303 } // check for old stb_ttf
10304 
10305 
10306 } // version
10307 
10308 
10309 // ////////////////////////////////////////////////////////////////////////// //
10310 private:
10311 enum FONS_SCRATCH_BUF_SIZE = 64000;
10312 enum FONS_HASH_LUT_SIZE = 256;
10313 enum FONS_INIT_FONTS = 4;
10314 enum FONS_INIT_GLYPHS = 256;
10315 enum FONS_INIT_ATLAS_NODES = 256;
10316 enum FONS_VERTEX_COUNT = 1024;
10317 enum FONS_MAX_STATES = 20;
10318 enum FONS_MAX_FALLBACKS = 20;
10319 
10320 
10321 struct FONSglyph {
10322   uint codepoint;
10323   int index;
10324   int next;
10325   short size, blur;
10326   short x0, y0, x1, y1;
10327   short xadv, xoff, yoff;
10328 }
10329 
10330 // refcounted
10331 struct FONSfontData {
10332   ubyte* data;
10333   int dataSize;
10334   bool freeData;
10335   int rc;
10336 
10337   @disable this (this); // no copies
10338   void opAssign() (const scope auto ref FONSfontData a) { static assert(0, "no copies!"); }
10339 }
10340 
10341 // won't set rc to 1
10342 FONSfontData* fons__createFontData (ubyte* adata, int asize, bool afree) nothrow @trusted @nogc {
10343   import core.stdc.stdlib : malloc;
10344   assert(adata !is null);
10345   assert(asize > 0);
10346   auto res = cast(FONSfontData*)malloc(FONSfontData.sizeof);
10347   if (res is null) assert(0, "FONS: out of memory");
10348   res.data = adata;
10349   res.dataSize = asize;
10350   res.freeData = afree;
10351   res.rc = 0;
10352   return res;
10353 }
10354 
10355 void incref (FONSfontData* fd) pure nothrow @trusted @nogc {
10356   pragma(inline, true);
10357   if (fd !is null) ++fd.rc;
10358 }
10359 
10360 void decref (ref FONSfontData* fd) nothrow @trusted @nogc {
10361   if (fd !is null) {
10362     if (--fd.rc == 0) {
10363       import core.stdc.stdlib : free;
10364       if (fd.freeData && fd.data !is null) {
10365         free(fd.data);
10366         fd.data = null;
10367       }
10368       free(fd);
10369       fd = null;
10370     }
10371   }
10372 }
10373 
10374 // as creating and destroying fonts is a rare operation, malloc some data
10375 struct FONSfont {
10376   FONSttFontImpl font;
10377   char* name; // malloced, strz, always lowercase
10378   uint namelen;
10379   uint namehash;
10380   char* path; // malloced, strz
10381   FONSfontData* fdata;
10382   float ascender;
10383   float descender;
10384   float lineh;
10385   FONSglyph* glyphs;
10386   int cglyphs;
10387   int nglyphs;
10388   int[FONS_HASH_LUT_SIZE] lut;
10389   int[FONS_MAX_FALLBACKS] fallbacks;
10390   int nfallbacks;
10391 
10392   @disable this (this);
10393   void opAssign() (const scope auto ref FONSfont a) { static assert(0, "no copies"); }
10394 
10395   static uint djbhash (const(void)[] s) pure nothrow @safe @nogc {
10396     uint hash = 5381;
10397     foreach (ubyte b; cast(const(ubyte)[])s) {
10398       if (b >= 'A' && b <= 'Z') b += 32; // poor man's tolower
10399       hash = ((hash<<5)+hash)+b;
10400     }
10401     return hash;
10402   }
10403 
10404   // except glyphs
10405   void freeMemory () nothrow @trusted @nogc {
10406     import core.stdc.stdlib : free;
10407     if (name !is null) { free(name); name = null; }
10408     namelen = namehash = 0;
10409     if (path !is null) { free(path); path = null; }
10410     fdata.decref();
10411   }
10412 
10413   // this also calcs name hash
10414   void setName (const(char)[] aname) nothrow @trusted @nogc {
10415     //{ import core.stdc.stdio; printf("setname: [%.*s]\n", cast(uint)aname.length, aname.ptr); }
10416     import core.stdc.stdlib : realloc;
10417     if (aname.length > int.max/32) assert(0, "FONS: invalid font name");
10418     namelen = cast(uint)aname.length;
10419     name = cast(char*)realloc(name, namelen+1);
10420     if (name is null) assert(0, "FONS: out of memory");
10421     if (aname.length) name[0..aname.length] = aname[];
10422     name[namelen] = 0;
10423     // lowercase it
10424     foreach (ref char ch; name[0..namelen]) if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
10425     namehash = djbhash(name[0..namelen]);
10426     //{ import core.stdc.stdio; printf("  [%s] [%.*s] [0x%08x]\n", name, namelen, name, namehash); }
10427   }
10428 
10429   void setPath (const(char)[] apath) nothrow @trusted @nogc {
10430     import core.stdc.stdlib : realloc;
10431     if (apath.length > int.max/32) assert(0, "FONS: invalid font path");
10432     path = cast(char*)realloc(path, apath.length+1);
10433     if (path is null) assert(0, "FONS: out of memory");
10434     if (apath.length) path[0..apath.length] = apath[];
10435     path[apath.length] = 0;
10436   }
10437 
10438   // this won't check hash
10439   bool nameEqu (const(char)[] aname) const pure nothrow @trusted @nogc {
10440     //{ import core.stdc.stdio; printf("nameEqu: aname=[%.*s]; namelen=%u; aslen=%u\n", cast(uint)aname.length, aname.ptr, namelen, cast(uint)aname.length); }
10441     if (namelen != aname.length) return false;
10442     const(char)* ns = name;
10443     // name part
10444     foreach (char ch; aname) {
10445       if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
10446       if (ch != *ns++) return false;
10447     }
10448     // done (length was checked earlier)
10449     return true;
10450   }
10451 
10452   void clear () nothrow @trusted @nogc {
10453     import core.stdc.stdlib : free;
10454     import core.stdc.string : memset;
10455     if (glyphs !is null) free(glyphs);
10456     freeMemory();
10457     memset(&this, 0, this.sizeof);
10458   }
10459 
10460   FONSglyph* allocGlyph () nothrow @trusted @nogc {
10461     if (nglyphs+1 > cglyphs) {
10462       import core.stdc.stdlib : realloc;
10463       cglyphs = (cglyphs == 0 ? 8 : cglyphs*2);
10464       glyphs = cast(FONSglyph*)realloc(glyphs, FONSglyph.sizeof*cglyphs);
10465       if (glyphs is null) assert(0, "FontStash: out of memory");
10466     }
10467     ++nglyphs;
10468     return &glyphs[nglyphs-1];
10469   }
10470 }
10471 
10472 void kill (ref FONSfont* font) nothrow @trusted @nogc {
10473   if (font !is null) {
10474     import core.stdc.stdlib : free;
10475     font.clear();
10476     free(font);
10477     font = null;
10478   }
10479 }
10480 
10481 
10482 // ////////////////////////////////////////////////////////////////////////// //
10483 struct FONSstate {
10484   int font;
10485   NVGTextAlign talign;
10486   float size = 0;
10487   float blur = 0;
10488   float spacing = 0;
10489 }
10490 
10491 
10492 // ////////////////////////////////////////////////////////////////////////// //
10493 // atlas based on Skyline Bin Packer by Jukka Jylänki
10494 alias FONSAtlas = FONSatlasInternal*;
10495 
10496 struct FONSatlasInternal {
10497   static struct Node {
10498     short x, y, width;
10499   }
10500 
10501   int width, height;
10502   Node* nodes;
10503   int nnodes;
10504   int cnodes;
10505 
10506   @disable this (this);
10507   void opAssign() (const scope auto ref FONSatlasInternal a) { static assert(0, "no copies"); }
10508 
10509 nothrow @trusted @nogc:
10510   static FONSAtlas create (int w, int h, int nnodes) {
10511     import core.stdc.stdlib : malloc;
10512     import core.stdc.string : memset;
10513 
10514     FONSAtlas atlas = cast(FONSAtlas)malloc(FONSatlasInternal.sizeof);
10515     if (atlas is null) assert(0, "FontStash: out of memory");
10516     memset(atlas, 0, FONSatlasInternal.sizeof);
10517 
10518     atlas.width = w;
10519     atlas.height = h;
10520 
10521     // allocate space for skyline nodes
10522     atlas.nodes = cast(Node*)malloc(Node.sizeof*nnodes);
10523     if (atlas.nodes is null) assert(0, "FontStash: out of memory");
10524     memset(atlas.nodes, 0, Node.sizeof*nnodes);
10525     atlas.nnodes = 0;
10526     atlas.cnodes = nnodes;
10527 
10528     // init root node
10529     atlas.nodes[0].x = 0;
10530     atlas.nodes[0].y = 0;
10531     atlas.nodes[0].width = cast(short)w;
10532     ++atlas.nnodes;
10533 
10534     return atlas;
10535   }
10536 
10537   void clear () {
10538     import core.stdc.stdlib : free;
10539     import core.stdc.string : memset;
10540 
10541     if (nodes !is null) free(nodes);
10542     memset(&this, 0, this.sizeof);
10543   }
10544 
10545   void insertNode (int idx, int x, int y, int w) {
10546     if (nnodes+1 > cnodes) {
10547       import core.stdc.stdlib : realloc;
10548       cnodes = (cnodes == 0 ? 8 : cnodes*2);
10549       nodes = cast(Node*)realloc(nodes, Node.sizeof*cnodes);
10550       if (nodes is null) assert(0, "FontStash: out of memory");
10551     }
10552     for (int i = nnodes; i > idx; --i) nodes[i] = nodes[i-1];
10553     nodes[idx].x = cast(short)x;
10554     nodes[idx].y = cast(short)y;
10555     nodes[idx].width = cast(short)w;
10556     ++nnodes;
10557   }
10558 
10559   void removeNode (int idx) {
10560     if (nnodes == 0) return;
10561     foreach (immutable int i; idx+1..nnodes) nodes[i-1] = nodes[i];
10562     --nnodes;
10563   }
10564 
10565   // insert node for empty space
10566   void expand (int w, int h) {
10567     if (w > width) insertNode(nnodes, width, 0, w-width);
10568     width = w;
10569     height = h;
10570   }
10571 
10572   void reset (int w, int h) {
10573     width = w;
10574     height = h;
10575     nnodes = 0;
10576     // init root node
10577     nodes[0].x = 0;
10578     nodes[0].y = 0;
10579     nodes[0].width = cast(short)w;
10580     ++nnodes;
10581   }
10582 
10583   void addSkylineLevel (int idx, int x, int y, int w, int h) {
10584     insertNode(idx, x, y+h, w);
10585 
10586     // delete skyline segments that fall under the shadow of the new segment
10587     for (int i = idx+1; i < nnodes; ++i) {
10588       if (nodes[i].x < nodes[i-1].x+nodes[i-1].width) {
10589         int shrink = nodes[i-1].x+nodes[i-1].width-nodes[i].x;
10590         nodes[i].x += cast(short)shrink;
10591         nodes[i].width -= cast(short)shrink;
10592         if (nodes[i].width <= 0) {
10593           removeNode(i);
10594           --i;
10595         } else {
10596           break;
10597         }
10598       } else {
10599         break;
10600       }
10601     }
10602 
10603     // Merge same height skyline segments that are next to each other
10604     for (int i = 0; i < nnodes-1; ++i) {
10605       if (nodes[i].y == nodes[i+1].y) {
10606         nodes[i].width += nodes[i+1].width;
10607         removeNode(i+1);
10608         --i;
10609       }
10610     }
10611   }
10612 
10613   // checks if there is enough space at the location of skyline span 'i',
10614   // and return the max height of all skyline spans under that at that location,
10615   // (think tetris block being dropped at that position); or -1 if no space found
10616   int rectFits (int i, int w, int h) {
10617     int x = nodes[i].x;
10618     int y = nodes[i].y;
10619     if (x+w > width) return -1;
10620     int spaceLeft = w;
10621     while (spaceLeft > 0) {
10622       if (i == nnodes) return -1;
10623       y = nvg__max(y, nodes[i].y);
10624       if (y+h > height) return -1;
10625       spaceLeft -= nodes[i].width;
10626       ++i;
10627     }
10628     return y;
10629   }
10630 
10631   bool addRect (int rw, int rh, int* rx, int* ry) {
10632     int besth = height, bestw = width, besti = -1;
10633     int bestx = -1, besty = -1;
10634 
10635     // Bottom left fit heuristic.
10636     for (int i = 0; i < nnodes; ++i) {
10637       int y = rectFits(i, rw, rh);
10638       if (y != -1) {
10639         if (y+rh < besth || (y+rh == besth && nodes[i].width < bestw)) {
10640           besti = i;
10641           bestw = nodes[i].width;
10642           besth = y+rh;
10643           bestx = nodes[i].x;
10644           besty = y;
10645         }
10646       }
10647     }
10648 
10649     if (besti == -1) return false;
10650 
10651     // perform the actual packing
10652     addSkylineLevel(besti, bestx, besty, rw, rh);
10653 
10654     *rx = bestx;
10655     *ry = besty;
10656 
10657     return true;
10658   }
10659 }
10660 
10661 void kill (ref FONSAtlas atlas) nothrow @trusted @nogc {
10662   if (atlas !is null) {
10663     import core.stdc.stdlib : free;
10664     atlas.clear();
10665     free(atlas);
10666     atlas = null;
10667   }
10668 }
10669 
10670 
10671 // ////////////////////////////////////////////////////////////////////////// //
10672 /// FontStash context (internal definition). Don't use it derectly, it was made public only to generate documentation.
10673 /// Group: font_stash
10674 public struct FONScontextInternal {
10675 private:
10676   FONSParams params;
10677   float itw, ith;
10678   ubyte* texData;
10679   int[4] dirtyRect;
10680   FONSfont** fonts; // actually, a simple hash table; can't grow yet
10681   int cfonts; // allocated
10682   int nfonts; // used (so we can track hash table stats)
10683   int* hashidx; // [hsize] items; holds indicies in [fonts] array
10684   int hused, hsize;// used items and total items in [hashidx]
10685   FONSAtlas atlas;
10686   ubyte* scratch;
10687   int nscratch;
10688   FONSstate[FONS_MAX_STATES] states;
10689   int nstates;
10690 
10691   void delegate (FONSError error, int val) nothrow @trusted @nogc handleError;
10692 
10693   @disable this (this);
10694   void opAssign() (const scope auto ref FONScontextInternal ctx) { static assert(0, "FONS copying is not allowed"); }
10695 
10696 private:
10697   static bool strequci (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
10698     if (s0.length != s1.length) return false;
10699     const(char)* sp0 = s0.ptr;
10700     const(char)* sp1 = s1.ptr;
10701     foreach (immutable _; 0..s0.length) {
10702       char c0 = *sp0++;
10703       char c1 = *sp1++;
10704       if (c0 != c1) {
10705         if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man tolower
10706         if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man tolower
10707         if (c0 != c1) return false;
10708       }
10709     }
10710     return true;
10711   }
10712 
10713   inout(FONSstate)* getState () inout pure nothrow @trusted @nogc return {
10714     pragma(inline, true);
10715     return cast(inout)(&states[(nstates > 0 ? nstates-1 : 0)]);
10716   }
10717 
10718   // simple linear probing; returns [FONS_INVALID] if not found
10719   int findNameInHash (const(char)[] name) const pure nothrow @trusted @nogc {
10720     if (nfonts == 0) return FONS_INVALID;
10721     auto nhash = FONSfont.djbhash(name);
10722     //{ import core.stdc.stdio; printf("findinhash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10723     auto res = nhash%hsize;
10724     // hash will never be 100% full, so this loop is safe
10725     for (;;) {
10726       int idx = hashidx[res];
10727       if (idx == -1) break;
10728       auto font = fonts[idx];
10729       if (font is null) assert(0, "FONS internal error");
10730       if (font.namehash == nhash && font.nameEqu(name)) return idx;
10731       //{ import core.stdc.stdio; printf("findinhash chained: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10732       res = (res+1)%hsize;
10733     }
10734     return FONS_INVALID;
10735   }
10736 
10737   // should be called $(B before) freeing `fonts[fidx]`
10738   void removeIndexFromHash (int fidx) nothrow @trusted @nogc {
10739     if (fidx < 0 || fidx >= nfonts) assert(0, "FONS internal error");
10740     if (fonts[fidx] is null) assert(0, "FONS internal error");
10741     if (hused != nfonts) assert(0, "FONS internal error");
10742     auto nhash = fonts[fidx].namehash;
10743     auto res = nhash%hsize;
10744     // hash will never be 100% full, so this loop is safe
10745     for (;;) {
10746       int idx = hashidx[res];
10747       if (idx == -1) assert(0, "FONS INTERNAL ERROR");
10748       if (idx == fidx) {
10749         // i found her! copy rest here
10750         int nidx = (res+1)%hsize;
10751         for (;;) {
10752           if ((hashidx[res] = hashidx[nidx]) == -1) break; // so it will copy `-1` too
10753           res = nidx;
10754           nidx = (nidx+1)%hsize;
10755         }
10756         return;
10757       }
10758       res = (res+1)%hsize;
10759     }
10760   }
10761 
10762   // add font with the given index to hash
10763   // prerequisite: font should not exists in hash
10764   void addIndexToHash (int idx) nothrow @trusted @nogc {
10765     if (idx < 0 || idx >= nfonts) assert(0, "FONS internal error");
10766     if (fonts[idx] is null) assert(0, "FONS internal error");
10767     import core.stdc.stdlib : realloc;
10768     auto nhash = fonts[idx].namehash;
10769     //{ import core.stdc.stdio; printf("addtohash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10770     // allocate new hash table if there was none
10771     if (hsize == 0) {
10772       enum InitSize = 256;
10773       auto newlist = cast(int*)realloc(null, InitSize*hashidx[0].sizeof);
10774       if (newlist is null) assert(0, "FONS: out of memory");
10775       newlist[0..InitSize] = -1;
10776       hsize = InitSize;
10777       hused = 0;
10778       hashidx = newlist;
10779     }
10780     int res = cast(int)(nhash%hsize);
10781     // need to rehash? we want our hash table 50% full at max
10782     if (hashidx[res] != -1 && hused >= hsize/2) {
10783       uint nsz = hsize*2;
10784       if (nsz > 1024*1024) assert(0, "FONS: out of memory for fonts");
10785       auto newlist = cast(int*)realloc(fonts, nsz*hashidx[0].sizeof);
10786       if (newlist is null) assert(0, "FONS: out of memory");
10787       newlist[0..nsz] = -1;
10788       hused = 0;
10789       // rehash
10790       foreach (immutable fidx, FONSfont* ff; fonts[0..nfonts]) {
10791         if (ff is null) continue;
10792         // find slot for this font (guaranteed to have one)
10793         uint newslot = ff.namehash%nsz;
10794         while (newlist[newslot] != -1) newslot = (newslot+1)%nsz;
10795         newlist[newslot] = cast(int)fidx;
10796         ++hused;
10797       }
10798       hsize = nsz;
10799       hashidx = newlist;
10800       // we added everything, including [idx], so nothing more to do here
10801     } else {
10802       // find slot (guaranteed to have one)
10803       while (hashidx[res] != -1) res = (res+1)%hsize;
10804       // i found her!
10805       hashidx[res] = idx;
10806       ++hused;
10807     }
10808   }
10809 
10810   void addWhiteRect (int w, int h) nothrow @trusted @nogc {
10811     int gx, gy;
10812     ubyte* dst;
10813 
10814     if (!atlas.addRect(w, h, &gx, &gy)) return;
10815 
10816     // Rasterize
10817     dst = &texData[gx+gy*params.width];
10818     foreach (int y; 0..h) {
10819       foreach (int x; 0..w) {
10820         dst[x] = 0xff;
10821       }
10822       dst += params.width;
10823     }
10824 
10825     dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], gx);
10826     dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], gy);
10827     dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], gx+w);
10828     dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], gy+h);
10829   }
10830 
10831   // returns fid, not hash slot
10832   int allocFontAt (int atidx) nothrow @trusted @nogc {
10833     if (atidx >= 0 && atidx >= nfonts) assert(0, "internal NanoVega fontstash error");
10834 
10835     if (atidx < 0) {
10836       if (nfonts >= cfonts) {
10837         import core.stdc.stdlib : realloc;
10838         import core.stdc.string : memset;
10839         assert(nfonts == cfonts);
10840         int newsz = cfonts+64;
10841         if (newsz > 65535) assert(0, "FONS: too many fonts");
10842         auto newlist = cast(FONSfont**)realloc(fonts, newsz*(FONSfont*).sizeof);
10843         if (newlist is null) assert(0, "FONS: out of memory");
10844         memset(newlist+cfonts, 0, (newsz-cfonts)*(FONSfont*).sizeof);
10845         fonts = newlist;
10846         cfonts = newsz;
10847       }
10848       assert(nfonts < cfonts);
10849     }
10850 
10851     FONSfont* font = cast(FONSfont*)malloc(FONSfont.sizeof);
10852     if (font is null) assert(0, "FONS: out of memory");
10853     memset(font, 0, FONSfont.sizeof);
10854 
10855     font.glyphs = cast(FONSglyph*)malloc(FONSglyph.sizeof*FONS_INIT_GLYPHS);
10856     if (font.glyphs is null) assert(0, "FONS: out of memory");
10857     font.cglyphs = FONS_INIT_GLYPHS;
10858     font.nglyphs = 0;
10859 
10860     if (atidx < 0) {
10861       fonts[nfonts] = font;
10862       return nfonts++;
10863     } else {
10864       fonts[atidx] = font;
10865       return atidx;
10866     }
10867   }
10868 
10869   // 0: ooops
10870   int findGlyphForCP (FONSfont *font, dchar dch, FONSfont** renderfont) nothrow @trusted @nogc {
10871     if (renderfont !is null) *renderfont = font;
10872     if (font is null || font.fdata is null) return 0;
10873     auto g = fons__tt_getGlyphIndex(&font.font, cast(uint)dch);
10874     // try to find the glyph in fallback fonts
10875     if (g == 0 && renderfont !is null) {
10876       foreach (immutable i; 0..font.nfallbacks) {
10877         FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]];
10878         if (fallbackFont !is null) {
10879           int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, cast(uint)dch);
10880           if (fallbackIndex != 0) {
10881             *renderfont = fallbackFont;
10882             return fallbackIndex;
10883           }
10884         }
10885       }
10886       // no char, try to find replacement one
10887       if (dch != 0xFFFD) {
10888         g = fons__tt_getGlyphIndex(&font.font, 0xFFFD);
10889         if (g == 0) {
10890           foreach (immutable i; 0..font.nfallbacks) {
10891             FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]];
10892             if (fallbackFont !is null) {
10893               int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, 0xFFFD);
10894               if (fallbackIndex != 0) {
10895                 *renderfont = fallbackFont;
10896                 return fallbackIndex;
10897               }
10898             }
10899           }
10900         }
10901       }
10902     }
10903     return g;
10904   }
10905 
10906   void clear () nothrow @trusted @nogc {
10907     import core.stdc.stdlib : free;
10908 
10909     if (params.renderDelete !is null) params.renderDelete(params.userPtr);
10910     foreach (immutable int i; 0..nfonts) fonts[i].kill();
10911 
10912     if (atlas !is null) atlas.kill();
10913     if (fonts !is null) free(fonts);
10914     if (texData !is null) free(texData);
10915     if (scratch !is null) free(scratch);
10916     if (hashidx !is null) free(hashidx);
10917   }
10918 
10919   // add font from another fontstash
10920   int addCookedFont (FONSfont* font) nothrow @trusted @nogc {
10921     if (font is null || font.fdata is null) return FONS_INVALID;
10922     font.fdata.incref();
10923     auto res = addFontWithData(font.name[0..font.namelen], font.fdata, !font.font.mono);
10924     if (res == FONS_INVALID) font.fdata.decref(); // oops
10925     return res;
10926   }
10927 
10928   // fdata refcount must be already increased; it won't be changed
10929   int addFontWithData (const(char)[] name, FONSfontData* fdata, bool defAA) nothrow @trusted @nogc {
10930     int i, ascent, descent, fh, lineGap;
10931 
10932     if (name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID;
10933     if (name.length > 32767) return FONS_INVALID;
10934     if (fdata is null) return FONS_INVALID;
10935 
10936     // find a font with the given name
10937     int newidx;
10938     FONSfont* oldfont = null;
10939     int oldidx = findNameInHash(name);
10940     if (oldidx != FONS_INVALID) {
10941       // replacement font
10942       oldfont = fonts[oldidx];
10943       newidx = oldidx;
10944     } else {
10945       // new font, allocate new bucket
10946       newidx = -1;
10947     }
10948 
10949     newidx = allocFontAt(newidx);
10950     FONSfont* font = fonts[newidx];
10951     font.setName(name);
10952     font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; // init hash lookup
10953     font.fdata = fdata; // set the font data (don't change reference count)
10954     fons__tt_setMono(&this, &font.font, !defAA);
10955 
10956     // init font
10957     nscratch = 0;
10958     if (!fons__tt_loadFont(&this, &font.font, fdata.data, fdata.dataSize)) {
10959       // we promised to not free data on error, so just clear the data store (it will be freed by the caller)
10960       font.fdata = null;
10961       font.kill();
10962       if (oldidx != FONS_INVALID) {
10963         assert(oldidx == newidx);
10964         fonts[oldidx] = oldfont;
10965       } else {
10966         assert(newidx == nfonts-1);
10967         fonts[newidx] = null;
10968         --nfonts;
10969       }
10970       return FONS_INVALID;
10971     } else {
10972       // free old font data, if any
10973       if (oldfont !is null) oldfont.kill();
10974     }
10975 
10976     // add font to name hash
10977     if (oldidx == FONS_INVALID) addIndexToHash(newidx);
10978 
10979     // store normalized line height
10980     // the real line height is got by multiplying the lineh by font size
10981     fons__tt_getFontVMetrics(&font.font, &ascent, &descent, &lineGap);
10982     fh = ascent-descent;
10983     font.ascender = cast(float)ascent/cast(float)fh;
10984     font.descender = cast(float)descent/cast(float)fh;
10985     font.lineh = cast(float)(fh+lineGap)/cast(float)fh;
10986 
10987     //{ import core.stdc.stdio; printf("created font [%.*s] (idx=%d)...\n", cast(uint)name.length, name.ptr, idx); }
10988     return newidx;
10989   }
10990 
10991   // isize: size*10
10992   float getVertAlign (FONSfont* font, NVGTextAlign talign, short isize) pure nothrow @trusted @nogc {
10993     if (params.isZeroTopLeft) {
10994       final switch (talign.vertical) {
10995         case NVGTextAlign.V.Top: return font.ascender*cast(float)isize/10.0f;
10996         case NVGTextAlign.V.Middle: return (font.ascender+font.descender)/2.0f*cast(float)isize/10.0f;
10997         case NVGTextAlign.V.Baseline: return 0.0f;
10998         case NVGTextAlign.V.Bottom: return font.descender*cast(float)isize/10.0f;
10999       }
11000     } else {
11001       final switch (talign.vertical) {
11002         case NVGTextAlign.V.Top: return -font.ascender*cast(float)isize/10.0f;
11003         case NVGTextAlign.V.Middle: return -(font.ascender+font.descender)/2.0f*cast(float)isize/10.0f;
11004         case NVGTextAlign.V.Baseline: return 0.0f;
11005         case NVGTextAlign.V.Bottom: return -font.descender*cast(float)isize/10.0f;
11006       }
11007     }
11008     assert(0);
11009   }
11010 
11011 public:
11012   /** Create new FontStash context. It can be destroyed with `fs.kill()` later.
11013    *
11014    * Note that if you don't plan to rasterize glyphs (i.e. you will use created
11015    * FontStash only to measure text), you can simply pass `FONSParams.init`).
11016    */
11017   static FONSContext create() (const scope auto ref FONSParams params) nothrow @trusted @nogc {
11018     import core.stdc.string : memcpy;
11019 
11020     FONSContext stash = null;
11021 
11022     // allocate memory for the font stash
11023     stash = cast(FONSContext)malloc(FONScontextInternal.sizeof);
11024     if (stash is null) goto error;
11025     memset(stash, 0, FONScontextInternal.sizeof);
11026 
11027     memcpy(&stash.params, &params, params.sizeof);
11028     if (stash.params.width < 1) stash.params.width = 32;
11029     if (stash.params.height < 1) stash.params.height = 32;
11030 
11031     // allocate scratch buffer
11032     stash.scratch = cast(ubyte*)malloc(FONS_SCRATCH_BUF_SIZE);
11033     if (stash.scratch is null) goto error;
11034 
11035     // initialize implementation library
11036     if (!fons__tt_init(stash)) goto error;
11037 
11038     if (stash.params.renderCreate !is null) {
11039       if (!stash.params.renderCreate(stash.params.userPtr, stash.params.width, stash.params.height)) goto error;
11040     }
11041 
11042     stash.atlas = FONSAtlas.create(stash.params.width, stash.params.height, FONS_INIT_ATLAS_NODES);
11043     if (stash.atlas is null) goto error;
11044 
11045     // don't allocate space for fonts: hash manager will do that for us later
11046     //stash.cfonts = 0;
11047     //stash.nfonts = 0;
11048 
11049     // create texture for the cache
11050     stash.itw = 1.0f/stash.params.width;
11051     stash.ith = 1.0f/stash.params.height;
11052     stash.texData = cast(ubyte*)malloc(stash.params.width*stash.params.height);
11053     if (stash.texData is null) goto error;
11054     memset(stash.texData, 0, stash.params.width*stash.params.height);
11055 
11056     stash.dirtyRect.ptr[0] = stash.params.width;
11057     stash.dirtyRect.ptr[1] = stash.params.height;
11058     stash.dirtyRect.ptr[2] = 0;
11059     stash.dirtyRect.ptr[3] = 0;
11060 
11061     // add white rect at 0, 0 for debug drawing
11062     stash.addWhiteRect(2, 2);
11063 
11064     stash.pushState();
11065     stash.clearState();
11066 
11067     return stash;
11068 
11069   error:
11070     stash.kill();
11071     return null;
11072   }
11073 
11074 public:
11075   /// Add fallback font (FontStash will try to find missing glyph in all fallback fonts before giving up).
11076   bool addFallbackFont (int base, int fallback) nothrow @trusted @nogc {
11077     FONSfont* baseFont = fonts[base];
11078     if (baseFont !is null && baseFont.nfallbacks < FONS_MAX_FALLBACKS) {
11079       baseFont.fallbacks.ptr[baseFont.nfallbacks++] = fallback;
11080       return true;
11081     }
11082     return false;
11083   }
11084 
11085   @property void size (float size) nothrow @trusted @nogc { pragma(inline, true); getState.size = size; } /// Set current font size.
11086   @property float size () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.size; } /// Get current font size.
11087 
11088   @property void spacing (float spacing) nothrow @trusted @nogc { pragma(inline, true); getState.spacing = spacing; } /// Set current letter spacing.
11089   @property float spacing () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.spacing; } /// Get current letter spacing.
11090 
11091   @property void blur (float blur) nothrow @trusted @nogc { pragma(inline, true); getState.blur = blur; } /// Set current letter blur.
11092   @property float blur () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.blur; } /// Get current letter blur.
11093 
11094   @property void textAlign (NVGTextAlign talign) nothrow @trusted @nogc { pragma(inline, true); getState.talign = talign; } /// Set current text align.
11095   @property NVGTextAlign textAlign () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.talign; } /// Get current text align.
11096 
11097   @property void fontId (int font) nothrow @trusted @nogc { pragma(inline, true); getState.font = font; } /// Set current font id.
11098   @property int fontId () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.font; } /// Get current font id.
11099 
11100   @property void fontId (const(char)[] name) nothrow @trusted @nogc { pragma(inline, true); getState.font = getFontByName(name); } /// Set current font using its name.
11101 
11102   /// Check if FontStash has a font with the given name loaded.
11103   bool hasFont (const(char)[] name) const pure nothrow @trusted @nogc { pragma(inline, true); return (getFontByName(name) >= 0); }
11104 
11105   /// Get AA for the current font, or for the specified font.
11106   bool getFontAA (int font=-1) nothrow @trusted @nogc {
11107     FONSstate* state = getState;
11108     if (font < 0) font = state.font;
11109     if (font < 0 || font >= nfonts) return false;
11110     FONSfont* f = fonts[font];
11111     return (f !is null ? !f.font.mono : false);
11112   }
11113 
11114   /// Push current state. Returns `false` if state stack overflowed.
11115   bool pushState () nothrow @trusted @nogc {
11116     if (nstates >= FONS_MAX_STATES) {
11117       if (handleError !is null) handleError(FONSError.StatesOverflow, 0);
11118       return false;
11119     }
11120     if (nstates > 0) {
11121       import core.stdc.string : memcpy;
11122       memcpy(&states[nstates], &states[nstates-1], FONSstate.sizeof);
11123     }
11124     ++nstates;
11125     return true;
11126   }
11127 
11128   /// Pop current state. Returns `false` if state stack underflowed.
11129   bool popState () nothrow @trusted @nogc {
11130     if (nstates <= 1) {
11131       if (handleError !is null) handleError(FONSError.StatesUnderflow, 0);
11132       return false;
11133     }
11134     --nstates;
11135     return true;
11136   }
11137 
11138   /// Clear current state (i.e. set it to some sane defaults).
11139   void clearState () nothrow @trusted @nogc {
11140     FONSstate* state = getState;
11141     state.size = 12.0f;
11142     state.font = 0;
11143     state.blur = 0;
11144     state.spacing = 0;
11145     state.talign.reset;
11146   }
11147 
11148   private enum NoAlias = ":noaa";
11149 
11150   /** Add font to FontStash.
11151    *
11152    * Load scalable font from disk, and add it to FontStash. If you will try to load a font
11153    * with same name and path several times, FontStash will load it only once. Also, you can
11154    * load new disk font for any existing logical font.
11155    *
11156    * Params:
11157    *   name = logical font name, that will be used to select this font later.
11158    *   path = path to disk file with your font.
11159    *   defAA = should FontStash use antialiased font rasterizer?
11160    *
11161    * Returns:
11162    *   font id or [FONS_INVALID].
11163    */
11164   int addFont (const(char)[] name, const(char)[] path, bool defAA=false) nothrow @trusted {
11165     if (path.length == 0 || name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID;
11166     if (path.length > 32768) return FONS_INVALID; // arbitrary limit
11167 
11168     // if font path ends with ":noaa", turn off antialiasing
11169     if (path.length >= NoAlias.length && strequci(path[$-NoAlias.length..$], NoAlias)) {
11170       path = path[0..$-NoAlias.length];
11171       if (path.length == 0) return FONS_INVALID;
11172       defAA = false;
11173     }
11174 
11175     // if font name ends with ":noaa", turn off antialiasing
11176     if (name.length > NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) {
11177       name = name[0..$-NoAlias.length];
11178       defAA = false;
11179     }
11180 
11181     // find a font with the given name
11182     int fidx = findNameInHash(name);
11183     //{ import core.stdc.stdio; printf("loading font '%.*s' [%s] (fidx=%d)...\n", cast(uint)path.length, path.ptr, fontnamebuf.ptr, fidx); }
11184 
11185     int loadFontFile (const(char)[] path) {
11186       // check if existing font (if any) has the same path
11187       if (fidx >= 0) {
11188         import core.stdc.string : strlen;
11189         auto plen = (fonts[fidx].path !is null ? strlen(fonts[fidx].path) : 0);
11190         version(Posix) {
11191           //{ 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); }
11192           if (plen == path.length && fonts[fidx].path[0..plen] == path) {
11193             //{ import core.stdc.stdio; printf("*** font [%.*s] already loaded from [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)plen, path.ptr); }
11194             // i found her!
11195             return fidx;
11196           }
11197         } else {
11198           if (plen == path.length && strequci(fonts[fidx].path[0..plen], path)) {
11199             // i found her!
11200             return fidx;
11201           }
11202         }
11203       }
11204       version(Windows) {
11205         // special shitdows check: this will reject fontconfig font names (but still allow things like "c:myfont")
11206         foreach (immutable char ch; path[(path.length >= 2 && path[1] == ':' ? 2 : 0)..$]) if (ch == ':') return FONS_INVALID;
11207       }
11208       // either no such font, or different path
11209       //{ import core.stdc.stdio; printf("trying font [%.*s] from file [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)path.length, path.ptr); }
11210       int xres = FONS_INVALID;
11211       try {
11212         import core.stdc.stdlib : free, malloc;
11213         static if (NanoVegaHasIVVFS) {
11214           auto fl = VFile(path);
11215           auto dataSize = fl.size;
11216           if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID;
11217           ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize);
11218           if (data is null) assert(0, "out of memory in NanoVega fontstash");
11219           scope(failure) free(data); // oops
11220           fl.rawReadExact(data[0..cast(uint)dataSize]);
11221           fl.close();
11222         } else {
11223           import core.stdc.stdio : FILE, fopen, fclose, fread, ftell, fseek;
11224           import std.internal.cstring : tempCString;
11225           auto fl = fopen(path.tempCString, "rb");
11226           if (fl is null) return FONS_INVALID;
11227           scope(exit) fclose(fl);
11228           if (fseek(fl, 0, 2/*SEEK_END*/) != 0) return FONS_INVALID;
11229           auto dataSize = ftell(fl);
11230           if (fseek(fl, 0, 0/*SEEK_SET*/) != 0) return FONS_INVALID;
11231           if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID;
11232           ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize);
11233           if (data is null) assert(0, "out of memory in NanoVega fontstash");
11234           scope(failure) free(data); // oops
11235           ubyte* dptr = data;
11236           auto left = cast(uint)dataSize;
11237           while (left > 0) {
11238             auto rd = fread(dptr, 1, left, fl);
11239             if (rd == 0) { free(data); return FONS_INVALID; } // unexpected EOF or reading error, it doesn't matter
11240             dptr += rd;
11241             left -= rd;
11242           }
11243         }
11244         scope(failure) free(data); // oops
11245         // create font data
11246         FONSfontData* fdata = fons__createFontData(data, cast(int)dataSize, true); // free data
11247         fdata.incref();
11248         xres = addFontWithData(name, fdata, defAA);
11249         if (xres == FONS_INVALID) {
11250           fdata.decref(); // this will free [data] and [fdata]
11251         } else {
11252           // remember path
11253           fonts[xres].setPath(path);
11254         }
11255       } catch (Exception e) {
11256         // oops; sorry
11257       }
11258       return xres;
11259     }
11260 
11261     // first try direct path
11262     auto res = loadFontFile(path);
11263     // if loading failed, try fontconfig (if fontconfig is available)
11264     static if (NanoVegaHasFontConfig) {
11265       if (res == FONS_INVALID && fontconfigAvailable) {
11266         // idiotic fontconfig NEVER fails; let's skip it if `path` looks like a path
11267         bool ok = true;
11268         if (path.length > 4 && (path[$-4..$] == ".ttf" || path[$-4..$] == ".ttc")) ok = false;
11269         if (ok) { foreach (immutable char ch; path) if (ch == '/') { ok = false; break; } }
11270         if (ok) {
11271           import std.internal.cstring : tempCString;
11272           FcPattern* pat = FcNameParse(path.tempCString);
11273           if (pat !is null) {
11274             scope(exit) FcPatternDestroy(pat);
11275             if (FcConfigSubstitute(null, pat, FcMatchPattern)) {
11276               FcDefaultSubstitute(pat);
11277               // find the font
11278               FcResult result;
11279               FcPattern* font = FcFontMatch(null, pat, &result);
11280               if (font !is null) {
11281                 scope(exit) FcPatternDestroy(font);
11282                 char* file = null;
11283                 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
11284                   if (file !is null && file[0]) {
11285                     import core.stdc.string : strlen;
11286                     res = loadFontFile(file[0..strlen(file)]);
11287                   }
11288                 }
11289               }
11290             }
11291           }
11292         }
11293       }
11294     }
11295     return res;
11296   }
11297 
11298   /** Add font to FontStash, using data from memory.
11299    *
11300    * And already loaded font to FontStash. You can replace existing logical fonts.
11301    * But note that you can't remove logical font by passing "empty" data.
11302    *
11303    * $(WARNING If [FONS_INVALID] returned, `data` won't be freed even if `freeData` is `true`.)
11304    *
11305    * Params:
11306    *   name = logical font name, that will be used to select this font later.
11307    *   data = font data.
11308    *   dataSize = font data size.
11309    *   freeData = should FontStash take ownership of the font data?
11310    *   defAA = should FontStash use antialiased font rasterizer?
11311    *
11312    * Returns:
11313    *   font id or [FONS_INVALID].
11314    */
11315   int addFontMem (const(char)[] name, ubyte* data, int dataSize, bool freeData, bool defAA=false) nothrow @trusted @nogc {
11316     if (data is null || dataSize < 16) return FONS_INVALID;
11317     FONSfontData* fdata = fons__createFontData(data, dataSize, freeData);
11318     fdata.incref();
11319     auto res = addFontWithData(name, fdata, defAA);
11320     if (res == FONS_INVALID) {
11321       // we promised to not free data on error
11322       fdata.freeData = false;
11323       fdata.decref(); // this will free [fdata]
11324     }
11325     return res;
11326   }
11327 
11328   /** Add fonts from another FontStash.
11329    *
11330    * This is more effective (and faster) than reloading fonts, because internally font data
11331    * is reference counted.
11332    */
11333   void addFontsFrom (FONSContext source) nothrow @trusted @nogc {
11334     if (source is null) return;
11335     foreach (FONSfont* font; source.fonts[0..source.nfonts]) {
11336       if (font !is null) {
11337         auto newidx = addCookedFont(font);
11338         FONSfont* newfont = fonts[newidx];
11339         assert(newfont !is null);
11340         assert(newfont.path is null);
11341         // copy path
11342         if (font.path !is null && font.path[0]) {
11343           import core.stdc.stdlib : malloc;
11344           import core.stdc.string : strcpy, strlen;
11345           newfont.path = cast(char*)malloc(strlen(font.path)+1);
11346           if (newfont.path is null) assert(0, "FONS: out of memory");
11347           strcpy(newfont.path, font.path);
11348         }
11349       }
11350     }
11351   }
11352 
11353   /// Returns logical font name corresponding to the given font id, or `null`.
11354   /// $(WARNING Copy returned name, as name buffer can be invalidated by next FontStash API call!)
11355   const(char)[] getNameById (int idx) const pure nothrow @trusted @nogc {
11356     if (idx < 0 || idx >= nfonts || fonts[idx] is null) return null;
11357     return fonts[idx].name[0..fonts[idx].namelen];
11358   }
11359 
11360   /// Returns font id corresponding to the given logical font name, or [FONS_INVALID].
11361   int getFontByName (const(char)[] name) const pure nothrow @trusted @nogc {
11362     //{ import core.stdc.stdio; printf("fonsGetFontByName: [%.*s]\n", cast(uint)name.length, name.ptr); }
11363     // remove ":noaa" suffix
11364     if (name.length >= NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) {
11365       name = name[0..$-NoAlias.length];
11366     }
11367     if (name.length == 0) return FONS_INVALID;
11368     return findNameInHash(name);
11369   }
11370 
11371   /** Measures the specified text string. Parameter bounds should be a float[4],
11372    * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax]
11373    * Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
11374    */
11375   float getTextBounds(T) (float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc if (isAnyCharType!T) {
11376     FONSstate* state = getState;
11377     uint codepoint;
11378     uint utf8state = 0;
11379     FONSQuad q;
11380     FONSglyph* glyph = null;
11381     int prevGlyphIndex = -1;
11382     short isize = cast(short)(state.size*10.0f);
11383     short iblur = cast(short)state.blur;
11384     FONSfont* font;
11385 
11386     if (state.font < 0 || state.font >= nfonts) return 0;
11387     font = fonts[state.font];
11388     if (font is null || font.fdata is null) return 0;
11389 
11390     float scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
11391 
11392     // Align vertically.
11393     y += getVertAlign(font, state.talign, isize);
11394 
11395     float minx = x, maxx = x;
11396     float miny = y, maxy = y;
11397     float startx = x;
11398 
11399     foreach (T ch; str) {
11400       static if (T.sizeof == 1) {
11401         //if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;
11402         mixin(DecUtfMixin!("utf8state", "codepoint", "(cast(ubyte)ch)"));
11403         if (utf8state) continue;
11404       } else {
11405         static if (T.sizeof == 4) {
11406           if (ch > dchar.max) ch = 0xFFFD;
11407         }
11408         codepoint = cast(uint)ch;
11409       }
11410       glyph = getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional);
11411       if (glyph !is null) {
11412         getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q);
11413         if (q.x0 < minx) minx = q.x0;
11414         if (q.x1 > maxx) maxx = q.x1;
11415         if (params.isZeroTopLeft) {
11416           if (q.y0 < miny) miny = q.y0;
11417           if (q.y1 > maxy) maxy = q.y1;
11418         } else {
11419           if (q.y1 < miny) miny = q.y1;
11420           if (q.y0 > maxy) maxy = q.y0;
11421         }
11422         prevGlyphIndex = glyph.index;
11423       } else {
11424         //{ import core.stdc.stdio; printf("NO GLYPH FOR 0x%04x\n", cast(uint)codepoint); }
11425         prevGlyphIndex = -1;
11426       }
11427     }
11428 
11429     float advance = x-startx;
11430     //{ import core.stdc.stdio; printf("***: x=%g; startx=%g; advance=%g\n", cast(double)x, cast(double)startx, cast(double)advance); }
11431 
11432     // Align horizontally
11433     if (state.talign.left) {
11434       // empty
11435     } else if (state.talign.right) {
11436       minx -= advance;
11437       maxx -= advance;
11438     } else if (state.talign.center) {
11439       minx -= advance*0.5f;
11440       maxx -= advance*0.5f;
11441     }
11442 
11443     if (bounds.length) {
11444       if (bounds.length > 0) bounds.ptr[0] = minx;
11445       if (bounds.length > 1) bounds.ptr[1] = miny;
11446       if (bounds.length > 2) bounds.ptr[2] = maxx;
11447       if (bounds.length > 3) bounds.ptr[3] = maxy;
11448     }
11449 
11450     return advance;
11451   }
11452 
11453   /// Returns various font metrics. Any argument can be `null` if you aren't interested in its value.
11454   void getVertMetrics (float* ascender, float* descender, float* lineh) nothrow @trusted @nogc {
11455     FONSstate* state = getState;
11456     if (state.font < 0 || state.font >= nfonts) {
11457       if (ascender !is null) *ascender = 0;
11458       if (descender !is null) *descender = 0;
11459       if (lineh !is null) *lineh = 0;
11460     } else {
11461       FONSfont* font = fonts[state.font];
11462       if (font is null || font.fdata is null) {
11463         if (ascender !is null) *ascender = 0;
11464         if (descender !is null) *descender = 0;
11465         if (lineh !is null) *lineh = 0;
11466       } else {
11467         short isize = cast(short)(state.size*10.0f);
11468         if (ascender !is null) *ascender = font.ascender*isize/10.0f;
11469         if (descender !is null) *descender = font.descender*isize/10.0f;
11470         if (lineh !is null) *lineh = font.lineh*isize/10.0f;
11471       }
11472     }
11473   }
11474 
11475   /// Returns line bounds. Any argument can be `null` if you aren't interested in its value.
11476   void getLineBounds (float y, float* minyp, float* maxyp) nothrow @trusted @nogc {
11477     FONSfont* font;
11478     FONSstate* state = getState;
11479     short isize;
11480 
11481     if (minyp !is null) *minyp = 0;
11482     if (maxyp !is null) *maxyp = 0;
11483 
11484     if (state.font < 0 || state.font >= nfonts) return;
11485     font = fonts[state.font];
11486     isize = cast(short)(state.size*10.0f);
11487     if (font is null || font.fdata is null) return;
11488 
11489     y += getVertAlign(font, state.talign, isize);
11490 
11491     if (params.isZeroTopLeft) {
11492       immutable float miny = y-font.ascender*cast(float)isize/10.0f;
11493       immutable float maxy = miny+font.lineh*isize/10.0f;
11494       if (minyp !is null) *minyp = miny;
11495       if (maxyp !is null) *maxyp = maxy;
11496     } else {
11497       immutable float maxy = y+font.descender*cast(float)isize/10.0f;
11498       immutable float miny = maxy-font.lineh*isize/10.0f;
11499       if (minyp !is null) *minyp = miny;
11500       if (maxyp !is null) *maxyp = maxy;
11501     }
11502   }
11503 
11504   /// Returns font line height.
11505   float fontHeight () nothrow @trusted @nogc {
11506     float res = void;
11507     getVertMetrics(null, null, &res);
11508     return res;
11509   }
11510 
11511   /// Returns font ascender (positive).
11512   float fontAscender () nothrow @trusted @nogc {
11513     float res = void;
11514     getVertMetrics(&res, null, null);
11515     return res;
11516   }
11517 
11518   /// Returns font descender (negative).
11519   float fontDescender () nothrow @trusted @nogc {
11520     float res = void;
11521     getVertMetrics(null, &res, null);
11522     return res;
11523   }
11524 
11525   //TODO: document this
11526   const(ubyte)* getTextureData (int* width, int* height) nothrow @trusted @nogc {
11527     if (width !is null) *width = params.width;
11528     if (height !is null) *height = params.height;
11529     return texData;
11530   }
11531 
11532   //TODO: document this
11533   bool validateTexture (int* dirty) nothrow @trusted @nogc {
11534     if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) {
11535       dirty[0] = dirtyRect.ptr[0];
11536       dirty[1] = dirtyRect.ptr[1];
11537       dirty[2] = dirtyRect.ptr[2];
11538       dirty[3] = dirtyRect.ptr[3];
11539       // reset dirty rect
11540       dirtyRect.ptr[0] = params.width;
11541       dirtyRect.ptr[1] = params.height;
11542       dirtyRect.ptr[2] = 0;
11543       dirtyRect.ptr[3] = 0;
11544       return true;
11545     }
11546     return false;
11547   }
11548 
11549   //TODO: document this
11550   void errorCallback (void delegate (FONSError error, int val) nothrow @trusted @nogc callback) nothrow @trusted @nogc {
11551     handleError = callback;
11552   }
11553 
11554   //TODO: document this
11555   void getAtlasSize (int* width, int* height) const pure nothrow @trusted @nogc {
11556     if (width !is null) *width = params.width;
11557     if (height !is null) *height = params.height;
11558   }
11559 
11560   //TODO: document this
11561   bool expandAtlas (int width, int height) nothrow @trusted @nogc {
11562     import core.stdc.stdlib : free;
11563     import core.stdc.string : memcpy, memset;
11564 
11565     int maxy = 0;
11566     ubyte* data = null;
11567 
11568     width = nvg__max(width, params.width);
11569     height = nvg__max(height, params.height);
11570 
11571     if (width == params.width && height == params.height) return true;
11572 
11573     // Flush pending glyphs.
11574     flush();
11575 
11576     // Create new texture
11577     if (params.renderResize !is null) {
11578       if (params.renderResize(params.userPtr, width, height) == 0) return false;
11579     }
11580     // Copy old texture data over.
11581     data = cast(ubyte*)malloc(width*height);
11582     if (data is null) return 0;
11583     foreach (immutable int i; 0..params.height) {
11584       ubyte* dst = &data[i*width];
11585       ubyte* src = &texData[i*params.width];
11586       memcpy(dst, src, params.width);
11587       if (width > params.width) memset(dst+params.width, 0, width-params.width);
11588     }
11589     if (height > params.height) memset(&data[params.height*width], 0, (height-params.height)*width);
11590 
11591     free(texData);
11592     texData = data;
11593 
11594     // Increase atlas size
11595     atlas.expand(width, height);
11596 
11597     // Add existing data as dirty.
11598     foreach (immutable int i; 0..atlas.nnodes) maxy = nvg__max(maxy, atlas.nodes[i].y);
11599     dirtyRect.ptr[0] = 0;
11600     dirtyRect.ptr[1] = 0;
11601     dirtyRect.ptr[2] = params.width;
11602     dirtyRect.ptr[3] = maxy;
11603 
11604     params.width = width;
11605     params.height = height;
11606     itw = 1.0f/params.width;
11607     ith = 1.0f/params.height;
11608 
11609     return true;
11610   }
11611 
11612   //TODO: document this
11613   bool resetAtlas (int width, int height) nothrow @trusted @nogc {
11614     import core.stdc.stdlib : realloc;
11615     import core.stdc.string : memcpy, memset;
11616 
11617     // flush pending glyphs
11618     flush();
11619 
11620     // create new texture
11621     if (params.renderResize !is null) {
11622       if (params.renderResize(params.userPtr, width, height) == 0) return false;
11623     }
11624 
11625     // reset atlas
11626     atlas.reset(width, height);
11627 
11628     // clear texture data
11629     texData = cast(ubyte*)realloc(texData, width*height);
11630     if (texData is null) assert(0, "FONS: out of memory");
11631     memset(texData, 0, width*height);
11632 
11633     // reset dirty rect
11634     dirtyRect.ptr[0] = width;
11635     dirtyRect.ptr[1] = height;
11636     dirtyRect.ptr[2] = 0;
11637     dirtyRect.ptr[3] = 0;
11638 
11639     // Reset cached glyphs
11640     foreach (FONSfont* font; fonts[0..nfonts]) {
11641       if (font !is null) {
11642         font.nglyphs = 0;
11643         font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1;
11644       }
11645     }
11646 
11647     params.width = width;
11648     params.height = height;
11649     itw = 1.0f/params.width;
11650     ith = 1.0f/params.height;
11651 
11652     // Add white rect at 0, 0 for debug drawing.
11653     addWhiteRect(2, 2);
11654 
11655     return true;
11656   }
11657 
11658   //TODO: document this
11659   bool getPathBounds (dchar dch, float[] bounds) nothrow @trusted @nogc {
11660     if (bounds.length > 4) bounds = bounds.ptr[0..4];
11661     static if (is(typeof(&fons__nvg__bounds))) {
11662       FONSstate* state = getState;
11663       if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; }
11664       FONSfont* font;
11665       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11666       if (g == 0) { bounds[] = 0; return false; }
11667       assert(font !is null);
11668       return fons__nvg__bounds(&font.font, g, bounds);
11669     } else {
11670       bounds[] = 0;
11671       return false;
11672     }
11673   }
11674 
11675   //TODO: document this
11676   bool toPath() (NVGContext vg, dchar dch, float[] bounds=null) nothrow @trusted @nogc {
11677     if (bounds.length > 4) bounds = bounds.ptr[0..4];
11678     static if (is(typeof(&fons__nvg__toPath))) {
11679       if (vg is null) { bounds[] = 0; return false; }
11680       FONSstate* state = getState;
11681       if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; }
11682       FONSfont* font;
11683       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11684       if (g == 0) { bounds[] = 0; return false; }
11685       assert(font !is null);
11686       return fons__nvg__toPath(vg, &font.font, g, bounds);
11687     } else {
11688       bounds[] = 0;
11689       return false;
11690     }
11691   }
11692 
11693   //TODO: document this
11694   bool toOutline (dchar dch, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
11695     if (ol is null) return false;
11696     static if (is(typeof(&fons__nvg__toOutline))) {
11697       FONSstate* state = getState;
11698       if (state.font < 0 || state.font >= nfonts) return false;
11699       FONSfont* font;
11700       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11701       if (g == 0) return false;
11702       assert(font !is null);
11703       return fons__nvg__toOutline(&font.font, g, ol);
11704     } else {
11705       return false;
11706     }
11707   }
11708 
11709   //TODO: document this
11710   FONSglyph* getGlyph (FONSfont* font, uint codepoint, short isize, short iblur, FONSBitmapFlag bitmapOption) nothrow @trusted @nogc {
11711     static uint fons__hashint() (uint a) pure nothrow @safe @nogc {
11712       pragma(inline, true);
11713       a += ~(a<<15);
11714       a ^=  (a>>10);
11715       a +=  (a<<3);
11716       a ^=  (a>>6);
11717       a += ~(a<<11);
11718       a ^=  (a>>16);
11719       return a;
11720     }
11721 
11722     // based on Exponential blur, Jani Huhtanen, 2006
11723     enum APREC = 16;
11724     enum ZPREC = 7;
11725 
11726     static void fons__blurCols (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc {
11727       foreach (immutable int y; 0..h) {
11728         int z = 0; // force zero border
11729         foreach (int x; 1..w) {
11730           z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC;
11731           dst[x] = cast(ubyte)(z>>ZPREC);
11732         }
11733         dst[w-1] = 0; // force zero border
11734         z = 0;
11735         for (int x = w-2; x >= 0; --x) {
11736           z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC;
11737           dst[x] = cast(ubyte)(z>>ZPREC);
11738         }
11739         dst[0] = 0; // force zero border
11740         dst += dstStride;
11741       }
11742     }
11743 
11744     static void fons__blurRows (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc {
11745       foreach (immutable int x; 0..w) {
11746         int z = 0; // force zero border
11747         for (int y = dstStride; y < h*dstStride; y += dstStride) {
11748           z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC;
11749           dst[y] = cast(ubyte)(z>>ZPREC);
11750         }
11751         dst[(h-1)*dstStride] = 0; // force zero border
11752         z = 0;
11753         for (int y = (h-2)*dstStride; y >= 0; y -= dstStride) {
11754           z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC;
11755           dst[y] = cast(ubyte)(z>>ZPREC);
11756         }
11757         dst[0] = 0; // force zero border
11758         ++dst;
11759       }
11760     }
11761 
11762     static void fons__blur (ubyte* dst, int w, int h, int dstStride, int blur) nothrow @trusted @nogc {
11763       import std.math : expf = exp;
11764       if (blur < 1) return;
11765       // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity)
11766       immutable float sigma = cast(float)blur*0.57735f; // 1/sqrt(3)
11767       int alpha = cast(int)((1<<APREC)*(1.0f-expf(-2.3f/(sigma+1.0f))));
11768       fons__blurRows(dst, w, h, dstStride, alpha);
11769       fons__blurCols(dst, w, h, dstStride, alpha);
11770       fons__blurRows(dst, w, h, dstStride, alpha);
11771       fons__blurCols(dst, w, h, dstStride, alpha);
11772       //fons__blurrows(dst, w, h, dstStride, alpha);
11773       //fons__blurcols(dst, w, h, dstStride, alpha);
11774     }
11775 
11776     int advance, lsb, x0, y0, x1, y1, gx, gy;
11777     FONSglyph* glyph = null;
11778     float size = isize/10.0f;
11779     FONSfont* renderFont = font;
11780 
11781     if (isize < 2) return null;
11782     if (iblur > 20) iblur = 20;
11783     int pad = iblur+2;
11784 
11785     // Reset allocator.
11786     nscratch = 0;
11787 
11788     // Find code point and size.
11789     uint h = fons__hashint(codepoint)&(FONS_HASH_LUT_SIZE-1);
11790     int i = font.lut.ptr[h];
11791     while (i != -1) {
11792       //if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) return &font.glyphs[i];
11793       if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) {
11794         glyph = &font.glyphs[i];
11795         // Negative coordinate indicates there is no bitmap data created.
11796         if (bitmapOption == FONSBitmapFlag.Optional || (glyph.x0 >= 0 && glyph.y0 >= 0)) return glyph;
11797         // At this point, glyph exists but the bitmap data is not yet created.
11798         break;
11799       }
11800       i = font.glyphs[i].next;
11801     }
11802 
11803     // Create a new glyph or rasterize bitmap data for a cached glyph.
11804     //scale = fons__tt_getPixelHeightScale(&font.font, size);
11805     int g = findGlyphForCP(font, cast(dchar)codepoint, &renderFont);
11806     // It is possible that we did not find a fallback glyph.
11807     // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph.
11808 
11809     float scale = fons__tt_getPixelHeightScale(&renderFont.font, size);
11810     fons__tt_buildGlyphBitmap(&renderFont.font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1);
11811     int gw = x1-x0+pad*2;
11812     int gh = y1-y0+pad*2;
11813 
11814     // Determines the spot to draw glyph in the atlas.
11815     if (bitmapOption == FONSBitmapFlag.Required) {
11816       // Find free spot for the rect in the atlas.
11817       bool added = atlas.addRect(gw, gh, &gx, &gy);
11818       if (!added && handleError !is null) {
11819         // Atlas is full, let the user to resize the atlas (or not), and try again.
11820         handleError(FONSError.AtlasFull, 0);
11821         added = atlas.addRect(gw, gh, &gx, &gy);
11822       }
11823       if (!added) return null;
11824     } else {
11825       // Negative coordinate indicates there is no bitmap data created.
11826       gx = -1;
11827       gy = -1;
11828     }
11829 
11830     // Init glyph.
11831     if (glyph is null) {
11832       glyph = font.allocGlyph();
11833       glyph.codepoint = codepoint;
11834       glyph.size = isize;
11835       glyph.blur = iblur;
11836       glyph.next = 0;
11837 
11838       // Insert char to hash lookup.
11839       glyph.next = font.lut.ptr[h];
11840       font.lut.ptr[h] = font.nglyphs-1;
11841     }
11842     glyph.index = g;
11843     glyph.x0 = cast(short)gx;
11844     glyph.y0 = cast(short)gy;
11845     glyph.x1 = cast(short)(glyph.x0+gw);
11846     glyph.y1 = cast(short)(glyph.y0+gh);
11847     glyph.xadv = cast(short)(scale*advance*10.0f);
11848     glyph.xoff = cast(short)(x0-pad);
11849     glyph.yoff = cast(short)(y0-pad);
11850 
11851     if (bitmapOption == FONSBitmapFlag.Optional) return glyph;
11852 
11853     // Rasterize
11854     ubyte* dst = &texData[(glyph.x0+pad)+(glyph.y0+pad)*params.width];
11855     fons__tt_renderGlyphBitmap(&renderFont.font, dst, gw-pad*2, gh-pad*2, params.width, scale, scale, g);
11856 
11857     // Make sure there is one pixel empty border.
11858     dst = &texData[glyph.x0+glyph.y0*params.width];
11859     foreach (immutable int y; 0..gh) {
11860       dst[y*params.width] = 0;
11861       dst[gw-1+y*params.width] = 0;
11862     }
11863     foreach (immutable int x; 0..gw) {
11864       dst[x] = 0;
11865       dst[x+(gh-1)*params.width] = 0;
11866     }
11867 
11868     // Debug code to color the glyph background
11869     version(none) {
11870       foreach (immutable yy; 0..gh) {
11871         foreach (immutable xx; 0..gw) {
11872           int a = cast(int)dst[xx+yy*params.width]+42;
11873           if (a > 255) a = 255;
11874           dst[xx+yy*params.width] = cast(ubyte)a;
11875         }
11876       }
11877     }
11878 
11879     // Blur
11880     if (iblur > 0) {
11881       nscratch = 0;
11882       ubyte* bdst = &texData[glyph.x0+glyph.y0*params.width];
11883       fons__blur(bdst, gw, gh, params.width, iblur);
11884     }
11885 
11886     dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], glyph.x0);
11887     dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], glyph.y0);
11888     dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], glyph.x1);
11889     dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], glyph.y1);
11890 
11891     return glyph;
11892   }
11893 
11894   //TODO: document this
11895   void getQuad (FONSfont* font, int prevGlyphIndex, FONSglyph* glyph, float size, float scale, float spacing, float* x, float* y, FONSQuad* q) nothrow @trusted @nogc {
11896     if (prevGlyphIndex >= 0) {
11897       immutable float adv = fons__tt_getGlyphKernAdvance(&font.font, size, prevGlyphIndex, glyph.index)/**scale*/; //k8: do we really need scale here?
11898       //if (adv != 0) { import core.stdc.stdio; printf("adv=%g (scale=%g; spacing=%g)\n", cast(double)adv, cast(double)scale, cast(double)spacing); }
11899       *x += cast(int)(adv+spacing /*+0.5f*/); //k8: for me, it looks better this way (with non-aa fonts)
11900     }
11901 
11902     // Each glyph has 2px border to allow good interpolation,
11903     // one pixel to prevent leaking, and one to allow good interpolation for rendering.
11904     // Inset the texture region by one pixel for correct interpolation.
11905     immutable float xoff = cast(short)(glyph.xoff+1);
11906     immutable float yoff = cast(short)(glyph.yoff+1);
11907     immutable float x0 = cast(float)(glyph.x0+1);
11908     immutable float y0 = cast(float)(glyph.y0+1);
11909     immutable float x1 = cast(float)(glyph.x1-1);
11910     immutable float y1 = cast(float)(glyph.y1-1);
11911 
11912     if (params.isZeroTopLeft) {
11913       immutable float rx = cast(float)cast(int)(*x+xoff);
11914       immutable float ry = cast(float)cast(int)(*y+yoff);
11915 
11916       q.x0 = rx;
11917       q.y0 = ry;
11918       q.x1 = rx+x1-x0;
11919       q.y1 = ry+y1-y0;
11920 
11921       q.s0 = x0*itw;
11922       q.t0 = y0*ith;
11923       q.s1 = x1*itw;
11924       q.t1 = y1*ith;
11925     } else {
11926       immutable float rx = cast(float)cast(int)(*x+xoff);
11927       immutable float ry = cast(float)cast(int)(*y-yoff);
11928 
11929       q.x0 = rx;
11930       q.y0 = ry;
11931       q.x1 = rx+x1-x0;
11932       q.y1 = ry-y1+y0;
11933 
11934       q.s0 = x0*itw;
11935       q.t0 = y0*ith;
11936       q.s1 = x1*itw;
11937       q.t1 = y1*ith;
11938     }
11939 
11940     *x += cast(int)(glyph.xadv/10.0f+0.5f);
11941   }
11942 
11943   void flush () nothrow @trusted @nogc {
11944     // flush texture
11945     if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) {
11946       if (params.renderUpdate !is null) params.renderUpdate(params.userPtr, dirtyRect.ptr, texData);
11947       // reset dirty rect
11948       dirtyRect.ptr[0] = params.width;
11949       dirtyRect.ptr[1] = params.height;
11950       dirtyRect.ptr[2] = 0;
11951       dirtyRect.ptr[3] = 0;
11952     }
11953   }
11954 }
11955 
11956 /// Free all resources used by the `stash`, and `stash` itself.
11957 /// Group: font_stash
11958 public void kill (ref FONSContext stash) nothrow @trusted @nogc {
11959   import core.stdc.stdlib : free;
11960   if (stash is null) return;
11961   stash.clear();
11962   free(stash);
11963   stash = null;
11964 }
11965 
11966 
11967 // ////////////////////////////////////////////////////////////////////////// //
11968 void* fons__tmpalloc (usize size, void* up) nothrow @trusted @nogc {
11969   ubyte* ptr;
11970   FONSContext stash = cast(FONSContext)up;
11971   // 16-byte align the returned pointer
11972   size = (size+0xf)&~0xf;
11973   if (stash.nscratch+cast(int)size > FONS_SCRATCH_BUF_SIZE) {
11974     if (stash.handleError !is null) stash.handleError(FONSError.ScratchFull, stash.nscratch+cast(int)size);
11975     return null;
11976   }
11977   ptr = stash.scratch+stash.nscratch;
11978   stash.nscratch += cast(int)size;
11979   return ptr;
11980 }
11981 
11982 void fons__tmpfree (void* ptr, void* up) nothrow @trusted @nogc {
11983   // empty
11984 }
11985 
11986 
11987 // ////////////////////////////////////////////////////////////////////////// //
11988 // Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de>
11989 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
11990 
11991 enum FONS_UTF8_ACCEPT = 0;
11992 enum FONS_UTF8_REJECT = 12;
11993 
11994 static immutable ubyte[364] utf8d = [
11995   // The first part of the table maps bytes to character classes that
11996   // to reduce the size of the transition table and create bitmasks.
11997   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,
11998   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,
11999   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,
12000   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,
12001   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,
12002   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,
12003   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,
12004   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,
12005 
12006   // The second part is a transition table that maps a combination
12007   // of a state of the automaton and a character class to a state.
12008   0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
12009   12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12,
12010   12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12,
12011   12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12,
12012   12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
12013 ];
12014 
12015 private enum DecUtfMixin(string state, string codep, string byte_) =
12016 `{
12017   uint type_ = utf8d.ptr[`~byte_~`];
12018   `~codep~` = (`~state~` != FONS_UTF8_ACCEPT ? (`~byte_~`&0x3fu)|(`~codep~`<<6) : (0xff>>type_)&`~byte_~`);
12019   if ((`~state~` = utf8d.ptr[256+`~state~`+type_]) == FONS_UTF8_REJECT) {
12020     `~state~` = FONS_UTF8_ACCEPT;
12021     `~codep~` = 0xFFFD;
12022   }
12023  }`;
12024 
12025 /*
12026 uint fons__decutf8 (uint* state, uint* codep, uint byte_) {
12027   pragma(inline, true);
12028   uint type = utf8d.ptr[byte_];
12029   *codep = (*state != FONS_UTF8_ACCEPT ? (byte_&0x3fu)|(*codep<<6) : (0xff>>type)&byte_);
12030   *state = utf8d.ptr[256 + *state+type];
12031   return *state;
12032 }
12033 */
12034 
12035 
12036 // ////////////////////////////////////////////////////////////////////////// //
12037 /// This iterator can be used to do text measurement.
12038 /// $(WARNING Don't add new fonts to stash while you are iterating, or you WILL get segfault!)
12039 /// Group: font_stash
12040 public struct FONSTextBoundsIterator {
12041 private:
12042   FONSContext stash;
12043   FONSstate state;
12044   uint codepoint = 0xFFFD;
12045   uint utf8state = 0;
12046   int prevGlyphIndex = -1;
12047   short isize, iblur;
12048   float scale = 0;
12049   FONSfont* font;
12050   float startx = 0, x = 0, y = 0;
12051   float minx = 0, miny = 0, maxx = 0, maxy = 0;
12052 
12053 private:
12054   void clear () nothrow @trusted @nogc {
12055     import core.stdc.string : memset;
12056     memset(&this, 0, this.sizeof);
12057     this.prevGlyphIndex = -1;
12058     this.codepoint = 0xFFFD;
12059   }
12060 
12061 public:
12062   /// Initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator.
12063   this (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { reset(astash, ax, ay); }
12064 
12065   /// (Re)initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator.
12066   void reset (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc {
12067     clear();
12068 
12069     if (astash is null || astash.nstates == 0) return;
12070 
12071     stash = astash;
12072     state = *stash.getState;
12073 
12074     if (state.font < 0 || state.font >= stash.nfonts) { clear(); return; }
12075     font = stash.fonts[state.font];
12076     if (font is null || font.fdata is null) { clear(); return; }
12077 
12078     x = ax;
12079     y = ay;
12080     isize = cast(short)(state.size*10.0f);
12081     iblur = cast(short)state.blur;
12082     scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
12083 
12084     // align vertically
12085     y += astash.getVertAlign(font, state.talign, isize);
12086 
12087     minx = maxx = x;
12088     miny = maxy = y;
12089     startx = x;
12090   }
12091 
12092   /// Can this iterator be used?
12093   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null); }
12094 
12095   /// Put some text into iterator, calculate new values.
12096   void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) {
12097     enum DoCodePointMixin = q{
12098       glyph = stash.getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional);
12099       if (glyph !is null) {
12100         stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q);
12101         if (q.x0 < minx) minx = q.x0;
12102         if (q.x1 > maxx) maxx = q.x1;
12103         if (stash.params.isZeroTopLeft) {
12104           if (q.y0 < miny) miny = q.y0;
12105           if (q.y1 > maxy) maxy = q.y1;
12106         } else {
12107           if (q.y1 < miny) miny = q.y1;
12108           if (q.y0 > maxy) maxy = q.y0;
12109         }
12110         prevGlyphIndex = glyph.index;
12111       } else {
12112         prevGlyphIndex = -1;
12113       }
12114     };
12115 
12116     if (stash is null || str.length == 0) return; // alas
12117 
12118     FONSQuad q;
12119     FONSglyph* glyph;
12120 
12121     static if (is(T == char)) {
12122       foreach (char ch; str) {
12123         mixin(DecUtfMixin!("utf8state", "codepoint", "cast(ubyte)ch"));
12124         if (utf8state) continue; // full char is not collected yet
12125         mixin(DoCodePointMixin);
12126       }
12127     } else {
12128       if (utf8state) {
12129         utf8state = 0;
12130         codepoint = 0xFFFD;
12131         mixin(DoCodePointMixin);
12132       }
12133       foreach (T dch; str) {
12134         static if (is(T == dchar)) {
12135           if (dch > dchar.max) dch = 0xFFFD;
12136         }
12137         codepoint = cast(uint)dch;
12138         mixin(DoCodePointMixin);
12139       }
12140     }
12141   }
12142 
12143   /// Returns current advance.
12144   @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null ? x-startx : 0); }
12145 
12146   /// Returns current text bounds.
12147   void getBounds (ref float[4] bounds) const pure nothrow @safe @nogc {
12148     if (stash is null) { bounds[] = 0; return; }
12149     float lminx = minx, lmaxx = maxx;
12150     // align horizontally
12151     if (state.talign.left) {
12152       // empty
12153     } else if (state.talign.right) {
12154       float ca = advance;
12155       lminx -= ca;
12156       lmaxx -= ca;
12157     } else if (state.talign.center) {
12158       float ca = advance*0.5f;
12159       lminx -= ca;
12160       lmaxx -= ca;
12161     }
12162     bounds[0] = lminx;
12163     bounds[1] = miny;
12164     bounds[2] = lmaxx;
12165     bounds[3] = maxy;
12166   }
12167 
12168   /// Returns current horizontal text bounds.
12169   void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc {
12170     if (stash !is null) {
12171       float lminx = minx, lmaxx = maxx;
12172       // align horizontally
12173       if (state.talign.left) {
12174         // empty
12175       } else if (state.talign.right) {
12176         float ca = advance;
12177         lminx -= ca;
12178         lmaxx -= ca;
12179       } else if (state.talign.center) {
12180         float ca = advance*0.5f;
12181         lminx -= ca;
12182         lmaxx -= ca;
12183       }
12184       xmin = lminx;
12185       xmax = lmaxx;
12186     } else {
12187       xmin = xmax = 0;
12188     }
12189   }
12190 
12191   /// Returns current vertical text bounds.
12192   void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc {
12193     pragma(inline, true);
12194     if (stash !is null) {
12195       ymin = miny;
12196       ymax = maxy;
12197     } else {
12198       ymin = ymax = 0;
12199     }
12200   }
12201 
12202   /// Returns font line height.
12203   float lineHeight () nothrow @trusted @nogc {
12204     pragma(inline, true);
12205     return (stash !is null ? stash.fonts[state.font].lineh*cast(short)(state.size*10.0f)/10.0f : 0);
12206   }
12207 
12208   /// Returns font ascender (positive).
12209   float ascender () nothrow @trusted @nogc {
12210     pragma(inline, true);
12211     return (stash !is null ? stash.fonts[state.font].ascender*cast(short)(state.size*10.0f)/10.0f : 0);
12212   }
12213 
12214   /// Returns font descender (negative).
12215   float descender () nothrow @trusted @nogc {
12216     pragma(inline, true);
12217     return (stash !is null ? stash.fonts[state.font].descender*cast(short)(state.size*10.0f)/10.0f : 0);
12218   }
12219 }
12220 
12221 
12222 // ////////////////////////////////////////////////////////////////////////// //
12223 // backgl
12224 // ////////////////////////////////////////////////////////////////////////// //
12225 import core.stdc.stdlib : malloc, realloc, free;
12226 import core.stdc.string : memcpy, memset;
12227 
12228 static if (__VERSION__ < 2076) {
12229   private auto DGNoThrowNoGC(T) (scope T t) /*if (isFunctionPointer!T || isDelegate!T)*/ {
12230     import std.traits;
12231     enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
12232     return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
12233   }
12234 }
12235 
12236 
12237 //import arsd.simpledisplay;
12238 version(nanovg_bindbc_opengl_bindings) {
12239   import bindbc.opengl;
12240 } else version(nanovg_builtin_opengl_bindings) {
12241   import arsd.simpledisplay;
12242 
12243 	/++
12244 		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.
12245 
12246 		History:
12247 			Added January 22, 2021 (version 9.2 release)
12248 	+/
12249 	public class NVGWindow : SimpleWindow {
12250 		NVGContext nvg;
12251 
12252 		/++
12253 
12254 		+/
12255 		this(int width, int height, string title) {
12256 			setOpenGLContextVersion(3, 0);
12257 			super(width, height, title, OpenGlOptions.yes, Resizability.allowResizing);
12258 
12259 			this.onClosing = delegate() {
12260 				nvg.kill();
12261 			};
12262 
12263 			this.visibleForTheFirstTime = delegate() {
12264 				this.setAsCurrentOpenGlContext();
12265 				nvg = nvgCreateContext();
12266 				if(nvg is null) throw new Exception("cannot initialize NanoVega");
12267 			};
12268 
12269 			this.redrawOpenGlScene = delegate() {
12270 				if(redrawNVGScene is null)
12271 					return;
12272 				glViewport(0, 0, this.width, this.height);
12273 				if(clearOnEachFrame) {
12274 					glClearColor(0, 0, 0, 0);
12275 					glClear(glNVGClearFlags);
12276 				}
12277 
12278 				nvg.beginFrame(this.width, this.height);
12279 				scope(exit) nvg.endFrame();
12280 
12281 				redrawNVGScene(nvg);
12282 			};
12283 
12284 			this.setEventHandlers(
12285 				&redrawOpenGlSceneNow,
12286 				(KeyEvent ke) {
12287 					if(ke.key == Key.Escape || ke.key == Key.Q)
12288 						this.close();
12289 				}
12290 			);
12291 		}
12292 
12293 		/++
12294 
12295 		+/
12296 		bool clearOnEachFrame = true;
12297 
12298 		/++
12299 
12300 		+/
12301 		void delegate(NVGContext nvg) redrawNVGScene;
12302 
12303 		/++
12304 
12305 		+/
12306 		void redrawNVGSceneNow() {
12307 			redrawOpenGlSceneNow();
12308 		}
12309 	}
12310 
12311 } else {
12312   import iv.glbinds;
12313 }
12314 
12315 private:
12316 // sdpy is missing that yet
12317 static if (!is(typeof(GL_STENCIL_BUFFER_BIT))) enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
12318 
12319 
12320 
12321 version(bindbc){
12322   private extern(System) nothrow @nogc:
12323   // this definition doesn't exist in regular OpenGL (?)
12324   enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
12325   private void nanovgInitOpenGL () {
12326     // i'm not aware of calling the load multiple times having negative side effects, so i don't do an initialization check
12327     GLSupport support = loadOpenGL();
12328     if (support == GLSupport.noLibrary)
12329       assert(0, "OpenGL initialization failed: shared library failed to load");
12330     else if (support == GLSupport.badLibrary)
12331       assert(0, "OpenGL initialization failed: a context-independent symbol failed to load");
12332     else if (support == GLSupport.noContext)
12333       assert(0, "OpenGL initialization failed: a context needs to be created prior to initialization");
12334   }
12335 } else { // OpenGL API missing from simpledisplay
12336     private void nanovgInitOpenGL () @nogc nothrow @system {
12337       __gshared bool initialized = false;
12338       if (initialized) return;
12339 
12340       try
12341         gl3.loadDynamicLibrary();
12342       catch(Exception)
12343       	assert(0, "GL 3 failed to load");
12344 
12345       initialized = true;
12346   }
12347 }
12348 
12349 
12350 
12351 /// Context creation flags.
12352 /// Group: context_management
12353 public enum NVGContextFlag : int {
12354   /// Nothing special, i.e. empty flag.
12355   None = 0,
12356   /// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA).
12357   Antialias = 1U<<0,
12358   /** Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little
12359     * slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. */
12360   StencilStrokes = 1U<<1,
12361   /// Flag indicating that additional debug checks are done.
12362   Debug = 1U<<2,
12363   /// Filter (antialias) fonts
12364   FontAA = 1U<<7,
12365   /// Don't filter (antialias) fonts
12366   FontNoAA = 1U<<8,
12367   /// You can use this as a substitute for default flags, for cases like this: `nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);`.
12368   Default = 1U<<31,
12369 }
12370 
12371 public enum NANOVG_GL_USE_STATE_FILTER = true;
12372 
12373 /// Returns flags for glClear().
12374 /// Group: context_management
12375 public uint glNVGClearFlags () pure nothrow @safe @nogc {
12376   pragma(inline, true);
12377   return (GL_COLOR_BUFFER_BIT|/*GL_DEPTH_BUFFER_BIT|*/GL_STENCIL_BUFFER_BIT);
12378 }
12379 
12380 
12381 // ////////////////////////////////////////////////////////////////////////// //
12382 private:
12383 
12384 version = nanovega_shared_stencil;
12385 //version = nanovega_debug_clipping;
12386 
12387 enum GLNVGuniformLoc {
12388   ViewSize,
12389   Tex,
12390   Frag,
12391   TMat,
12392   TTr,
12393   ClipTex,
12394 }
12395 
12396 alias GLNVGshaderType = int;
12397 enum /*GLNVGshaderType*/ {
12398   NSVG_SHADER_FILLCOLOR,
12399   NSVG_SHADER_FILLGRAD,
12400   NSVG_SHADER_FILLIMG,
12401   NSVG_SHADER_SIMPLE, // also used for clipfill
12402   NSVG_SHADER_IMG,
12403 }
12404 
12405 struct GLNVGshader {
12406   GLuint prog;
12407   GLuint frag;
12408   GLuint vert;
12409   GLint[GLNVGuniformLoc.max+1] loc;
12410 }
12411 
12412 struct GLNVGtexture {
12413   int id;
12414   GLuint tex;
12415   int width, height;
12416   NVGtexture type;
12417   int flags;
12418   shared int rc; // this can be 0 with tex != 0 -- postponed deletion
12419   int nextfree;
12420 }
12421 
12422 struct GLNVGblend {
12423   bool simple;
12424   GLenum srcRGB;
12425   GLenum dstRGB;
12426   GLenum srcAlpha;
12427   GLenum dstAlpha;
12428 }
12429 
12430 alias GLNVGcallType = int;
12431 enum /*GLNVGcallType*/ {
12432   GLNVG_NONE = 0,
12433   GLNVG_FILL,
12434   GLNVG_CONVEXFILL,
12435   GLNVG_STROKE,
12436   GLNVG_TRIANGLES,
12437   GLNVG_AFFINE, // change affine transformation matrix
12438   GLNVG_PUSHCLIP,
12439   GLNVG_POPCLIP,
12440   GLNVG_RESETCLIP,
12441   GLNVG_CLIP_DDUMP_ON,
12442   GLNVG_CLIP_DDUMP_OFF,
12443 }
12444 
12445 struct GLNVGcall {
12446   int type;
12447   int evenOdd; // for fill
12448   int image;
12449   int pathOffset;
12450   int pathCount;
12451   int triangleOffset;
12452   int triangleCount;
12453   int uniformOffset;
12454   NVGMatrix affine;
12455   GLNVGblend blendFunc;
12456   NVGClipMode clipmode;
12457 }
12458 
12459 struct GLNVGpath {
12460   int fillOffset;
12461   int fillCount;
12462   int strokeOffset;
12463   int strokeCount;
12464 }
12465 
12466 align(1) struct GLNVGfragUniforms {
12467 align(1):
12468   enum UNIFORM_ARRAY_SIZE = 13;
12469   // note: after modifying layout or size of uniform array,
12470   // don't forget to also update the fragment shader source!
12471   align(1) union {
12472   align(1):
12473     align(1) struct {
12474     align(1):
12475       float[12] scissorMat; // matrices are actually 3 vec4s
12476       float[12] paintMat;
12477       NVGColor innerCol;
12478       NVGColor middleCol;
12479       NVGColor outerCol;
12480       float[2] scissorExt;
12481       float[2] scissorScale;
12482       float[2] extent;
12483       float radius;
12484       float feather;
12485       float strokeMult;
12486       float strokeThr;
12487       float texType;
12488       float type;
12489       float doclip;
12490       float midp; // for gradients
12491       float unused2, unused3;
12492     }
12493     float[4][UNIFORM_ARRAY_SIZE] uniformArray;
12494   }
12495 }
12496 
12497 enum GLMaskState {
12498   DontMask = -1,
12499   Uninitialized = 0,
12500   Initialized = 1,
12501   JustCleared = 2,
12502 }
12503 
12504 import core.sync.mutex;
12505 __gshared Mutex GLNVGTextureLocker;
12506 shared static this() {
12507 	GLNVGTextureLocker = new Mutex();
12508 }
12509 
12510 struct GLNVGcontext {
12511   private import core.thread : ThreadID;
12512 
12513   GLNVGshader shader;
12514   GLNVGtexture* textures;
12515   float[2] view;
12516   int freetexid; // -1: none
12517   int ntextures;
12518   int ctextures;
12519   GLuint vaoBuf;
12520   GLuint vertBuf;
12521   int fragSize;
12522   int flags;
12523   // FBOs for masks
12524   GLuint[NVG_MAX_STATES] fbo;
12525   GLuint[2][NVG_MAX_STATES] fboTex; // FBO textures: [0] is color, [1] is stencil
12526   int fboWidth, fboHeight;
12527   GLMaskState[NVG_MAX_STATES] maskStack;
12528   int msp; // mask stack pointer; starts from `0`; points to next free item; see below for logic description
12529   int lastClipFBO; // -666: cache invalidated; -1: don't mask
12530   int lastClipUniOfs;
12531   bool doClipUnion; // specal mode
12532   GLNVGshader shaderFillFBO;
12533   GLNVGshader shaderCopyFBO;
12534 
12535   bool inFrame; // will be `true` if we can perform OpenGL operations (used in texture deletion)
12536   shared bool mustCleanTextures; // will be `true` if we should delete some textures
12537   ThreadID mainTID;
12538   uint mainFBO;
12539 
12540   // Per frame buffers
12541   GLNVGcall* calls;
12542   int ccalls;
12543   int ncalls;
12544   GLNVGpath* paths;
12545   int cpaths;
12546   int npaths;
12547   NVGVertex* verts;
12548   int cverts;
12549   int nverts;
12550   ubyte* uniforms;
12551   int cuniforms;
12552   int nuniforms;
12553   NVGMatrix lastAffine;
12554 
12555   // cached state
12556   static if (NANOVG_GL_USE_STATE_FILTER) {
12557     GLuint boundTexture;
12558     GLuint stencilMask;
12559     GLenum stencilFunc;
12560     GLint stencilFuncRef;
12561     GLuint stencilFuncMask;
12562     GLNVGblend blendFunc;
12563   }
12564 }
12565 
12566 int glnvg__maxi() (int a, int b) { pragma(inline, true); return (a > b ? a : b); }
12567 
12568 void glnvg__bindTexture (GLNVGcontext* gl, GLuint tex) nothrow @trusted @nogc {
12569   static if (NANOVG_GL_USE_STATE_FILTER) {
12570     if (gl.boundTexture != tex) {
12571       gl.boundTexture = tex;
12572       glBindTexture(GL_TEXTURE_2D, tex);
12573     }
12574   } else {
12575     glBindTexture(GL_TEXTURE_2D, tex);
12576   }
12577 }
12578 
12579 void glnvg__stencilMask (GLNVGcontext* gl, GLuint mask) nothrow @trusted @nogc {
12580   static if (NANOVG_GL_USE_STATE_FILTER) {
12581     if (gl.stencilMask != mask) {
12582       gl.stencilMask = mask;
12583       glStencilMask(mask);
12584     }
12585   } else {
12586     glStencilMask(mask);
12587   }
12588 }
12589 
12590 void glnvg__stencilFunc (GLNVGcontext* gl, GLenum func, GLint ref_, GLuint mask) nothrow @trusted @nogc {
12591   static if (NANOVG_GL_USE_STATE_FILTER) {
12592     if (gl.stencilFunc != func || gl.stencilFuncRef != ref_ || gl.stencilFuncMask != mask) {
12593       gl.stencilFunc = func;
12594       gl.stencilFuncRef = ref_;
12595       gl.stencilFuncMask = mask;
12596       glStencilFunc(func, ref_, mask);
12597     }
12598   } else {
12599     glStencilFunc(func, ref_, mask);
12600   }
12601 }
12602 
12603 // texture id is never zero
12604 // sets refcount to one
12605 GLNVGtexture* glnvg__allocTexture (GLNVGcontext* gl) nothrow @trusted @nogc {
12606   GLNVGtexture* tex = null;
12607 
12608   int tid = gl.freetexid;
12609   if (tid == -1) {
12610     if (gl.ntextures >= gl.ctextures) {
12611       assert(gl.ntextures == gl.ctextures);
12612       //pragma(msg, GLNVGtexture.sizeof*32);
12613       int ctextures = (gl.ctextures == 0 ? 32 : gl.ctextures+gl.ctextures/2); // 1.5x overallocate
12614       GLNVGtexture* textures = cast(GLNVGtexture*)realloc(gl.textures, GLNVGtexture.sizeof*ctextures);
12615       if (textures is null) assert(0, "NanoVega: out of memory for textures");
12616       memset(&textures[gl.ctextures], 0, (ctextures-gl.ctextures)*GLNVGtexture.sizeof);
12617       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated more textures (n=%d; c=%d; nc=%d)\n", gl.ntextures, gl.ctextures, ctextures); }}
12618       gl.textures = textures;
12619       gl.ctextures = ctextures;
12620     }
12621     assert(gl.ntextures+1 <= gl.ctextures);
12622     tid = gl.ntextures++;
12623     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("  got next free texture id %d, ntextures=%d\n", tid+1, gl.ntextures); }}
12624   } else {
12625     gl.freetexid = gl.textures[tid].nextfree;
12626   }
12627   assert(tid <= gl.ntextures);
12628 
12629   assert(gl.textures[tid].id == 0);
12630   tex = &gl.textures[tid];
12631   memset(tex, 0, (*tex).sizeof);
12632   tex.id = tid+1;
12633   tex.rc = 1;
12634   tex.nextfree = -1;
12635 
12636   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated texture with id %d (%d)\n", tex.id, tid+1); }}
12637 
12638   return tex;
12639 }
12640 
12641 GLNVGtexture* glnvg__findTexture (GLNVGcontext* gl, int id) nothrow @trusted @nogc {
12642   if (id <= 0 || id > gl.ntextures) return null;
12643   if (gl.textures[id-1].id == 0) return null; // free one
12644   assert(gl.textures[id-1].id == id);
12645   return &gl.textures[id-1];
12646 }
12647 
12648 bool glnvg__deleteTexture (GLNVGcontext* gl, ref int id) nothrow @trusted @nogc {
12649   if (id <= 0 || id > gl.ntextures) return false;
12650   auto tx = &gl.textures[id-1];
12651   if (tx.id == 0) { id = 0; return false; } // free one
12652   assert(tx.id == id);
12653   assert(tx.tex != 0);
12654   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("decrefing texture with id %d (%d)\n", tx.id, id); }}
12655   import core.atomic : atomicOp;
12656   if (atomicOp!"-="(tx.rc, 1) == 0) {
12657     import core.thread : ThreadID;
12658     ThreadID mytid;
12659     static if (__VERSION__ < 2076) {
12660       DGNoThrowNoGC(() {
12661         import core.thread; mytid = Thread.getThis.id;
12662       })();
12663     } else {
12664       try { import core.thread; mytid = Thread.getThis.id; } catch (Exception e) {}
12665     }
12666     if (gl.mainTID == mytid && gl.inFrame) {
12667       // can delete it right now
12668       if ((tx.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tx.tex);
12669       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** deleted texture with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }}
12670       memset(tx, 0, (*tx).sizeof);
12671       //{ import core.stdc.stdio; printf("deleting texture with id %d\n", id); }
12672       tx.nextfree = gl.freetexid;
12673       gl.freetexid = id-1;
12674     } else {
12675       // alas, we aren't doing frame business, so we should postpone deletion
12676       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** POSTPONED texture deletion with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }}
12677       version(aliced) {
12678 	{
12679         GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow;
12680           tx.id = 0; // mark it as dead
12681           gl.mustCleanTextures = true; // set "need cleanup" flag
12682 	}
12683       } else {
12684         try {
12685             GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow;
12686             tx.id = 0; // mark it as dead
12687             gl.mustCleanTextures = true; // set "need cleanup" flag
12688         } catch (Exception e) {}
12689       }
12690     }
12691   }
12692   id = 0;
12693   return true;
12694 }
12695 
12696 void glnvg__dumpShaderError (GLuint shader, const(char)* name, const(char)* type) nothrow @trusted @nogc {
12697   import core.stdc.stdio : fprintf, stderr;
12698   GLchar[512+1] str = 0;
12699   GLsizei len = 0;
12700   glGetShaderInfoLog(shader, 512, &len, str.ptr);
12701   if (len > 512) len = 512;
12702   str[len] = '\0';
12703   fprintf(stderr, "Shader %s/%s error:\n%s\n", name, type, str.ptr);
12704 }
12705 
12706 void glnvg__dumpProgramError (GLuint prog, const(char)* name) nothrow @trusted @nogc {
12707   import core.stdc.stdio : fprintf, stderr;
12708   GLchar[512+1] str = 0;
12709   GLsizei len = 0;
12710   glGetProgramInfoLog(prog, 512, &len, str.ptr);
12711   if (len > 512) len = 512;
12712   str[len] = '\0';
12713   fprintf(stderr, "Program %s error:\n%s\n", name, str.ptr);
12714 }
12715 
12716 void glnvg__resetError(bool force=false) (GLNVGcontext* gl) nothrow @trusted @nogc {
12717   static if (!force) {
12718     if ((gl.flags&NVGContextFlag.Debug) == 0) return;
12719   }
12720   glGetError();
12721 }
12722 
12723 void glnvg__checkError(bool force=false) (GLNVGcontext* gl, const(char)* str) nothrow @trusted @nogc {
12724   GLenum err;
12725   static if (!force) {
12726     if ((gl.flags&NVGContextFlag.Debug) == 0) return;
12727   }
12728   err = glGetError();
12729   if (err != GL_NO_ERROR) {
12730     import core.stdc.stdio : fprintf, stderr;
12731     fprintf(stderr, "Error %08x after %s\n", err, str);
12732     return;
12733   }
12734 }
12735 
12736 bool glnvg__createShader (GLNVGshader* shader, const(char)* name, const(char)* header, const(char)* opts, const(char)* vshader, const(char)* fshader) nothrow @trusted @nogc {
12737   GLint status;
12738   GLuint prog, vert, frag;
12739   const(char)*[3] str;
12740 
12741   memset(shader, 0, (*shader).sizeof);
12742 
12743   prog = glCreateProgram();
12744   vert = glCreateShader(GL_VERTEX_SHADER);
12745   frag = glCreateShader(GL_FRAGMENT_SHADER);
12746   str[0] = header;
12747   str[1] = (opts !is null ? opts : "");
12748   str[2] = vshader;
12749   glShaderSource(vert, 3, cast(const(char)**)str.ptr, null);
12750 
12751   glCompileShader(vert);
12752   glGetShaderiv(vert, GL_COMPILE_STATUS, &status);
12753   if (status != GL_TRUE) {
12754     glnvg__dumpShaderError(vert, name, "vert");
12755     return false;
12756   }
12757 
12758   str[0] = header;
12759   str[1] = (opts !is null ? opts : "");
12760   str[2] = fshader;
12761   glShaderSource(frag, 3, cast(const(char)**)str.ptr, null);
12762 
12763   glCompileShader(frag);
12764   glGetShaderiv(frag, GL_COMPILE_STATUS, &status);
12765   if (status != GL_TRUE) {
12766     glnvg__dumpShaderError(frag, name, "frag");
12767     return false;
12768   }
12769 
12770   glAttachShader(prog, vert);
12771   glAttachShader(prog, frag);
12772 
12773   glBindAttribLocation(prog, 0, "vertex");
12774   glBindAttribLocation(prog, 1, "tcoord");
12775 
12776   glLinkProgram(prog);
12777   glGetProgramiv(prog, GL_LINK_STATUS, &status);
12778   if (status != GL_TRUE) {
12779     glnvg__dumpProgramError(prog, name);
12780     return false;
12781   }
12782 
12783   shader.prog = prog;
12784   shader.vert = vert;
12785   shader.frag = frag;
12786 
12787   return true;
12788 }
12789 
12790 void glnvg__deleteShader (GLNVGshader* shader) nothrow @trusted @nogc {
12791   if (shader.prog != 0) glDeleteProgram(shader.prog);
12792   if (shader.vert != 0) glDeleteShader(shader.vert);
12793   if (shader.frag != 0) glDeleteShader(shader.frag);
12794 }
12795 
12796 void glnvg__getUniforms (GLNVGshader* shader) nothrow @trusted @nogc {
12797   shader.loc[GLNVGuniformLoc.ViewSize] = glGetUniformLocation(shader.prog, "viewSize");
12798   shader.loc[GLNVGuniformLoc.Tex] = glGetUniformLocation(shader.prog, "tex");
12799   shader.loc[GLNVGuniformLoc.Frag] = glGetUniformLocation(shader.prog, "frag");
12800   shader.loc[GLNVGuniformLoc.TMat] = glGetUniformLocation(shader.prog, "tmat");
12801   shader.loc[GLNVGuniformLoc.TTr] = glGetUniformLocation(shader.prog, "ttr");
12802   shader.loc[GLNVGuniformLoc.ClipTex] = glGetUniformLocation(shader.prog, "clipTex");
12803 }
12804 
12805 void glnvg__killFBOs (GLNVGcontext* gl) nothrow @trusted @nogc {
12806   foreach (immutable fidx, ref GLuint fbo; gl.fbo[]) {
12807     if (fbo != 0) {
12808       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
12809       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
12810       foreach (ref GLuint tid; gl.fboTex.ptr[fidx][]) if (tid != 0) { glDeleteTextures(1, &tid); tid = 0; }
12811       glDeleteFramebuffers(1, &fbo);
12812       fbo = 0;
12813     }
12814   }
12815   gl.fboWidth = gl.fboHeight = 0;
12816 }
12817 
12818 // returns `true` is new FBO was created
12819 // will not unbind buffer, if it was created
12820 bool glnvg__allocFBO (GLNVGcontext* gl, int fidx, bool doclear=true) nothrow @trusted @nogc {
12821   assert(fidx >= 0 && fidx < gl.fbo.length);
12822   assert(gl.fboWidth > 0);
12823   assert(gl.fboHeight > 0);
12824 
12825   if (gl.fbo.ptr[fidx] != 0) return false; // nothing to do, this FBO is already initialized
12826 
12827   glnvg__resetError(gl);
12828 
12829   // allocate FBO object
12830   GLuint fbo = 0;
12831   glGenFramebuffers(1, &fbo);
12832   if (fbo == 0) assert(0, "NanoVega: cannot create FBO");
12833   glnvg__checkError(gl, "glnvg__allocFBO: glGenFramebuffers");
12834   glBindFramebuffer(GL_FRAMEBUFFER, fbo);
12835   //scope(exit) glBindFramebuffer(GL_FRAMEBUFFER, 0);
12836 
12837   // attach 2D texture to this FBO
12838   GLuint tidColor = 0;
12839   glGenTextures(1, &tidColor);
12840   if (tidColor == 0) assert(0, "NanoVega: cannot create RGBA texture for FBO");
12841   glBindTexture(GL_TEXTURE_2D, tidColor);
12842   //scope(exit) glBindTexture(GL_TEXTURE_2D, 0);
12843   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
12844   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_S");
12845   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
12846   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_T");
12847   //FIXME: linear or nearest?
12848   //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
12849   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
12850   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MIN_FILTER");
12851   //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
12852   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
12853   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MAG_FILTER");
12854   // empty texture
12855   //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
12856   // create texture with only one color channel
12857   glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, gl.fboWidth, gl.fboHeight, 0, GL_RED, GL_UNSIGNED_BYTE, null);
12858   //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
12859   glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (color)");
12860   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tidColor, 0);
12861   glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (color)");
12862 
12863   // attach stencil texture to this FBO
12864   GLuint tidStencil = 0;
12865   version(nanovega_shared_stencil) {
12866     if (gl.fboTex.ptr[0].ptr[0] == 0) {
12867       glGenTextures(1, &tidStencil);
12868       if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO");
12869       gl.fboTex.ptr[0].ptr[0] = tidStencil;
12870     } else {
12871       tidStencil = gl.fboTex.ptr[0].ptr[0];
12872     }
12873     if (fidx != 0) gl.fboTex.ptr[fidx].ptr[1] = 0; // stencil texture is shared among FBOs
12874   } else {
12875     glGenTextures(1, &tidStencil);
12876     if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO");
12877     gl.fboTex.ptr[0].ptr[0] = tidStencil;
12878   }
12879   glBindTexture(GL_TEXTURE_2D, tidStencil);
12880   glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, gl.fboWidth, gl.fboHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, null);
12881   glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (stencil)");
12882   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tidStencil, 0);
12883   glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (stencil)");
12884 
12885   {
12886     GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
12887     if (status != GL_FRAMEBUFFER_COMPLETE) {
12888       version(all) {
12889         import core.stdc.stdio;
12890         if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) printf("fucked attachement\n");
12891         if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) printf("fucked dimensions\n");
12892         if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) printf("missing attachement\n");
12893         if (status == GL_FRAMEBUFFER_UNSUPPORTED) printf("unsupported\n");
12894       }
12895       assert(0, "NanoVega: framebuffer creation failed");
12896     }
12897   }
12898 
12899   // clear 'em all
12900   if (doclear) {
12901     glClearColor(0, 0, 0, 0);
12902     glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
12903   }
12904 
12905   // save texture ids
12906   gl.fbo.ptr[fidx] = fbo;
12907   gl.fboTex.ptr[fidx].ptr[0] = tidColor;
12908   version(nanovega_shared_stencil) {} else {
12909     gl.fboTex.ptr[fidx].ptr[1] = tidStencil;
12910   }
12911 
12912   static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture);
12913 
12914   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): created with index %d\n", gl.msp-1, fidx); }
12915 
12916   return true;
12917 }
12918 
12919 // will not unbind buffer
12920 void glnvg__clearFBO (GLNVGcontext* gl, int fidx) nothrow @trusted @nogc {
12921   assert(fidx >= 0 && fidx < gl.fbo.length);
12922   assert(gl.fboWidth > 0);
12923   assert(gl.fboHeight > 0);
12924   assert(gl.fbo.ptr[fidx] != 0);
12925   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[fidx]);
12926   glClearColor(0, 0, 0, 0);
12927   glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
12928   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cleared with index %d\n", gl.msp-1, fidx); }
12929 }
12930 
12931 // will not unbind buffer
12932 void glnvg__copyFBOToFrom (GLNVGcontext* gl, int didx, int sidx) nothrow @trusted @nogc {
12933   import core.stdc.string : memset;
12934   assert(didx >= 0 && didx < gl.fbo.length);
12935   assert(sidx >= 0 && sidx < gl.fbo.length);
12936   assert(gl.fboWidth > 0);
12937   assert(gl.fboHeight > 0);
12938   assert(gl.fbo.ptr[didx] != 0);
12939   assert(gl.fbo.ptr[sidx] != 0);
12940   if (didx == sidx) return;
12941 
12942   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copy FBO: %d -> %d\n", gl.msp-1, sidx, didx); }
12943 
12944   glUseProgram(gl.shaderCopyFBO.prog);
12945 
12946   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[didx]);
12947   glDisable(GL_CULL_FACE);
12948   glDisable(GL_BLEND);
12949   glDisable(GL_SCISSOR_TEST);
12950   glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[sidx].ptr[0]);
12951   // copy texture by drawing full quad
12952   enum x = 0;
12953   enum y = 0;
12954   immutable int w = gl.fboWidth;
12955   immutable int h = gl.fboHeight;
12956   immutable(NVGVertex[4]) vertices =
12957    [NVGVertex(x, y), // top-left
12958     NVGVertex(w, y), // top-right
12959     NVGVertex(w, h), // bottom-right
12960     NVGVertex(x, h)]; // bottom-left
12961 
12962   glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices);
12963   glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
12964 
12965   // restore state (but don't unbind FBO)
12966   static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture);
12967   glEnable(GL_CULL_FACE);
12968   glEnable(GL_BLEND);
12969   glUseProgram(gl.shader.prog);
12970 }
12971 
12972 void glnvg__resetFBOClipTextureCache (GLNVGcontext* gl) nothrow @trusted @nogc {
12973   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): texture cache invalidated (%d)\n", gl.msp-1, gl.lastClipFBO); }
12974   /*
12975   if (gl.lastClipFBO >= 0) {
12976     glActiveTexture(GL_TEXTURE1);
12977     glBindTexture(GL_TEXTURE_2D, 0);
12978     glActiveTexture(GL_TEXTURE0);
12979   }
12980   */
12981   gl.lastClipFBO = -666;
12982 }
12983 
12984 void glnvg__setFBOClipTexture (GLNVGcontext* gl, GLNVGfragUniforms* frag) nothrow @trusted @nogc {
12985   //assert(gl.msp > 0 && gl.msp <= gl.maskStack.length);
12986   if (gl.lastClipFBO != -666) {
12987     // cached
12988     version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cached (%d)\n", gl.msp-1, gl.lastClipFBO); }
12989     frag.doclip = (gl.lastClipFBO >= 0 ? 1 : 0);
12990     return;
12991   }
12992 
12993   // no cache
12994   int fboidx = -1;
12995   mainloop: foreach_reverse (immutable sp, GLMaskState mst; gl.maskStack.ptr[0..gl.msp]/*; reverse*/) {
12996     final switch (mst) {
12997       case GLMaskState.DontMask: fboidx = -1; break mainloop;
12998       case GLMaskState.Uninitialized: break;
12999       case GLMaskState.Initialized: fboidx = cast(int)sp; break mainloop;
13000       case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__setFBOClipTexture()` internal error");
13001     }
13002   }
13003 
13004   if (fboidx < 0) {
13005     // don't mask
13006     gl.lastClipFBO = -1;
13007     frag.doclip = 0;
13008   } else {
13009     // do masking
13010     assert(gl.fbo.ptr[fboidx] != 0);
13011     gl.lastClipFBO = fboidx;
13012     frag.doclip = 1;
13013   }
13014 
13015   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): new cache (new:%d)\n", gl.msp-1, gl.lastClipFBO); }
13016 
13017   if (gl.lastClipFBO >= 0) {
13018     assert(gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
13019     glActiveTexture(GL_TEXTURE1);
13020     glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
13021     glActiveTexture(GL_TEXTURE0);
13022   }
13023 }
13024 
13025 // returns index in `gl.fbo`, or -1 for "don't mask"
13026 int glnvg__generateFBOClipTexture (GLNVGcontext* gl) nothrow @trusted @nogc {
13027   assert(gl.msp > 0 && gl.msp <= gl.maskStack.length);
13028   // we need initialized FBO, even for "don't mask" case
13029   // for this, look back in stack, and either copy initialized FBO,
13030   // or stop at first uninitialized one, and clear it
13031   if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.Initialized) {
13032     // shortcut
13033     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); }
13034     glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[gl.msp-1]);
13035     return gl.msp-1;
13036   }
13037   foreach_reverse (immutable sp; 0..gl.msp/*; reverse*/) {
13038     final switch (gl.maskStack.ptr[sp]) {
13039       case GLMaskState.DontMask:
13040         // clear it
13041         version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture\n", gl.msp-1); }
13042         if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1);
13043         gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared;
13044         return gl.msp-1;
13045       case GLMaskState.Uninitialized: break; // do nothing
13046       case GLMaskState.Initialized:
13047         // i found her! copy to TOS
13048         version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copying texture from %d\n", gl.msp-1, cast(int)sp); }
13049         glnvg__allocFBO(gl, gl.msp-1, false);
13050         glnvg__copyFBOToFrom(gl, gl.msp-1, sp);
13051         gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized;
13052         return gl.msp-1;
13053       case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__generateFBOClipTexture()` internal error");
13054     }
13055   }
13056   // nothing was initialized, lol
13057   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture (first one)\n", gl.msp-1); }
13058   if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1);
13059   gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared;
13060   return gl.msp-1;
13061 }
13062 
13063 void glnvg__renderPushClip (void* uptr) nothrow @trusted @nogc {
13064   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13065   GLNVGcall* call = glnvg__allocCall(gl);
13066   if (call is null) return;
13067   call.type = GLNVG_PUSHCLIP;
13068 }
13069 
13070 void glnvg__renderPopClip (void* uptr) nothrow @trusted @nogc {
13071   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13072   GLNVGcall* call = glnvg__allocCall(gl);
13073   if (call is null) return;
13074   call.type = GLNVG_POPCLIP;
13075 }
13076 
13077 void glnvg__renderResetClip (void* uptr) nothrow @trusted @nogc {
13078   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13079   GLNVGcall* call = glnvg__allocCall(gl);
13080   if (call is null) return;
13081   call.type = GLNVG_RESETCLIP;
13082 }
13083 
13084 void glnvg__clipDebugDump (void* uptr, bool doit) nothrow @trusted @nogc {
13085   version(nanovega_debug_clipping) {
13086     GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13087     GLNVGcall* call = glnvg__allocCall(gl);
13088     call.type = (doit ? GLNVG_CLIP_DDUMP_ON : GLNVG_CLIP_DDUMP_OFF);
13089   }
13090 }
13091 
13092 bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc {
13093   import core.stdc.stdio : snprintf;
13094 
13095   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13096   enum align_ = 4;
13097 
13098   char[64] shaderHeader = void;
13099   //enum shaderHeader = "#define UNIFORM_ARRAY_SIZE 12\n";
13100   snprintf(shaderHeader.ptr, shaderHeader.length, "#define UNIFORM_ARRAY_SIZE %u\n", cast(uint)GLNVGfragUniforms.UNIFORM_ARRAY_SIZE);
13101 
13102   enum fillVertShader = q{
13103     uniform vec2 viewSize;
13104     attribute vec2 vertex;
13105     attribute vec2 tcoord;
13106     varying vec2 ftcoord;
13107     varying vec2 fpos;
13108     uniform vec4 tmat; /* abcd of affine matrix: xyzw */
13109     uniform vec2 ttr; /* tx and ty of affine matrix */
13110     void main (void) {
13111       /* affine transformation */
13112       float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x;
13113       float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y;
13114       ftcoord = tcoord;
13115       fpos = vec2(nx, ny);
13116       gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1);
13117     }
13118   };
13119 
13120   enum fillFragShader = `
13121     uniform vec4 frag[UNIFORM_ARRAY_SIZE];
13122     uniform sampler2D tex;
13123     uniform sampler2D clipTex;
13124     uniform vec2 viewSize;
13125     varying vec2 ftcoord;
13126     varying vec2 fpos;
13127     #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz)
13128     #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz)
13129     #define innerCol frag[6]
13130     #define middleCol frag[7]
13131     #define outerCol frag[7+1]
13132     #define scissorExt frag[8+1].xy
13133     #define scissorScale frag[8+1].zw
13134     #define extent frag[9+1].xy
13135     #define radius frag[9+1].z
13136     #define feather frag[9+1].w
13137     #define strokeMult frag[10+1].x
13138     #define strokeThr frag[10+1].y
13139     #define texType int(frag[10+1].z)
13140     #define type int(frag[10+1].w)
13141     #define doclip int(frag[11+1].x)
13142     #define midp frag[11+1].y
13143 
13144     float sdroundrect (in vec2 pt, in vec2 ext, in float rad) {
13145       vec2 ext2 = ext-vec2(rad, rad);
13146       vec2 d = abs(pt)-ext2;
13147       return min(max(d.x, d.y), 0.0)+length(max(d, 0.0))-rad;
13148     }
13149 
13150     // Scissoring
13151     float scissorMask (in vec2 p) {
13152       vec2 sc = (abs((scissorMat*vec3(p, 1.0)).xy)-scissorExt);
13153       sc = vec2(0.5, 0.5)-sc*scissorScale;
13154       return clamp(sc.x, 0.0, 1.0)*clamp(sc.y, 0.0, 1.0);
13155     }
13156 
13157     #ifdef EDGE_AA
13158     // Stroke - from [0..1] to clipped pyramid, where the slope is 1px.
13159     float strokeMask () {
13160       return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult)*min(1.0, ftcoord.y);
13161     }
13162     #endif
13163 
13164     void main (void) {
13165       // clipping
13166       if (doclip != 0) {
13167         /*vec4 clr = texelFetch(clipTex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);*/
13168         vec4 clr = texture2D(clipTex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y));
13169         if (clr.r == 0.0) discard;
13170       }
13171       float scissor = scissorMask(fpos);
13172       if (scissor <= 0.0) discard; //k8: is it really faster?
13173       #ifdef EDGE_AA
13174       float strokeAlpha = strokeMask();
13175       if (strokeAlpha < strokeThr) discard;
13176       #else
13177       float strokeAlpha = 1.0;
13178       #endif
13179       // rendering
13180       vec4 color;
13181       if (type == 0) { /* NSVG_SHADER_FILLCOLOR */
13182         color = innerCol;
13183         // Combine alpha
13184         color *= strokeAlpha*scissor;
13185       } else if (type == 1) { /* NSVG_SHADER_FILLGRAD */
13186         // Gradient
13187         // Calculate gradient color using box gradient
13188         vec2 pt = (paintMat*vec3(fpos, 1.0)).xy;
13189         float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0);
13190         if (midp <= 0.0) {
13191           color = mix(innerCol, outerCol, d);
13192         } else {
13193           float gdst = min(midp, 1.0);
13194           if (d < gdst) {
13195             color = mix(innerCol, middleCol, d/gdst);
13196           } else {
13197             color = mix(middleCol, outerCol, (d-gdst)/gdst);
13198           }
13199         }
13200         // Combine alpha
13201         color *= strokeAlpha*scissor;
13202       } else if (type == 2) { /* NSVG_SHADER_FILLIMG */
13203         // Image
13204         // Calculate color from texture
13205         vec2 pt = (paintMat*vec3(fpos, 1.0)).xy/extent;
13206         color = texture2D(tex, pt);
13207         if (texType == 1) color = vec4(color.xyz*color.w, color.w);
13208         if (texType == 2) color = vec4(color.x);
13209         // Apply color tint and alpha
13210         color *= innerCol;
13211         // Combine alpha
13212         color *= strokeAlpha*scissor;
13213       } else if (type == 3) { /* NSVG_SHADER_SIMPLE */
13214         // Stencil fill
13215         color = vec4(1, 1, 1, 1);
13216       } else if (type == 4) { /* NSVG_SHADER_IMG */
13217         // Textured tris
13218         color = texture2D(tex, ftcoord);
13219         if (texType == 1) color = vec4(color.xyz*color.w, color.w);
13220         if (texType == 2) color = vec4(color.x);
13221         color *= scissor;
13222         color *= innerCol; // Apply color tint
13223       }
13224       gl_FragColor = color;
13225     }
13226   `;
13227 
13228   enum clipVertShaderFill = q{
13229     uniform vec2 viewSize;
13230     attribute vec2 vertex;
13231     uniform vec4 tmat; /* abcd of affine matrix: xyzw */
13232     uniform vec2 ttr; /* tx and ty of affine matrix */
13233     void main (void) {
13234       /* affine transformation */
13235       float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x;
13236       float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y;
13237       gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1);
13238     }
13239   };
13240 
13241   enum clipFragShaderFill = q{
13242     uniform vec2 viewSize;
13243     void main (void) {
13244       gl_FragColor = vec4(1, 1, 1, 1);
13245     }
13246   };
13247 
13248   enum clipVertShaderCopy = q{
13249     uniform vec2 viewSize;
13250     attribute vec2 vertex;
13251     void main (void) {
13252       gl_Position = vec4(2.0*vertex.x/viewSize.x-1.0, 1.0-2.0*vertex.y/viewSize.y, 0, 1);
13253     }
13254   };
13255 
13256   enum clipFragShaderCopy = q{
13257     uniform sampler2D tex;
13258     uniform vec2 viewSize;
13259     void main (void) {
13260       //gl_FragColor = texelFetch(tex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);
13261       gl_FragColor = texture2D(tex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y));
13262     }
13263   };
13264 
13265   glnvg__checkError(gl, "init");
13266 
13267   string defines = (gl.flags&NVGContextFlag.Antialias ? "#define EDGE_AA 1\n" : null);
13268   if (!glnvg__createShader(&gl.shader, "shader", shaderHeader.ptr, defines.ptr, fillVertShader, fillFragShader)) return false;
13269   if (!glnvg__createShader(&gl.shaderFillFBO, "shaderFillFBO", shaderHeader.ptr, defines.ptr, clipVertShaderFill, clipFragShaderFill)) return false;
13270   if (!glnvg__createShader(&gl.shaderCopyFBO, "shaderCopyFBO", shaderHeader.ptr, defines.ptr, clipVertShaderCopy, clipFragShaderCopy)) return false;
13271 
13272   glnvg__checkError(gl, "uniform locations");
13273   glnvg__getUniforms(&gl.shader);
13274   glnvg__getUniforms(&gl.shaderFillFBO);
13275   glnvg__getUniforms(&gl.shaderCopyFBO);
13276 
13277   // Create VAO (required for modern OpenGL compatibility)
13278   glGenVertexArrays(1, &gl.vaoBuf);
13279   // Create dynamic vertex array
13280   glGenBuffers(1, &gl.vertBuf);
13281 
13282   gl.fragSize = GLNVGfragUniforms.sizeof+align_-GLNVGfragUniforms.sizeof%align_;
13283 
13284   glnvg__checkError(gl, "create done");
13285 
13286   glFinish();
13287 
13288   return true;
13289 }
13290 
13291 int glnvg__renderCreateTexture (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc {
13292   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13293   GLNVGtexture* tex = glnvg__allocTexture(gl);
13294 
13295   if (tex is null) return 0;
13296 
13297   glGenTextures(1, &tex.tex);
13298   tex.width = w;
13299   tex.height = h;
13300   tex.type = type;
13301   tex.flags = imageFlags;
13302   glnvg__bindTexture(gl, tex.tex);
13303 
13304   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("created texture with id %d; glid=%u\n", tex.id, tex.tex); }}
13305 
13306   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
13307   glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width);
13308   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13309   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13310 
13311 
13312 
13313   immutable ttype = (type == NVGtexture.RGBA ? GL_RGBA : GL_RED);
13314   glTexImage2D(GL_TEXTURE_2D, 0, ttype, w, h, 0, ttype, GL_UNSIGNED_BYTE, data);
13315   // GL 3.0 and later have support for a dedicated function for generating mipmaps
13316   // it needs to be called after the glTexImage2D call
13317   if (imageFlags & NVGImageFlag.GenerateMipmaps)
13318     glGenerateMipmap(GL_TEXTURE_2D);
13319 
13320   immutable tfmin =
13321     (imageFlags & NVGImageFlag.GenerateMipmaps ? GL_LINEAR_MIPMAP_LINEAR :
13322      imageFlags & NVGImageFlag.NoFiltering ? GL_NEAREST :
13323      GL_LINEAR);
13324   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.0);
13325   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tfmin);
13326   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST : GL_LINEAR));
13327 
13328   int flag;
13329   if (imageFlags&NVGImageFlag.RepeatX)
13330     flag = GL_REPEAT;
13331   else if (imageFlags&NVGImageFlag.ClampToBorderX)
13332     flag = GL_CLAMP_TO_BORDER;
13333   else
13334     flag = GL_CLAMP_TO_EDGE;
13335   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, flag);
13336 
13337 
13338   if (imageFlags&NVGImageFlag.RepeatY)
13339     flag = GL_REPEAT;
13340   else if (imageFlags&NVGImageFlag.ClampToBorderY)
13341     flag = GL_CLAMP_TO_BORDER;
13342   else
13343     flag = GL_CLAMP_TO_EDGE;
13344   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, flag);
13345 
13346   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
13347   glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
13348   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13349   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13350 
13351   glnvg__checkError(gl, "create tex");
13352   glnvg__bindTexture(gl, 0);
13353 
13354   return tex.id;
13355 }
13356 
13357 bool glnvg__renderDeleteTexture (void* uptr, int image) nothrow @trusted @nogc {
13358   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13359   return glnvg__deleteTexture(gl, image);
13360 }
13361 
13362 bool glnvg__renderTextureIncRef (void* uptr, int image) nothrow @trusted @nogc {
13363   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13364   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13365   if (tex is null) {
13366     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT incref texture with id %d\n", image); }}
13367     return false;
13368   }
13369   import core.atomic : atomicOp;
13370   atomicOp!"+="(tex.rc, 1);
13371   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("texture #%d: incref; newref=%d\n", image, tex.rc); }}
13372   return true;
13373 }
13374 
13375 bool glnvg__renderUpdateTexture (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc {
13376   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13377   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13378 
13379   if (tex is null) {
13380     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT update texture with id %d\n", image); }}
13381     return false;
13382   }
13383 
13384   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("updated texture with id %d; glid=%u\n", tex.id, image, tex.tex); }}
13385 
13386   glnvg__bindTexture(gl, tex.tex);
13387 
13388   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
13389   glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width);
13390   glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
13391   glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
13392 
13393   immutable ttype = (tex.type == NVGtexture.RGBA ? GL_RGBA : GL_RED);
13394   glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, ttype, GL_UNSIGNED_BYTE, data);
13395 
13396   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
13397   glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
13398   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13399   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13400 
13401   glnvg__bindTexture(gl, 0);
13402 
13403   return true;
13404 }
13405 
13406 bool glnvg__renderGetTextureSize (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc {
13407   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13408   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13409   if (tex is null) {
13410     if (w !is null) *w = 0;
13411     if (h !is null) *h = 0;
13412     return false;
13413   } else {
13414     if (w !is null) *w = tex.width;
13415     if (h !is null) *h = tex.height;
13416     return true;
13417   }
13418 }
13419 
13420 void glnvg__xformToMat3x4 (float[] m3, const(float)[] t) nothrow @trusted @nogc {
13421   assert(t.length >= 6);
13422   assert(m3.length >= 12);
13423   m3.ptr[0] = t.ptr[0];
13424   m3.ptr[1] = t.ptr[1];
13425   m3.ptr[2] = 0.0f;
13426   m3.ptr[3] = 0.0f;
13427   m3.ptr[4] = t.ptr[2];
13428   m3.ptr[5] = t.ptr[3];
13429   m3.ptr[6] = 0.0f;
13430   m3.ptr[7] = 0.0f;
13431   m3.ptr[8] = t.ptr[4];
13432   m3.ptr[9] = t.ptr[5];
13433   m3.ptr[10] = 1.0f;
13434   m3.ptr[11] = 0.0f;
13435 }
13436 
13437 NVGColor glnvg__premulColor() (const scope auto ref NVGColor c) nothrow @trusted @nogc {
13438   //pragma(inline, true);
13439   NVGColor res = void;
13440   res.r = c.r*c.a;
13441   res.g = c.g*c.a;
13442   res.b = c.b*c.a;
13443   res.a = c.a;
13444   return res;
13445 }
13446 
13447 bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* paint, NVGscissor* scissor, float width, float fringe, float strokeThr) nothrow @trusted @nogc {
13448   import core.stdc.math : sqrtf;
13449   GLNVGtexture* tex = null;
13450   NVGMatrix invxform = void;
13451 
13452   memset(frag, 0, (*frag).sizeof);
13453 
13454   frag.innerCol = glnvg__premulColor(paint.innerColor);
13455   frag.middleCol = glnvg__premulColor(paint.middleColor);
13456   frag.outerCol = glnvg__premulColor(paint.outerColor);
13457   frag.midp = paint.midp;
13458 
13459   if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) {
13460     memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof);
13461     frag.scissorExt.ptr[0] = 1.0f;
13462     frag.scissorExt.ptr[1] = 1.0f;
13463     frag.scissorScale.ptr[0] = 1.0f;
13464     frag.scissorScale.ptr[1] = 1.0f;
13465   } else {
13466     //nvgTransformInverse(invxform[], scissor.xform[]);
13467     invxform = scissor.xform.inverted;
13468     glnvg__xformToMat3x4(frag.scissorMat[], invxform.mat[]);
13469     frag.scissorExt.ptr[0] = scissor.extent.ptr[0];
13470     frag.scissorExt.ptr[1] = scissor.extent.ptr[1];
13471     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;
13472     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;
13473   }
13474 
13475   memcpy(frag.extent.ptr, paint.extent.ptr, frag.extent.sizeof);
13476   frag.strokeMult = (width*0.5f+fringe*0.5f)/fringe;
13477   frag.strokeThr = strokeThr;
13478 
13479   if (paint.image.valid) {
13480     tex = glnvg__findTexture(gl, paint.image.id);
13481     if (tex is null) return false;
13482     if ((tex.flags&NVGImageFlag.FlipY) != 0) {
13483       /*
13484       NVGMatrix flipped;
13485       nvgTransformScale(flipped[], 1.0f, -1.0f);
13486       nvgTransformMultiply(flipped[], paint.xform[]);
13487       nvgTransformInverse(invxform[], flipped[]);
13488       */
13489       /*
13490       NVGMatrix m1 = void, m2 = void;
13491       nvgTransformTranslate(m1[], 0.0f, frag.extent.ptr[1]*0.5f);
13492       nvgTransformMultiply(m1[], paint.xform[]);
13493       nvgTransformScale(m2[], 1.0f, -1.0f);
13494       nvgTransformMultiply(m2[], m1[]);
13495       nvgTransformTranslate(m1[], 0.0f, -frag.extent.ptr[1]*0.5f);
13496       nvgTransformMultiply(m1[], m2[]);
13497       nvgTransformInverse(invxform[], m1[]);
13498       */
13499       NVGMatrix m1 = NVGMatrix.Translated(0.0f, frag.extent.ptr[1]*0.5f);
13500       m1.mul(paint.xform);
13501       NVGMatrix m2 = NVGMatrix.Scaled(1.0f, -1.0f);
13502       m2.mul(m1);
13503       m1 = NVGMatrix.Translated(0.0f, -frag.extent.ptr[1]*0.5f);
13504       m1.mul(m2);
13505       invxform = m1.inverted;
13506     } else {
13507       //nvgTransformInverse(invxform[], paint.xform[]);
13508       invxform = paint.xform.inverted;
13509     }
13510     frag.type = NSVG_SHADER_FILLIMG;
13511 
13512     if (tex.type == NVGtexture.RGBA) {
13513       frag.texType = (tex.flags&NVGImageFlag.Premultiplied ? 0 : 1);
13514     } else {
13515       frag.texType = 2;
13516     }
13517     //printf("frag.texType = %d\n", frag.texType);
13518   } else {
13519     frag.type = (paint.simpleColor ? NSVG_SHADER_FILLCOLOR : NSVG_SHADER_FILLGRAD);
13520     frag.radius = paint.radius;
13521     frag.feather = paint.feather;
13522     //nvgTransformInverse(invxform[], paint.xform[]);
13523     invxform = paint.xform.inverted;
13524   }
13525 
13526   glnvg__xformToMat3x4(frag.paintMat[], invxform.mat[]);
13527 
13528   return true;
13529 }
13530 
13531 void glnvg__setUniforms (GLNVGcontext* gl, int uniformOffset, int image) nothrow @trusted @nogc {
13532   GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset);
13533   glnvg__setFBOClipTexture(gl, frag);
13534   glUniform4fv(gl.shader.loc[GLNVGuniformLoc.Frag], frag.UNIFORM_ARRAY_SIZE, &(frag.uniformArray.ptr[0].ptr[0]));
13535   glnvg__checkError(gl, "glnvg__setUniforms");
13536   if (image != 0) {
13537     GLNVGtexture* tex = glnvg__findTexture(gl, image);
13538     glnvg__bindTexture(gl, (tex !is null ? tex.tex : 0));
13539     glnvg__checkError(gl, "tex paint tex");
13540   } else {
13541     glnvg__bindTexture(gl, 0);
13542   }
13543 }
13544 
13545 void glnvg__finishClip (GLNVGcontext* gl, NVGClipMode clipmode) nothrow @trusted @nogc {
13546   assert(clipmode != NVGClipMode.None);
13547 
13548   // fill FBO, clear stencil buffer
13549   //TODO: optimize with bounds?
13550   version(all) {
13551     //glnvg__resetAffine(gl);
13552     //glUseProgram(gl.shaderFillFBO.prog);
13553     glDisable(GL_CULL_FACE);
13554     glDisable(GL_BLEND);
13555     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13556     glEnable(GL_STENCIL_TEST);
13557     if (gl.doClipUnion) {
13558       // for "and" we should clear everything that is NOT stencil-masked
13559       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13560       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13561     } else {
13562       glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x00, 0xff);
13563       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13564     }
13565 
13566     immutable(NVGVertex[4]) vertices =
13567      [NVGVertex(0, 0, 0, 0),
13568       NVGVertex(0, gl.fboHeight, 0, 0),
13569       NVGVertex(gl.fboWidth, gl.fboHeight, 0, 0),
13570       NVGVertex(gl.fboWidth, 0, 0, 0)];
13571 
13572     glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices);
13573     glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
13574 
13575     //glnvg__restoreAffine(gl);
13576   }
13577 
13578   glBindFramebuffer(GL_FRAMEBUFFER, gl.mainFBO);
13579   glDisable(GL_COLOR_LOGIC_OP);
13580   //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // done above
13581   glEnable(GL_BLEND);
13582   glDisable(GL_STENCIL_TEST);
13583   glEnable(GL_CULL_FACE);
13584   glUseProgram(gl.shader.prog);
13585 
13586   // set current FBO as used one
13587   assert(gl.msp > 0 && gl.fbo.ptr[gl.msp-1] > 0 && gl.fboTex.ptr[gl.msp-1].ptr[0] > 0);
13588   if (gl.lastClipFBO != gl.msp-1) {
13589     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); }
13590     gl.lastClipFBO = gl.msp-1;
13591     glActiveTexture(GL_TEXTURE1);
13592     glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
13593     glActiveTexture(GL_TEXTURE0);
13594   }
13595 }
13596 
13597 void glnvg__setClipUniforms (GLNVGcontext* gl, int uniformOffset, NVGClipMode clipmode) nothrow @trusted @nogc {
13598   assert(clipmode != NVGClipMode.None);
13599   GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset);
13600   // save uniform offset for `glnvg__finishClip()`
13601   gl.lastClipUniOfs = uniformOffset;
13602   // get FBO index, bind this FBO
13603   immutable int clipTexId = glnvg__generateFBOClipTexture(gl);
13604   assert(clipTexId >= 0);
13605   glUseProgram(gl.shaderFillFBO.prog);
13606   glnvg__checkError(gl, "use");
13607   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[clipTexId]);
13608   // set logic op for clip
13609   gl.doClipUnion = false;
13610   if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.JustCleared) {
13611     // it is cleared to zero, we can just draw a path
13612     glDisable(GL_COLOR_LOGIC_OP);
13613     gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized;
13614   } else {
13615     glEnable(GL_COLOR_LOGIC_OP);
13616     final switch (clipmode) {
13617       case NVGClipMode.None: assert(0, "wtf?!");
13618       case NVGClipMode.Union: glLogicOp(GL_CLEAR); gl.doClipUnion = true; break; // use `GL_CLEAR` to avoid adding another shader mode
13619       case NVGClipMode.Or: glLogicOp(GL_COPY); break; // GL_OR
13620       case NVGClipMode.Xor: glLogicOp(GL_XOR); break;
13621       case NVGClipMode.Sub: glLogicOp(GL_CLEAR); break;
13622       case NVGClipMode.Replace: glLogicOp(GL_COPY); break;
13623     }
13624   }
13625   // set affine matrix
13626   glUniform4fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TMat], 1, gl.lastAffine.mat.ptr);
13627   glnvg__checkError(gl, "affine 0");
13628   glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TTr], 1, gl.lastAffine.mat.ptr+4);
13629   glnvg__checkError(gl, "affine 1");
13630   // setup common OpenGL parameters
13631   glDisable(GL_BLEND);
13632   glDisable(GL_CULL_FACE);
13633   glEnable(GL_STENCIL_TEST);
13634   glnvg__stencilMask(gl, 0xff);
13635   glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13636   glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
13637   glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13638 }
13639 
13640 void glnvg__renderViewport (void* uptr, int width, int height) nothrow @trusted @nogc {
13641   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13642   gl.inFrame = true;
13643   gl.view.ptr[0] = cast(float)width;
13644   gl.view.ptr[1] = cast(float)height;
13645   // kill FBOs if we need to create new ones (flushing will recreate 'em if necessary)
13646   if (width != gl.fboWidth || height != gl.fboHeight) {
13647     glnvg__killFBOs(gl);
13648     gl.fboWidth = width;
13649     gl.fboHeight = height;
13650   }
13651   gl.msp = 1;
13652   gl.maskStack.ptr[0] = GLMaskState.DontMask;
13653   // texture cleanup
13654   import core.atomic : atomicLoad;
13655   if (atomicLoad(gl.mustCleanTextures)) {
13656     try {
13657       import core.thread : Thread;
13658       static if (__VERSION__ < 2076) {
13659         DGNoThrowNoGC(() {
13660           if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13661         })();
13662       } else {
13663         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13664       }
13665       {
13666         GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow;
13667         gl.mustCleanTextures = false;
13668         foreach (immutable tidx, ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) {
13669           // no need to use atomic ops here, as we're locked
13670           if (tex.rc == 0 && tex.tex != 0 && tex.id == 0) {
13671             version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** cleaned up texture with glid=%u\n", tex.tex); }}
13672             import core.stdc.string : memset;
13673             if ((tex.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tex.tex);
13674             memset(&tex, 0, tex.sizeof);
13675             tex.nextfree = gl.freetexid;
13676             gl.freetexid = cast(int)tidx;
13677           }
13678         }
13679       }
13680     } catch (Exception e) {}
13681   }
13682 }
13683 
13684 void glnvg__fill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13685   GLNVGpath* paths = &gl.paths[call.pathOffset];
13686   int npaths = call.pathCount;
13687 
13688   if (call.clipmode == NVGClipMode.None) {
13689     // Draw shapes
13690     glEnable(GL_STENCIL_TEST);
13691     glnvg__stencilMask(gl, 0xffU);
13692     glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xffU);
13693 
13694     glnvg__setUniforms(gl, call.uniformOffset, 0);
13695     glnvg__checkError(gl, "fill simple");
13696 
13697     glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13698     if (call.evenOdd) {
13699       //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT);
13700       //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT);
13701       glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
13702     } else {
13703       glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
13704       glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
13705     }
13706     glDisable(GL_CULL_FACE);
13707     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13708     glEnable(GL_CULL_FACE);
13709 
13710     // Draw anti-aliased pixels
13711     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13712     glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image);
13713     glnvg__checkError(gl, "fill fill");
13714 
13715     if (gl.flags&NVGContextFlag.Antialias) {
13716       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xffU);
13717       glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13718       // Draw fringes
13719       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13720     }
13721 
13722     // Draw fill
13723     glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xffU);
13724     glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13725     if (call.evenOdd) {
13726       glDisable(GL_CULL_FACE);
13727       glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount);
13728       //foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13729       glEnable(GL_CULL_FACE);
13730     } else {
13731       glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount);
13732     }
13733 
13734     glDisable(GL_STENCIL_TEST);
13735   } else {
13736     glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); // this activates our FBO
13737     glnvg__checkError(gl, "fillclip simple");
13738     glnvg__stencilFunc(gl, GL_ALWAYS, 0x00, 0xffU);
13739     if (call.evenOdd) {
13740       //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT);
13741       //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT);
13742       glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
13743     } else {
13744       glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
13745       glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
13746     }
13747     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13748     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13749   }
13750 }
13751 
13752 void glnvg__convexFill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13753   GLNVGpath* paths = &gl.paths[call.pathOffset];
13754   int npaths = call.pathCount;
13755 
13756   if (call.clipmode == NVGClipMode.None) {
13757     glnvg__setUniforms(gl, call.uniformOffset, call.image);
13758     glnvg__checkError(gl, "convex fill");
13759     if (call.evenOdd) glDisable(GL_CULL_FACE);
13760     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13761     if (gl.flags&NVGContextFlag.Antialias) {
13762       // Draw fringes
13763       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13764     }
13765     if (call.evenOdd) glEnable(GL_CULL_FACE);
13766   } else {
13767     glnvg__setClipUniforms(gl, call.uniformOffset, call.clipmode); // this activates our FBO
13768     glnvg__checkError(gl, "clip convex fill");
13769     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13770     if (gl.flags&NVGContextFlag.Antialias) {
13771       // Draw fringes
13772       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13773     }
13774     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13775   }
13776 }
13777 
13778 void glnvg__stroke (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13779   GLNVGpath* paths = &gl.paths[call.pathOffset];
13780   int npaths = call.pathCount;
13781 
13782   if (call.clipmode == NVGClipMode.None) {
13783     if (gl.flags&NVGContextFlag.StencilStrokes) {
13784       glEnable(GL_STENCIL_TEST);
13785       glnvg__stencilMask(gl, 0xff);
13786 
13787       // Fill the stroke base without overlap
13788       glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff);
13789       glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
13790       glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image);
13791       glnvg__checkError(gl, "stroke fill 0");
13792       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13793 
13794       // Draw anti-aliased pixels.
13795       glnvg__setUniforms(gl, call.uniformOffset, call.image);
13796       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13797       glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13798       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13799 
13800       // Clear stencil buffer.
13801       glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13802       glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff);
13803       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13804       glnvg__checkError(gl, "stroke fill 1");
13805       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13806       glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13807 
13808       glDisable(GL_STENCIL_TEST);
13809 
13810       //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f);
13811     } else {
13812       glnvg__setUniforms(gl, call.uniformOffset, call.image);
13813       glnvg__checkError(gl, "stroke fill");
13814       // Draw Strokes
13815       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13816     }
13817   } else {
13818     glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode);
13819     glnvg__checkError(gl, "stroke fill 0");
13820     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13821     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13822   }
13823 }
13824 
13825 void glnvg__triangles (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13826   if (call.clipmode == NVGClipMode.None) {
13827     glnvg__setUniforms(gl, call.uniformOffset, call.image);
13828     glnvg__checkError(gl, "triangles fill");
13829     glDrawArrays(GL_TRIANGLES, call.triangleOffset, call.triangleCount);
13830   } else {
13831     //TODO(?): use texture as mask?
13832   }
13833 }
13834 
13835 void glnvg__affine (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13836   glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, call.affine.mat.ptr);
13837   glnvg__checkError(gl, "affine");
13838   glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, call.affine.mat.ptr+4);
13839   glnvg__checkError(gl, "affine");
13840   //glnvg__setUniforms(gl, call.uniformOffset, call.image);
13841 }
13842 
13843 void glnvg__renderCancelInternal (GLNVGcontext* gl, bool clearTextures) nothrow @trusted @nogc {
13844   scope(exit) gl.inFrame = false;
13845   if (clearTextures && gl.inFrame) {
13846     try {
13847       import core.thread : Thread;
13848       static if (__VERSION__ < 2076) {
13849         DGNoThrowNoGC(() {
13850           if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13851         })();
13852       } else {
13853         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13854       }
13855     } catch (Exception e) {}
13856     foreach (ref GLNVGcall c; gl.calls[0..gl.ncalls]) if (c.image > 0) glnvg__deleteTexture(gl, c.image);
13857   }
13858   gl.nverts = 0;
13859   gl.npaths = 0;
13860   gl.ncalls = 0;
13861   gl.nuniforms = 0;
13862   gl.msp = 1;
13863   gl.maskStack.ptr[0] = GLMaskState.DontMask;
13864 }
13865 
13866 void glnvg__renderCancel (void* uptr) nothrow @trusted @nogc {
13867   glnvg__renderCancelInternal(cast(GLNVGcontext*)uptr, true);
13868 }
13869 
13870 GLenum glnvg_convertBlendFuncFactor (NVGBlendFactor factor) pure nothrow @trusted @nogc {
13871   if (factor == NVGBlendFactor.Zero) return GL_ZERO;
13872   if (factor == NVGBlendFactor.One) return GL_ONE;
13873   if (factor == NVGBlendFactor.SrcColor) return GL_SRC_COLOR;
13874   if (factor == NVGBlendFactor.OneMinusSrcColor) return GL_ONE_MINUS_SRC_COLOR;
13875   if (factor == NVGBlendFactor.DstColor) return GL_DST_COLOR;
13876   if (factor == NVGBlendFactor.OneMinusDstColor) return GL_ONE_MINUS_DST_COLOR;
13877   if (factor == NVGBlendFactor.SrcAlpha) return GL_SRC_ALPHA;
13878   if (factor == NVGBlendFactor.OneMinusSrcAlpha) return GL_ONE_MINUS_SRC_ALPHA;
13879   if (factor == NVGBlendFactor.DstAlpha) return GL_DST_ALPHA;
13880   if (factor == NVGBlendFactor.OneMinusDstAlpha) return GL_ONE_MINUS_DST_ALPHA;
13881   if (factor == NVGBlendFactor.SrcAlphaSaturate) return GL_SRC_ALPHA_SATURATE;
13882   return GL_INVALID_ENUM;
13883 }
13884 
13885 GLNVGblend glnvg__buildBlendFunc (NVGCompositeOperationState op) pure nothrow @trusted @nogc {
13886   GLNVGblend res;
13887   res.simple = op.simple;
13888   res.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB);
13889   res.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB);
13890   res.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha);
13891   res.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha);
13892   if (res.simple) {
13893     if (res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) {
13894       res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM;
13895     }
13896   } else {
13897     if (res.srcRGB == GL_INVALID_ENUM || res.dstRGB == GL_INVALID_ENUM || res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) {
13898       res.simple = true;
13899       res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM;
13900     }
13901   }
13902   return res;
13903 }
13904 
13905 void glnvg__blendCompositeOperation() (GLNVGcontext* gl, const scope auto ref GLNVGblend op) nothrow @trusted @nogc {
13906   //glBlendFuncSeparate(glnvg_convertBlendFuncFactor(op.srcRGB), glnvg_convertBlendFuncFactor(op.dstRGB), glnvg_convertBlendFuncFactor(op.srcAlpha), glnvg_convertBlendFuncFactor(op.dstAlpha));
13907   static if (NANOVG_GL_USE_STATE_FILTER) {
13908     if (gl.blendFunc.simple == op.simple) {
13909       if (op.simple) {
13910         if (gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return;
13911       } else {
13912         if (gl.blendFunc.srcRGB == op.srcRGB && gl.blendFunc.dstRGB == op.dstRGB && gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return;
13913       }
13914     }
13915     gl.blendFunc = op;
13916   }
13917   if (op.simple) {
13918     if (op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) {
13919       glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13920     } else {
13921       glBlendFunc(op.srcAlpha, op.dstAlpha);
13922     }
13923   } else {
13924     if (op.srcRGB == GL_INVALID_ENUM || op.dstRGB == GL_INVALID_ENUM || op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) {
13925       glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13926     } else {
13927       glBlendFuncSeparate(op.srcRGB, op.dstRGB, op.srcAlpha, op.dstAlpha);
13928     }
13929   }
13930 }
13931 
13932 void glnvg__renderSetAffine (void* uptr, const scope ref NVGMatrix mat) nothrow @trusted @nogc {
13933   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13934   GLNVGcall* call;
13935   // if last operation was GLNVG_AFFINE, simply replace the matrix
13936   if (gl.ncalls > 0 && gl.calls[gl.ncalls-1].type == GLNVG_AFFINE) {
13937     call = &gl.calls[gl.ncalls-1];
13938   } else {
13939     call = glnvg__allocCall(gl);
13940     if (call is null) return;
13941     call.type = GLNVG_AFFINE;
13942   }
13943   call.affine.mat.ptr[0..6] = mat.mat.ptr[0..6];
13944 }
13945 
13946 version(nanovega_debug_clipping) public __gshared bool nanovegaClipDebugDump = false;
13947 
13948 void glnvg__renderFlush (void* uptr) nothrow @trusted @nogc {
13949   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13950   if (!gl.inFrame) assert(0, "NanoVega: internal driver error");
13951   try {
13952     import core.thread : Thread;
13953     static if (__VERSION__ < 2076) {
13954       DGNoThrowNoGC(() {
13955         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13956       })();
13957     } else {
13958       if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13959     }
13960   } catch (Exception e) {}
13961   scope(exit) gl.inFrame = false;
13962 
13963   glnvg__resetError!true(gl);
13964   {
13965     int vv = 0;
13966     glGetIntegerv(GL_FRAMEBUFFER_BINDING, &vv);
13967     if (glGetError() || vv < 0) vv = 0;
13968     gl.mainFBO = cast(uint)vv;
13969   }
13970 
13971   enum ShaderType { None, Fill, Clip }
13972   auto lastShader = ShaderType.None;
13973   if (gl.ncalls > 0) {
13974     gl.msp = 1;
13975     gl.maskStack.ptr[0] = GLMaskState.DontMask;
13976 
13977     // Setup require GL state.
13978     glUseProgram(gl.shader.prog);
13979 
13980     glActiveTexture(GL_TEXTURE1);
13981     glBindTexture(GL_TEXTURE_2D, 0);
13982     glActiveTexture(GL_TEXTURE0);
13983     glnvg__resetFBOClipTextureCache(gl);
13984 
13985     //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13986     static if (NANOVG_GL_USE_STATE_FILTER) {
13987       gl.blendFunc.simple = true;
13988       gl.blendFunc.srcRGB = gl.blendFunc.dstRGB = gl.blendFunc.srcAlpha = gl.blendFunc.dstAlpha = GL_INVALID_ENUM;
13989     }
13990     glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // just in case
13991     glEnable(GL_CULL_FACE);
13992     glCullFace(GL_BACK);
13993     glFrontFace(GL_CCW);
13994     glEnable(GL_BLEND);
13995     glDisable(GL_DEPTH_TEST);
13996     glDisable(GL_SCISSOR_TEST);
13997     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13998     glStencilMask(0xffffffff);
13999     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
14000     glStencilFunc(GL_ALWAYS, 0, 0xffffffff);
14001     glActiveTexture(GL_TEXTURE0);
14002     glBindTexture(GL_TEXTURE_2D, 0);
14003     static if (NANOVG_GL_USE_STATE_FILTER) {
14004       gl.boundTexture = 0;
14005       gl.stencilMask = 0xffffffff;
14006       gl.stencilFunc = GL_ALWAYS;
14007       gl.stencilFuncRef = 0;
14008       gl.stencilFuncMask = 0xffffffff;
14009     }
14010     glnvg__checkError(gl, "OpenGL setup");
14011 
14012     glBindVertexArray(gl.vaoBuf);
14013     // Upload vertex data
14014     glBindBuffer(GL_ARRAY_BUFFER, gl.vertBuf);
14015     // ensure that there's space for at least 4 vertices in case we need to draw a quad (e. g. framebuffer copy)
14016     glBufferData(GL_ARRAY_BUFFER, (gl.nverts < 4 ? 4 : gl.nverts) * NVGVertex.sizeof, gl.verts, GL_STREAM_DRAW);
14017     glEnableVertexAttribArray(0);
14018     glEnableVertexAttribArray(1);
14019     glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)cast(usize)0);
14020     glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)(0+2*float.sizeof));
14021     glnvg__checkError(gl, "vertex data uploading");
14022 
14023     // Set view and texture just once per frame.
14024     glUniform1i(gl.shader.loc[GLNVGuniformLoc.Tex], 0);
14025     if (gl.shader.loc[GLNVGuniformLoc.ClipTex] != -1) {
14026       //{ import core.stdc.stdio; printf("%d\n", gl.shader.loc[GLNVGuniformLoc.ClipTex]); }
14027       glUniform1i(gl.shader.loc[GLNVGuniformLoc.ClipTex], 1);
14028     }
14029     if (gl.shader.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shader.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
14030     glnvg__checkError(gl, "render shader setup");
14031 
14032     // Reset affine transformations.
14033     glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, NVGMatrix.IdentityMat.ptr);
14034     glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, NVGMatrix.IdentityMat.ptr+4);
14035     glnvg__checkError(gl, "affine setup");
14036 
14037     // set clip shaders params
14038     // fill
14039     glUseProgram(gl.shaderFillFBO.prog);
14040     glnvg__checkError(gl, "clip shaders setup (fill 0)");
14041     if (gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
14042     glnvg__checkError(gl, "clip shaders setup (fill 1)");
14043     // copy
14044     glUseProgram(gl.shaderCopyFBO.prog);
14045     glnvg__checkError(gl, "clip shaders setup (copy 0)");
14046     if (gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
14047     glnvg__checkError(gl, "clip shaders setup (copy 1)");
14048     //glUniform1i(gl.shaderFillFBO.loc[GLNVGuniformLoc.Tex], 0);
14049     glUniform1i(gl.shaderCopyFBO.loc[GLNVGuniformLoc.Tex], 0);
14050     glnvg__checkError(gl, "clip shaders setup (copy 2)");
14051     // restore render shader
14052     glUseProgram(gl.shader.prog);
14053 
14054     //{ 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]); }
14055 
14056     gl.lastAffine.identity;
14057 
14058     foreach (int i; 0..gl.ncalls) {
14059       GLNVGcall* call = &gl.calls[i];
14060       switch (call.type) {
14061         case GLNVG_FILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__fill(gl, call); break;
14062         case GLNVG_CONVEXFILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__convexFill(gl, call); break;
14063         case GLNVG_STROKE: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__stroke(gl, call); break;
14064         case GLNVG_TRIANGLES: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__triangles(gl, call); break;
14065         case GLNVG_AFFINE: gl.lastAffine = call.affine; glnvg__affine(gl, call); break;
14066         // clip region management
14067         case GLNVG_PUSHCLIP:
14068           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]); }
14069           if (gl.msp >= gl.maskStack.length) assert(0, "NanoVega: mask stack overflow in OpenGL backend");
14070           if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.DontMask) {
14071             gl.maskStack.ptr[gl.msp++] = GLMaskState.DontMask;
14072           } else {
14073             gl.maskStack.ptr[gl.msp++] = GLMaskState.Uninitialized;
14074           }
14075           // no need to reset FBO cache here, as nothing was changed
14076           break;
14077         case GLNVG_POPCLIP:
14078           if (gl.msp <= 1) assert(0, "NanoVega: mask stack underflow in OpenGL backend");
14079           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]); }
14080           --gl.msp;
14081           assert(gl.msp > 0);
14082           //{ import core.stdc.stdio; printf("popped; new msp is %d; state is %d\n", gl.msp, gl.maskStack.ptr[gl.msp]); }
14083           // check popped item
14084           final switch (gl.maskStack.ptr[gl.msp]) {
14085             case GLMaskState.DontMask:
14086               // if last FBO was "don't mask", reset cache if current is not "don't mask"
14087               if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) {
14088                 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14089                 glnvg__resetFBOClipTextureCache(gl);
14090               }
14091               break;
14092             case GLMaskState.Uninitialized:
14093               // if last FBO texture was uninitialized, it means that nothing was changed,
14094               // so we can keep using cached FBO
14095               break;
14096             case GLMaskState.Initialized:
14097               // if last FBO was initialized, it means that something was definitely changed
14098               version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14099               glnvg__resetFBOClipTextureCache(gl);
14100               break;
14101             case GLMaskState.JustCleared: assert(0, "NanoVega: internal FBO stack error");
14102           }
14103           break;
14104         case GLNVG_RESETCLIP:
14105           // mark current mask as "don't mask"
14106           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]); }
14107           if (gl.msp > 0) {
14108             if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) {
14109               gl.maskStack.ptr[gl.msp-1] = GLMaskState.DontMask;
14110               version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14111               glnvg__resetFBOClipTextureCache(gl);
14112             }
14113           }
14114           break;
14115         case GLNVG_CLIP_DDUMP_ON:
14116           version(nanovega_debug_clipping) nanovegaClipDebugDump = true;
14117           break;
14118         case GLNVG_CLIP_DDUMP_OFF:
14119           version(nanovega_debug_clipping) nanovegaClipDebugDump = false;
14120           break;
14121         case GLNVG_NONE: break;
14122         default:
14123           {
14124             import core.stdc.stdio; stderr.fprintf("NanoVega FATAL: invalid command in OpenGL backend: %d\n", call.type);
14125           }
14126           assert(0, "NanoVega: invalid command in OpenGL backend (fatal internal error)");
14127       }
14128       // and free texture, why not
14129       glnvg__deleteTexture(gl, call.image);
14130     }
14131 
14132     glDisableVertexAttribArray(0);
14133     glDisableVertexAttribArray(1);
14134     glDisable(GL_CULL_FACE);
14135     glBindBuffer(GL_ARRAY_BUFFER, 0);
14136     glBindVertexArray(0);
14137     glUseProgram(0);
14138     glnvg__bindTexture(gl, 0);
14139   }
14140 
14141   // this will do all necessary cleanup
14142   glnvg__renderCancelInternal(gl, false); // no need to clear textures
14143 }
14144 
14145 int glnvg__maxVertCount (const(NVGpath)* paths, int npaths) nothrow @trusted @nogc {
14146   int count = 0;
14147   foreach (int i; 0..npaths) {
14148     count += paths[i].nfill;
14149     count += paths[i].nstroke;
14150   }
14151   return count;
14152 }
14153 
14154 GLNVGcall* glnvg__allocCall (GLNVGcontext* gl) nothrow @trusted @nogc {
14155   GLNVGcall* ret = null;
14156   if (gl.ncalls+1 > gl.ccalls) {
14157     GLNVGcall* calls;
14158     int ccalls = glnvg__maxi(gl.ncalls+1, 128)+gl.ccalls/2; // 1.5x Overallocate
14159     calls = cast(GLNVGcall*)realloc(gl.calls, GLNVGcall.sizeof*ccalls);
14160     if (calls is null) return null;
14161     gl.calls = calls;
14162     gl.ccalls = ccalls;
14163   }
14164   ret = &gl.calls[gl.ncalls++];
14165   memset(ret, 0, GLNVGcall.sizeof);
14166   return ret;
14167 }
14168 
14169 int glnvg__allocPaths (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14170   int ret = 0;
14171   if (gl.npaths+n > gl.cpaths) {
14172     GLNVGpath* paths;
14173     int cpaths = glnvg__maxi(gl.npaths+n, 128)+gl.cpaths/2; // 1.5x Overallocate
14174     paths = cast(GLNVGpath*)realloc(gl.paths, GLNVGpath.sizeof*cpaths);
14175     if (paths is null) return -1;
14176     gl.paths = paths;
14177     gl.cpaths = cpaths;
14178   }
14179   ret = gl.npaths;
14180   gl.npaths += n;
14181   return ret;
14182 }
14183 
14184 int glnvg__allocVerts (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14185   int ret = 0;
14186   if (gl.nverts+n > gl.cverts) {
14187     NVGVertex* verts;
14188     int cverts = glnvg__maxi(gl.nverts+n, 4096)+gl.cverts/2; // 1.5x Overallocate
14189     verts = cast(NVGVertex*)realloc(gl.verts, NVGVertex.sizeof*cverts);
14190     if (verts is null) return -1;
14191     gl.verts = verts;
14192     gl.cverts = cverts;
14193   }
14194   ret = gl.nverts;
14195   gl.nverts += n;
14196   return ret;
14197 }
14198 
14199 int glnvg__allocFragUniforms (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14200   int ret = 0, structSize = gl.fragSize;
14201   if (gl.nuniforms+n > gl.cuniforms) {
14202     ubyte* uniforms;
14203     int cuniforms = glnvg__maxi(gl.nuniforms+n, 128)+gl.cuniforms/2; // 1.5x Overallocate
14204     uniforms = cast(ubyte*)realloc(gl.uniforms, structSize*cuniforms);
14205     if (uniforms is null) return -1;
14206     gl.uniforms = uniforms;
14207     gl.cuniforms = cuniforms;
14208   }
14209   ret = gl.nuniforms*structSize;
14210   gl.nuniforms += n;
14211   return ret;
14212 }
14213 
14214 GLNVGfragUniforms* nvg__fragUniformPtr (GLNVGcontext* gl, int i) nothrow @trusted @nogc {
14215   return cast(GLNVGfragUniforms*)&gl.uniforms[i];
14216 }
14217 
14218 void glnvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc {
14219   vtx.x = x;
14220   vtx.y = y;
14221   vtx.u = u;
14222   vtx.v = v;
14223 }
14224 
14225 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 {
14226   if (npaths < 1) return;
14227 
14228   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14229   GLNVGcall* call = glnvg__allocCall(gl);
14230   NVGVertex* quad;
14231   GLNVGfragUniforms* frag;
14232   int maxverts, offset;
14233 
14234   if (call is null) return;
14235 
14236   call.type = GLNVG_FILL;
14237   call.evenOdd = evenOdd;
14238   call.clipmode = clipmode;
14239   //if (clipmode != NVGClipMode.None) { import core.stdc.stdio; printf("CLIP!\n"); }
14240   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14241   call.triangleCount = 4;
14242   call.pathOffset = glnvg__allocPaths(gl, npaths);
14243   if (call.pathOffset == -1) goto error;
14244   call.pathCount = npaths;
14245   call.image = paint.image.id;
14246   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14247 
14248   if (npaths == 1 && paths[0].convex) {
14249     call.type = GLNVG_CONVEXFILL;
14250     call.triangleCount = 0; // Bounding box fill quad not needed for convex fill
14251   }
14252 
14253   // Allocate vertices for all the paths.
14254   maxverts = glnvg__maxVertCount(paths, npaths)+call.triangleCount;
14255   offset = glnvg__allocVerts(gl, maxverts);
14256   if (offset == -1) goto error;
14257 
14258   foreach (int i; 0..npaths) {
14259     GLNVGpath* copy = &gl.paths[call.pathOffset+i];
14260     const(NVGpath)* path = &paths[i];
14261     memset(copy, 0, GLNVGpath.sizeof);
14262     if (path.nfill > 0) {
14263       copy.fillOffset = offset;
14264       copy.fillCount = path.nfill;
14265       memcpy(&gl.verts[offset], path.fill, NVGVertex.sizeof*path.nfill);
14266       offset += path.nfill;
14267     }
14268     if (path.nstroke > 0) {
14269       copy.strokeOffset = offset;
14270       copy.strokeCount = path.nstroke;
14271       memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke);
14272       offset += path.nstroke;
14273     }
14274   }
14275 
14276   // Setup uniforms for draw calls
14277   if (call.type == GLNVG_FILL) {
14278     import core.stdc.string : memcpy;
14279     // Quad
14280     call.triangleOffset = offset;
14281     quad = &gl.verts[call.triangleOffset];
14282     glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f);
14283     glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f);
14284     glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f);
14285     glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f);
14286     // Get uniform
14287     call.uniformOffset = glnvg__allocFragUniforms(gl, 2);
14288     if (call.uniformOffset == -1) goto error;
14289     // Simple shader for stencil
14290     frag = nvg__fragUniformPtr(gl, call.uniformOffset);
14291     memset(frag, 0, (*frag).sizeof);
14292     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f);
14293     memcpy(nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), frag, (*frag).sizeof);
14294     frag.strokeThr = -1.0f;
14295     frag.type = NSVG_SHADER_SIMPLE;
14296     // Fill shader
14297     //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, fringe, fringe, -1.0f);
14298   } else {
14299     call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14300     if (call.uniformOffset == -1) goto error;
14301     // Fill shader
14302     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f);
14303   }
14304 
14305   return;
14306 
14307 error:
14308   // We get here if call alloc was ok, but something else is not.
14309   // Roll back the last call to prevent drawing it.
14310   if (gl.ncalls > 0) --gl.ncalls;
14311 }
14312 
14313 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 {
14314   if (npaths < 1) return;
14315 
14316   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14317   GLNVGcall* call = glnvg__allocCall(gl);
14318   int maxverts, offset;
14319 
14320   if (call is null) return;
14321 
14322   call.type = GLNVG_STROKE;
14323   call.clipmode = clipmode;
14324   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14325   call.pathOffset = glnvg__allocPaths(gl, npaths);
14326   if (call.pathOffset == -1) goto error;
14327   call.pathCount = npaths;
14328   call.image = paint.image.id;
14329   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14330 
14331   // Allocate vertices for all the paths.
14332   maxverts = glnvg__maxVertCount(paths, npaths);
14333   offset = glnvg__allocVerts(gl, maxverts);
14334   if (offset == -1) goto error;
14335 
14336   foreach (int i; 0..npaths) {
14337     GLNVGpath* copy = &gl.paths[call.pathOffset+i];
14338     const(NVGpath)* path = &paths[i];
14339     memset(copy, 0, GLNVGpath.sizeof);
14340     if (path.nstroke) {
14341       copy.strokeOffset = offset;
14342       copy.strokeCount = path.nstroke;
14343       memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke);
14344       offset += path.nstroke;
14345     }
14346   }
14347 
14348   if (gl.flags&NVGContextFlag.StencilStrokes) {
14349     // Fill shader
14350     call.uniformOffset = glnvg__allocFragUniforms(gl, 2);
14351     if (call.uniformOffset == -1) goto error;
14352     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
14353     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f);
14354   } else {
14355     // Fill shader
14356     call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14357     if (call.uniformOffset == -1) goto error;
14358     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
14359   }
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__renderTriangles (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc {
14370   if (nverts < 1) return;
14371 
14372   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14373   GLNVGcall* call = glnvg__allocCall(gl);
14374   GLNVGfragUniforms* frag;
14375 
14376   if (call is null) return;
14377 
14378   call.type = GLNVG_TRIANGLES;
14379   call.clipmode = clipmode;
14380   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14381   call.image = paint.image.id;
14382   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14383 
14384   // Allocate vertices for all the paths.
14385   call.triangleOffset = glnvg__allocVerts(gl, nverts);
14386   if (call.triangleOffset == -1) goto error;
14387   call.triangleCount = nverts;
14388 
14389   memcpy(&gl.verts[call.triangleOffset], verts, NVGVertex.sizeof*nverts);
14390 
14391   // Fill shader
14392   call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14393   if (call.uniformOffset == -1) goto error;
14394   frag = nvg__fragUniformPtr(gl, call.uniformOffset);
14395   glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f);
14396   frag.type = NSVG_SHADER_IMG;
14397 
14398   return;
14399 
14400 error:
14401   // We get here if call alloc was ok, but something else is not.
14402   // Roll back the last call to prevent drawing it.
14403   if (gl.ncalls > 0) --gl.ncalls;
14404 }
14405 
14406 void glnvg__renderDelete (void* uptr) nothrow @trusted @nogc {
14407   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14408   if (gl is null) return;
14409 
14410   glnvg__killFBOs(gl);
14411   glnvg__deleteShader(&gl.shader);
14412   glnvg__deleteShader(&gl.shaderFillFBO);
14413   glnvg__deleteShader(&gl.shaderCopyFBO);
14414 
14415   if (gl.vaoBuf != 0) glDeleteVertexArrays(1, &gl.vaoBuf);
14416   if (gl.vertBuf != 0) glDeleteBuffers(1, &gl.vertBuf);
14417 
14418   foreach (ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) {
14419     if (tex.id != 0 && (tex.flags&NVGImageFlag.NoDelete) == 0) {
14420       assert(tex.tex != 0);
14421       glDeleteTextures(1, &tex.tex);
14422     }
14423   }
14424   free(gl.textures);
14425 
14426   free(gl.paths);
14427   free(gl.verts);
14428   free(gl.uniforms);
14429   free(gl.calls);
14430 
14431   free(gl);
14432 }
14433 
14434 
14435 /** Creates NanoVega contexts for OpenGL2+.
14436  *
14437  * Specify creation flags as additional arguments, like this:
14438  * `nvgCreateContext(NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes);`
14439  *
14440  * If you won't specify any flags, defaults will be used:
14441  * `[NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes]`.
14442  *
14443  * Group: context_management
14444  */
14445 public NVGContext nvgCreateContext (const(NVGContextFlag)[] flagList...) nothrow @trusted @nogc {
14446   version(aliced) {
14447     enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA;
14448   } else {
14449     enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes;
14450   }
14451   uint flags = 0;
14452   if (flagList.length != 0) {
14453     foreach (immutable flg; flagList) flags |= (flg != NVGContextFlag.Default ? flg : DefaultFlags);
14454   } else {
14455     flags = DefaultFlags;
14456   }
14457   NVGparams params = void;
14458   NVGContext ctx = null;
14459   version(nanovg_builtin_opengl_bindings) nanovgInitOpenGL(); // why not?
14460   version(nanovg_bindbc_opengl_bindings) nanovgInitOpenGL();
14461   GLNVGcontext* gl = cast(GLNVGcontext*)malloc(GLNVGcontext.sizeof);
14462   if (gl is null) goto error;
14463   memset(gl, 0, GLNVGcontext.sizeof);
14464 
14465   memset(&params, 0, params.sizeof);
14466   params.renderCreate = &glnvg__renderCreate;
14467   params.renderCreateTexture = &glnvg__renderCreateTexture;
14468   params.renderTextureIncRef = &glnvg__renderTextureIncRef;
14469   params.renderDeleteTexture = &glnvg__renderDeleteTexture;
14470   params.renderUpdateTexture = &glnvg__renderUpdateTexture;
14471   params.renderGetTextureSize = &glnvg__renderGetTextureSize;
14472   params.renderViewport = &glnvg__renderViewport;
14473   params.renderCancel = &glnvg__renderCancel;
14474   params.renderFlush = &glnvg__renderFlush;
14475   params.renderPushClip = &glnvg__renderPushClip;
14476   params.renderPopClip = &glnvg__renderPopClip;
14477   params.renderResetClip = &glnvg__renderResetClip;
14478   params.renderFill = &glnvg__renderFill;
14479   params.renderStroke = &glnvg__renderStroke;
14480   params.renderTriangles = &glnvg__renderTriangles;
14481   params.renderSetAffine = &glnvg__renderSetAffine;
14482   params.renderDelete = &glnvg__renderDelete;
14483   params.userPtr = gl;
14484   params.edgeAntiAlias = (flags&NVGContextFlag.Antialias ? true : false);
14485   if (flags&(NVGContextFlag.FontAA|NVGContextFlag.FontNoAA)) {
14486     params.fontAA = (flags&NVGContextFlag.FontNoAA ? NVG_INVERT_FONT_AA : !NVG_INVERT_FONT_AA);
14487   } else {
14488     params.fontAA = NVG_INVERT_FONT_AA;
14489   }
14490 
14491   gl.flags = flags;
14492   gl.freetexid = -1;
14493 
14494   ctx = createInternal(&params);
14495   if (ctx is null) goto error;
14496 
14497   static if (__VERSION__ < 2076) {
14498     DGNoThrowNoGC(() { import core.thread; gl.mainTID = Thread.getThis.id; })();
14499   } else {
14500     try { import core.thread; gl.mainTID = Thread.getThis.id; } catch (Exception e) {}
14501   }
14502 
14503   return ctx;
14504 
14505 error:
14506   // 'gl' is freed by nvgDeleteInternal.
14507   if (ctx !is null) ctx.deleteInternal();
14508   return null;
14509 }
14510 
14511 /// Using  OpenGL texture id creates GLNVGtexture and return its id.
14512 /// Group: images
14513 public int glCreateImageFromHandleGL2 (NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc {
14514   GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr;
14515   GLNVGtexture* tex = glnvg__allocTexture(gl);
14516 
14517   if (tex is null) return 0;
14518 
14519   tex.type = NVGtexture.RGBA;
14520   tex.tex = textureId;
14521   tex.flags = imageFlags;
14522   tex.width = w;
14523   tex.height = h;
14524 
14525   return tex.id;
14526 }
14527 
14528 /// Create NVGImage from OpenGL texture id.
14529 /// Group: images
14530 public NVGImage glCreateImageFromOpenGLTexture(NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc {
14531   auto id = glCreateImageFromHandleGL2(ctx, textureId, w, h, imageFlags);
14532 
14533   NVGImage res;
14534   if (id > 0) {
14535     res.id = id;
14536     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); }
14537     res.ctx = ctx;
14538     ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount
14539   }
14540   return res;
14541 }
14542 
14543 /// Returns OpenGL texture id for NanoVega image.
14544 /// Group: images
14545 public GLuint glImageHandleGL2 (NVGContext ctx, int image) nothrow @trusted @nogc {
14546   GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr;
14547   GLNVGtexture* tex = glnvg__findTexture(gl, image);
14548   return tex.tex;
14549 }
14550 
14551 
14552 // ////////////////////////////////////////////////////////////////////////// //
14553 private:
14554 
14555 static if (NanoVegaHasFontConfig) {
14556   version(nanovg_builtin_fontconfig_bindings) {
14557     pragma(lib, "fontconfig");
14558 
14559     private extern(C) nothrow @trusted @nogc {
14560       enum FC_FILE = "file"; /* String */
14561       alias FcBool = int;
14562       alias FcChar8 = char;
14563       struct FcConfig;
14564       struct FcPattern;
14565       alias FcMatchKind = int;
14566       enum : FcMatchKind {
14567         FcMatchPattern,
14568         FcMatchFont,
14569         FcMatchScan
14570       }
14571       alias FcResult = int;
14572       enum : FcResult {
14573         FcResultMatch,
14574         FcResultNoMatch,
14575         FcResultTypeMismatch,
14576         FcResultNoId,
14577         FcResultOutOfMemory
14578       }
14579       FcBool FcInit ();
14580       FcBool FcConfigSubstituteWithPat (FcConfig* config, FcPattern* p, FcPattern* p_pat, FcMatchKind kind);
14581       void FcDefaultSubstitute (FcPattern* pattern);
14582       FcBool FcConfigSubstitute (FcConfig* config, FcPattern* p, FcMatchKind kind);
14583       FcPattern* FcFontMatch (FcConfig* config, FcPattern* p, FcResult* result);
14584       FcPattern* FcNameParse (const(FcChar8)* name);
14585       void FcPatternDestroy (FcPattern* p);
14586       FcResult FcPatternGetString (const(FcPattern)* p, const(char)* object, int n, FcChar8** s);
14587     }
14588   }
14589 
14590   __gshared bool fontconfigAvailable = false;
14591   // initialize fontconfig
14592   shared static this () {
14593     if (FcInit()) {
14594       fontconfigAvailable = true;
14595     } else {
14596       import core.stdc.stdio : stderr, fprintf;
14597       stderr.fprintf("***NanoVega WARNING: cannot init fontconfig!\n");
14598     }
14599   }
14600 }
14601 
14602 
14603 // ////////////////////////////////////////////////////////////////////////// //
14604 public enum BaphometDims = 512.0f; // baphomet icon is 512x512 ([0..511])
14605 
14606 private static immutable ubyte[7641] baphometPath = [
14607   0x01,0x04,0x06,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x08,0xa0,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,
14608   0x00,0x80,0xff,0x43,0xa2,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x08,0x00,0x80,0xff,
14609   0x43,0x7a,0x89,0xe5,0x42,0xa0,0x1d,0xc6,0x43,0x00,0x00,0x00,0x00,0x30,0x89,0x7f,0x43,0x00,0x00,0x00,
14610   0x00,0x08,0x7a,0x89,0xe5,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7a,0x89,0xe5,0x42,0x00,0x00,
14611   0x00,0x00,0x30,0x89,0x7f,0x43,0x08,0x00,0x00,0x00,0x00,0xa2,0x1d,0xc6,0x43,0x7a,0x89,0xe5,0x42,0x00,
14612   0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x09,0x06,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd,
14613   0x43,0x08,0x16,0x68,0xb3,0x43,0x72,0x87,0xdd,0x43,0x71,0x87,0xdd,0x43,0x17,0x68,0xb3,0x43,0x71,0x87,
14614   0xdd,0x43,0x30,0x89,0x7f,0x43,0x08,0x71,0x87,0xdd,0x43,0xd2,0x2f,0x18,0x43,0x16,0x68,0xb3,0x43,0x35,
14615   0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x35,0xe2,0x87,0x42,0x08,0xd1,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,
14616   0x35,0xe2,0x87,0x42,0xd2,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x08,0x35,0xe2,0x87,
14617   0x42,0x17,0x68,0xb3,0x43,0xd1,0x2f,0x18,0x43,0x72,0x87,0xdd,0x43,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd,
14618   0x43,0x09,0x06,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x07,0xa4,0x3f,0x7f,0x43,0x0b,0x86,0xdc,0x43,
14619   0x07,0x6c,0xb9,0xb2,0x43,0xe8,0xd1,0xca,0x42,0x07,0x6e,0x4d,0xa0,0x42,0xa9,0x10,0x9c,0x43,0x07,0xb7,
14620   0x40,0xd7,0x43,0xa9,0x10,0x9c,0x43,0x07,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x09,0x06,0x98,0x42,
14621   0x74,0x43,0xb1,0x8d,0x68,0x43,0x08,0xd7,0x24,0x79,0x43,0xba,0x83,0x6e,0x43,0xa9,0x16,0x7c,0x43,0x56,
14622   0xa1,0x76,0x43,0x74,0x2a,0x7d,0x43,0x44,0x73,0x80,0x43,0x08,0x55,0xd1,0x7e,0x43,0xe3,0xea,0x76,0x43,
14623   0xbc,0x18,0x81,0x43,0x7f,0xa8,0x6e,0x43,0x8f,0x0a,0x84,0x43,0x02,0xfc,0x68,0x43,0x09,0x06,0x92,0x29,
14624   0x8d,0x43,0x73,0xc3,0x67,0x43,0x08,0xa4,0xd9,0x8e,0x43,0xf2,0xa6,0x7a,0x43,0x8f,0x22,0x88,0x43,0x75,
14625   0x2a,0x7d,0x43,0x42,0x7f,0x82,0x43,0x08,0xc8,0x88,0x43,0x09,0x06,0xc1,0x79,0x74,0x43,0x50,0x64,0x89,
14626   0x43,0x08,0x68,0x2d,0x72,0x43,0xee,0x21,0x81,0x43,0xcd,0x97,0x55,0x43,0xe6,0xf1,0x7b,0x43,0x91,0xec,
14627   0x5d,0x43,0xa8,0xc7,0x6a,0x43,0x09,0x06,0xfa,0xa5,0x52,0x43,0x60,0x97,0x7c,0x43,0x08,0x19,0xff,0x50,
14628   0x43,0xe9,0x6e,0x8a,0x43,0xb0,0xbd,0x70,0x43,0x4c,0x51,0x82,0x43,0x04,0xeb,0x69,0x43,0x66,0x0f,0x8e,
14629   0x43,0x09,0x06,0x17,0xbf,0x71,0x43,0x2c,0x58,0x94,0x43,0x08,0x1c,0x96,0x6e,0x43,0x61,0x68,0x99,0x43,
14630   0x2d,0x3a,0x6e,0x43,0xc8,0x81,0x9e,0x43,0xb7,0x9b,0x72,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0x30,0xdb,
14631   0x82,0x43,0xdb,0xe9,0x93,0x43,0x08,0x11,0x82,0x84,0x43,0x61,0x68,0x99,0x43,0xe8,0x4a,0x84,0x43,0x8e,
14632   0xa6,0x9e,0x43,0x42,0x7f,0x82,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0xc4,0x02,0x85,0x43,0xd1,0x0b,0x92,
14633   0x43,0x08,0xd6,0xb2,0x86,0x43,0x34,0x1e,0x92,0x43,0x4f,0x58,0x87,0x43,0xa4,0xf1,0x92,0x43,0x03,0xd9,
14634   0x87,0x43,0x7b,0xc6,0x94,0x43,0x09,0x06,0x87,0x3e,0x64,0x43,0x31,0x3b,0x93,0x43,0x08,0x3b,0xbf,0x64,
14635   0x43,0x6f,0xf9,0x91,0x43,0x96,0x0b,0x67,0x43,0xc5,0x4a,0x91,0x43,0xcf,0xfe,0x6a,0x43,0x31,0x2f,0x91,
14636   0x43,0x09,0x06,0x16,0x74,0xb5,0x43,0x08,0xec,0x8e,0x43,0x08,0x1b,0x4b,0xb2,0x43,0xee,0x5d,0x8b,0x43,
14637   0x48,0x4d,0xad,0x43,0x12,0xa6,0x8a,0x43,0xf3,0xd7,0xa7,0x43,0x74,0xb8,0x8a,0x43,0x08,0x8c,0xb2,0xa0,
14638   0x43,0xcd,0xf8,0x8a,0x43,0x68,0x46,0x9b,0x43,0x79,0x8f,0x87,0x43,0x49,0xc9,0x96,0x43,0xe9,0x3e,0x82,
14639   0x43,0x08,0x60,0x5c,0x97,0x43,0xa1,0xde,0x8b,0x43,0x4e,0xa0,0x93,0x43,0x31,0x3b,0x93,0x43,0x9f,0xea,
14640   0x8d,0x43,0x27,0x8d,0x99,0x43,0x08,0x07,0xe0,0x8c,0x43,0x06,0x34,0x9b,0x43,0x38,0xe9,0x8c,0x43,0x46,
14641   0x0a,0x9e,0x43,0x3d,0xcc,0x8b,0x43,0xb2,0x06,0xa2,0x43,0x08,0xf1,0x40,0x8a,0x43,0xb0,0x12,0xa4,0x43,
14642   0x39,0xd1,0x88,0x43,0x76,0x43,0xa6,0x43,0xfa,0x06,0x88,0x43,0xa4,0x75,0xa9,0x43,0x08,0x19,0x6c,0x88,
14643   0x43,0x9f,0x9e,0xac,0x43,0x66,0xeb,0x87,0x43,0x44,0x76,0xb0,0x43,0x6b,0xce,0x86,0x43,0x3b,0xbc,0xb4,
14644   0x43,0x08,0xa9,0x8c,0x85,0x43,0x06,0xd0,0xb5,0x43,0xfa,0xee,0x83,0x43,0x74,0xa3,0xb6,0x43,0x3d,0x90,
14645   0x81,0x43,0x31,0xf6,0xb6,0x43,0x08,0x9d,0x61,0x7d,0x43,0xee,0x48,0xb7,0x43,0x3b,0x1f,0x75,0x43,0xcf,
14646   0xe3,0xb6,0x43,0xee,0x6f,0x6d,0x43,0x68,0xe2,0xb5,0x43,0x08,0xd4,0xed,0x6b,0x43,0x87,0x2f,0xb2,0x43,
14647   0x0e,0xc9,0x6b,0x43,0xa7,0x7c,0xae,0x43,0x98,0xfa,0x67,0x43,0xab,0x53,0xab,0x43,0x08,0x25,0x2c,0x64,
14648   0x43,0x33,0xa2,0xa8,0x43,0x40,0x96,0x61,0x43,0xc3,0xc2,0xa5,0x43,0x64,0xde,0x60,0x43,0xfa,0xa2,0xa2,
14649   0x43,0x08,0xb0,0x5d,0x60,0x43,0x06,0x4c,0x9f,0x43,0x9a,0xca,0x5f,0x43,0x38,0x3d,0x9b,0x43,0x3b,0x8f,
14650   0x5c,0x43,0x85,0xb0,0x98,0x43,0x08,0x42,0x36,0x51,0x43,0x3d,0xf0,0x91,0x43,0xcd,0x4f,0x49,0x43,0xdb,
14651   0xb9,0x8b,0x43,0xe0,0xdb,0x44,0x43,0x42,0x8b,0x84,0x43,0x08,0x7e,0xc9,0x44,0x43,0x8a,0x57,0x8d,0x43,
14652   0xbc,0x6c,0x0f,0x43,0x23,0x62,0x8e,0x43,0xf5,0x17,0x07,0x43,0xc5,0x3e,0x8f,0x43,0x09,0x06,0xe0,0xea,
14653   0x76,0x43,0xab,0xef,0xc5,0x43,0x08,0x12,0x00,0x79,0x43,0xab,0xcb,0xbf,0x43,0x79,0xb9,0x6d,0x43,0x7e,
14654   0x8d,0xba,0x43,0xee,0x6f,0x6d,0x43,0x98,0xeb,0xb5,0x43,0x08,0xe0,0x02,0x7b,0x43,0x5f,0x1c,0xb8,0x43,
14655   0x85,0x2c,0x82,0x43,0xe9,0x65,0xb8,0x43,0xd6,0xb2,0x86,0x43,0xc6,0x05,0xb5,0x43,0x08,0x03,0xcd,0x85,
14656   0x43,0x5a,0x39,0xb9,0x43,0xe4,0x4f,0x81,0x43,0xdb,0xd4,0xbf,0x43,0xdf,0x6c,0x82,0x43,0xbc,0x93,0xc5,
14657   0x43,0x09,0x06,0xf0,0xd0,0x22,0x43,0x5d,0x19,0x08,0x43,0x08,0xbc,0xab,0x49,0x43,0x4a,0x35,0x29,0x43,
14658   0xcb,0xf7,0x65,0x43,0xce,0x37,0x45,0x43,0x0e,0x99,0x63,0x43,0x67,0xc6,0x5c,0x43,0x09,0x06,0x05,0x94,
14659   0xab,0x43,0xc2,0x13,0x04,0x43,0x08,0x9f,0x26,0x98,0x43,0x11,0x42,0x25,0x43,0x97,0x00,0x8a,0x43,0x32,
14660   0x32,0x41,0x43,0xf5,0x2f,0x8b,0x43,0xc7,0xc0,0x58,0x43,0x09,0x06,0x8f,0x85,0x48,0x43,0xe0,0xa8,0x8c,
14661   0x43,0x08,0x55,0xaa,0x48,0x43,0xe0,0xa8,0x8c,0x43,0x6b,0x3d,0x49,0x43,0xc1,0x43,0x8c,0x43,0x31,0x62,
14662   0x49,0x43,0xc1,0x43,0x8c,0x43,0x08,0x2f,0xe3,0x2f,0x43,0xad,0xe7,0x98,0x43,0xff,0x0d,0x0d,0x43,0xad,
14663   0xf3,0x9a,0x43,0xf0,0xaf,0xcc,0x42,0x74,0x00,0x97,0x43,0x08,0xbb,0xa2,0xf7,0x42,0x93,0x4d,0x93,0x43,
14664   0x5e,0x19,0x08,0x43,0x5a,0x2a,0x87,0x43,0x23,0x6e,0x10,0x43,0x42,0x97,0x86,0x43,0x08,0xca,0xe8,0x33,
14665   0x43,0x1b,0x3c,0x80,0x43,0x80,0xe8,0x4d,0x43,0xda,0xf4,0x70,0x43,0xae,0x0e,0x4f,0x43,0x2b,0x1b,0x65,
14666   0x43,0x08,0x66,0x96,0x54,0x43,0xa3,0xe1,0x3b,0x43,0x4e,0xc4,0x19,0x43,0xa0,0x1a,0x16,0x43,0x10,0xe2,
14667   0x14,0x43,0x26,0x14,0xe0,0x42,0x08,0x5c,0x91,0x1c,0x43,0xcb,0x27,0xee,0x42,0xa9,0x40,0x24,0x43,0x71,
14668   0x3b,0xfc,0x42,0xf3,0xef,0x2b,0x43,0x8b,0x27,0x05,0x43,0x08,0xe2,0x4b,0x2c,0x43,0x48,0x86,0x07,0x43,
14669   0x79,0x62,0x2f,0x43,0x05,0xe5,0x09,0x43,0x55,0x32,0x34,0x43,0xa0,0xd2,0x09,0x43,0x08,0x74,0xa3,0x36,
14670   0x43,0x3a,0xd1,0x08,0x43,0x7e,0x81,0x38,0x43,0x09,0xd4,0x0a,0x43,0x0d,0xba,0x39,0x43,0xa0,0xea,0x0d,
14671   0x43,0x08,0x6f,0xe4,0x3d,0x43,0x43,0xc7,0x0e,0x43,0xd6,0xe5,0x3e,0x43,0xc4,0x4a,0x11,0x43,0x55,0x7a,
14672   0x40,0x43,0x59,0x72,0x13,0x43,0x08,0x55,0x92,0x44,0x43,0xbf,0x73,0x14,0x43,0x23,0x95,0x46,0x43,0xa5,
14673   0x09,0x17,0x43,0xe0,0xf3,0x48,0x43,0xfe,0x55,0x19,0x43,0x08,0xcd,0x4f,0x49,0x43,0xaa,0x10,0x1c,0x43,
14674   0x61,0x77,0x4b,0x43,0xfe,0x6d,0x1d,0x43,0x80,0xe8,0x4d,0x43,0x2b,0x94,0x1e,0x43,0x08,0x58,0xc9,0x51,
14675   0x43,0x41,0x27,0x1f,0x43,0x9b,0x82,0x53,0x43,0x35,0x72,0x20,0x43,0x53,0xf2,0x54,0x43,0x88,0xcf,0x21,
14676   0x43,0x08,0x7b,0x29,0x55,0x43,0xe8,0x0a,0x25,0x43,0xb2,0x2d,0x58,0x43,0xef,0xe8,0x26,0x43,0x9b,0xb2,
14677   0x5b,0x43,0xd0,0x8f,0x28,0x43,0x08,0x5f,0xef,0x5f,0x43,0xeb,0x11,0x2a,0x43,0xfd,0xdc,0x5f,0x43,0x6e,
14678   0x95,0x2c,0x43,0x3b,0xa7,0x60,0x43,0x2b,0xf4,0x2e,0x43,0x08,0x06,0xbb,0x61,0x43,0xfd,0xe5,0x31,0x43,
14679   0xe7,0x61,0x63,0x43,0xef,0x30,0x33,0x43,0x53,0x52,0x65,0x43,0xa3,0xb1,0x33,0x43,0x08,0x12,0xa0,0x68,
14680   0x43,0x7f,0x69,0x34,0x43,0x40,0xc6,0x69,0x43,0x64,0xff,0x36,0x43,0x7e,0x90,0x6a,0x43,0x71,0xcc,0x39,
14681   0x43,0x08,0xbc,0x5a,0x6b,0x43,0x51,0x73,0x3b,0x43,0xc1,0x49,0x6c,0x43,0xa5,0xd0,0x3c,0x43,0xe0,0xba,
14682   0x6e,0x43,0xb8,0x74,0x3c,0x43,0x08,0x6b,0x1c,0x73,0x43,0x13,0xc1,0x3e,0x43,0x40,0xf6,0x71,0x43,0xce,
14683   0x1f,0x41,0x43,0x55,0x89,0x72,0x43,0x8d,0x7e,0x43,0x43,0x08,0x68,0x2d,0x72,0x43,0x89,0xae,0x4b,0x43,
14684   0xc1,0x79,0x74,0x43,0xcb,0x78,0x4c,0x43,0x55,0xa1,0x76,0x43,0x5b,0xb1,0x4d,0x43,0x08,0xa2,0x38,0x7a,
14685   0x43,0xd1,0x56,0x4e,0x43,0x85,0xb6,0x78,0x43,0xb1,0x15,0x54,0x43,0x83,0xc7,0x77,0x43,0x89,0x0e,0x5c,
14686   0x43,0x08,0xcf,0x46,0x77,0x43,0x0f,0x81,0x5f,0x43,0x1a,0xde,0x7a,0x43,0xce,0xc7,0x5d,0x43,0x42,0x73,
14687   0x80,0x43,0x99,0xc3,0x5a,0x43,0x08,0x85,0x2c,0x82,0x43,0xf6,0xe6,0x59,0x43,0x81,0x3d,0x81,0x43,0x16,
14688   0x10,0x50,0x43,0xd6,0x8e,0x80,0x43,0x5b,0x99,0x49,0x43,0x08,0xc4,0xea,0x80,0x43,0x22,0x95,0x46,0x43,
14689   0xfa,0xe2,0x81,0x43,0xda,0xec,0x43,0x43,0x78,0x77,0x83,0x43,0xe4,0xb2,0x41,0x43,0x08,0x8a,0x27,0x85,
14690   0x43,0x86,0x77,0x3e,0x43,0x0c,0x9f,0x85,0x43,0x07,0xf4,0x3b,0x43,0x8f,0x16,0x86,0x43,0xe6,0x82,0x39,
14691   0x43,0x08,0x85,0x44,0x86,0x43,0x37,0xd9,0x35,0x43,0x1e,0x4f,0x87,0x43,0xe1,0x7b,0x34,0x43,0xdf,0x90,
14692   0x88,0x43,0xb6,0x55,0x33,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0xe5,0x31,0x43,0xfa,0x12,0x8a,0x43,0xbf,
14693   0x03,0x2d,0x43,0x19,0x78,0x8a,0x43,0x45,0x5e,0x2c,0x43,0x08,0x03,0xf1,0x8b,0x43,0xac,0x47,0x29,0x43,
14694   0x2f,0x17,0x8d,0x43,0x45,0x46,0x28,0x43,0xc8,0x21,0x8e,0x43,0x30,0xb3,0x27,0x43,0x08,0xa9,0xc8,0x8f,
14695   0x43,0xef,0xe8,0x26,0x43,0xbf,0x5b,0x90,0x43,0x5b,0xc1,0x24,0x43,0x10,0xca,0x90,0x43,0xa0,0x62,0x22,
14696   0x43,0x08,0x26,0x5d,0x91,0x43,0xbb,0xcc,0x1f,0x43,0xf0,0x70,0x92,0x43,0x78,0x13,0x1e,0x43,0x77,0xd7,
14697   0x93,0x43,0x73,0x24,0x1d,0x43,0x08,0x65,0x3f,0x96,0x43,0xce,0x58,0x1b,0x43,0xbe,0x7f,0x96,0x43,0xbf,
14698   0x8b,0x18,0x43,0x60,0x5c,0x97,0x43,0xb6,0xad,0x16,0x43,0x08,0xba,0xa8,0x99,0x43,0x78,0xcb,0x11,0x43,
14699   0x49,0xe1,0x9a,0x43,0x78,0xcb,0x11,0x43,0x01,0x51,0x9c,0x43,0x73,0xdc,0x10,0x43,0x08,0x72,0x24,0x9d,
14700   0x43,0xd2,0xff,0x0f,0x43,0x1c,0xd3,0x9d,0x43,0x07,0xec,0x0e,0x43,0xeb,0xc9,0x9d,0x43,0xe8,0x7a,0x0c,
14701   0x43,0x08,0x60,0x80,0x9d,0x43,0xd7,0xbe,0x08,0x43,0x4d,0xe8,0x9f,0x43,0x86,0x50,0x08,0x43,0x25,0xbd,
14702   0xa1,0x43,0x5b,0x2a,0x07,0x43,0x08,0x99,0x7f,0xa3,0x43,0xc9,0xf1,0x05,0x43,0x48,0x1d,0xa5,0x43,0x86,
14703   0x38,0x04,0x43,0x6c,0x71,0xa6,0x43,0x18,0x59,0x01,0x43,0x08,0x32,0x96,0xa6,0x43,0x6e,0x64,0xff,0x42,
14704   0x48,0x29,0xa7,0x43,0xed,0xcf,0xfd,0x42,0x5f,0xbc,0xa7,0x43,0x71,0x3b,0xfc,0x42,0x08,0xf3,0xe3,0xa9,
14705   0x43,0xf7,0x7d,0xf7,0x42,0xd8,0x6d,0xaa,0x43,0x45,0xe5,0xf2,0x42,0x48,0x41,0xab,0x43,0xcb,0x27,0xee,
14706   0x42,0x08,0x24,0xf9,0xab,0x43,0x52,0x6a,0xe9,0x42,0xee,0x0c,0xad,0x43,0x4c,0x8c,0xe7,0x42,0x1b,0x33,
14707   0xae,0x43,0xcc,0xf7,0xe5,0x42,0x08,0xaa,0x6b,0xaf,0x43,0xe8,0x61,0xe3,0x42,0x90,0xf5,0xaf,0x43,0xc9,
14708   0xf0,0xe0,0x42,0xe0,0x63,0xb0,0x43,0xe5,0x5a,0xde,0x42,0x08,0xaa,0x83,0xb3,0x43,0x29,0x2d,0x09,0x43,
14709   0x6a,0xfe,0x8e,0x43,0xb8,0x74,0x3c,0x43,0xd5,0x06,0x95,0x43,0xe6,0x79,0x67,0x43,0x08,0x2f,0x53,0x97,
14710   0x43,0xe9,0xb0,0x74,0x43,0xa8,0x28,0xa0,0x43,0x43,0xfd,0x76,0x43,0x83,0x28,0xad,0x43,0x17,0x59,0x81,
14711   0x43,0x08,0x3d,0xe7,0xbf,0x43,0x4b,0x8d,0x8c,0x43,0xae,0x96,0xba,0x43,0x66,0x27,0x92,0x43,0x15,0xe0,
14712   0xc7,0x43,0x6f,0x11,0x96,0x43,0x08,0x7e,0x5d,0xb2,0x43,0xdb,0x01,0x98,0x43,0x9e,0x56,0xa0,0x43,0x80,
14713   0xc1,0x97,0x43,0x69,0x2e,0x97,0x43,0x31,0x17,0x8d,0x43,0x09,0x06,0xab,0xa7,0x39,0x43,0x67,0x0f,0x0e,
14714   0x43,0x08,0xdb,0xbc,0x3b,0x43,0xe8,0x92,0x10,0x43,0xb5,0x85,0x3b,0x43,0x97,0x3c,0x14,0x43,0xab,0xa7,
14715   0x39,0x43,0x0c,0x0b,0x18,0x43,0x09,0x06,0xca,0x30,0x40,0x43,0x30,0x3b,0x13,0x43,0x08,0x17,0xc8,0x43,
14716   0x43,0xa5,0x09,0x17,0x43,0x7e,0xc9,0x44,0x43,0x1a,0xd8,0x1a,0x43,0x9d,0x22,0x43,0x43,0x8d,0xa6,0x1e,
14717   0x43,0x09,0x06,0xc8,0x78,0x4c,0x43,0xed,0xc9,0x1d,0x43,0x08,0x0b,0x32,0x4e,0x43,0x22,0xce,0x20,0x43,
14718   0x23,0xc5,0x4e,0x43,0x58,0xd2,0x23,0x43,0x0b,0x32,0x4e,0x43,0x2b,0xc4,0x26,0x43,0x09,0x06,0xec,0x08,
14719   0x58,0x43,0xc7,0xb1,0x26,0x43,0x08,0x02,0x9c,0x58,0x43,0xef,0x00,0x2b,0x43,0xd9,0x64,0x58,0x43,0x02,
14720   0xbd,0x2e,0x43,0x10,0x51,0x57,0x43,0x37,0xc1,0x31,0x43,0x09,0x06,0xcb,0xdf,0x61,0x43,0x4a,0x65,0x31,
14721   0x43,0x08,0xbe,0x2a,0x63,0x43,0xbd,0x33,0x35,0x43,0x32,0xe1,0x62,0x43,0x56,0x4a,0x38,0x43,0xde,0x83,
14722   0x61,0x43,0x3c,0xe0,0x3a,0x43,0x09,0x06,0x1c,0x7e,0x6a,0x43,0x5b,0x39,0x39,0x43,0x08,0x31,0x11,0x6b,
14723   0x43,0x0c,0xd2,0x3d,0x43,0x1c,0x7e,0x6a,0x43,0x13,0xd9,0x42,0x43,0xd9,0xc4,0x68,0x43,0xcb,0x60,0x48,
14724   0x43,0x09,0x06,0xe5,0xc1,0x73,0x43,0x16,0xf8,0x4b,0x43,0x08,0xa6,0xf7,0x72,0x43,0xb1,0xfd,0x4f,0x43,
14725   0x3b,0x07,0x71,0x43,0x4a,0x14,0x53,0x43,0xa2,0xf0,0x6d,0x43,0x7c,0x29,0x55,0x43,0x09,0x06,0x00,0x8d,
14726   0xa6,0x43,0xef,0x21,0x01,0x43,0x08,0x52,0xfb,0xa6,0x43,0xce,0xc8,0x02,0x43,0xe6,0x16,0xa7,0x43,0x51,
14727   0x4c,0x05,0x43,0x3b,0x68,0xa6,0x43,0x4c,0x75,0x08,0x43,0x09,0x06,0xde,0x20,0xa1,0x43,0x86,0x50,0x08,
14728   0x43,0x08,0xd4,0x4e,0xa1,0x43,0xd3,0xe7,0x0b,0x43,0xb5,0xe9,0xa0,0x43,0x59,0x5a,0x0f,0x43,0xba,0xcc,
14729   0x9f,0x43,0x54,0x83,0x12,0x43,0x09,0x06,0x77,0xfb,0x99,0x43,0x6c,0x16,0x13,0x43,0x08,0xde,0xfc,0x9a,
14730   0x43,0x4a,0xbd,0x14,0x43,0x06,0x34,0x9b,0x43,0xfe,0x55,0x19,0x43,0x13,0xe9,0x99,0x43,0x41,0x27,0x1f,
14731   0x43,0x09,0x06,0x46,0xce,0x93,0x43,0x26,0xa5,0x1d,0x43,0x08,0xe7,0xaa,0x94,0x43,0xbb,0xcc,0x1f,0x43,
14732   0x18,0xb4,0x94,0x43,0xa8,0x40,0x24,0x43,0xe2,0xbb,0x93,0x43,0x21,0xfe,0x28,0x43,0x09,0x06,0xb1,0x8e,
14733   0x8d,0x43,0xa8,0x58,0x28,0x43,0x08,0x19,0x90,0x8e,0x43,0x54,0x13,0x2b,0x43,0xa4,0xd9,0x8e,0x43,0x84,
14734   0x40,0x31,0x43,0x46,0xaa,0x8d,0x43,0x29,0x24,0x37,0x43,0x09,0x06,0xd6,0xbe,0x88,0x43,0xef,0x30,0x33,
14735   0x43,0x08,0x0c,0xb7,0x89,0x43,0x0e,0xa2,0x35,0x43,0xc0,0x37,0x8a,0x43,0x7a,0xaa,0x3b,0x43,0xbb,0x48,
14736   0x89,0x43,0xbb,0x7b,0x41,0x43,0x09,0x06,0x3a,0xad,0x82,0x43,0xc4,0x59,0x43,0x43,0x08,0xd2,0xb7,0x83,
14737   0x43,0x2b,0x5b,0x44,0x43,0x35,0xd6,0x85,0x43,0x48,0xf5,0x49,0x43,0x42,0x97,0x86,0x43,0xc4,0xa1,0x4f,
14738   0x43,0x09,0x06,0x9c,0xb3,0x80,0x43,0x48,0x55,0x5a,0x43,0x08,0xff,0xc5,0x80,0x43,0x09,0x73,0x55,0x43,
14739   0x93,0xe1,0x80,0x43,0x0f,0x39,0x53,0x43,0xf1,0xbe,0x7e,0x43,0x18,0xe7,0x4c,0x43,0x09,0x06,0xe0,0x02,
14740   0x7b,0x43,0x92,0xec,0x5d,0x43,0x08,0x09,0x3a,0x7b,0x43,0xf0,0xf7,0x58,0x43,0x09,0x3a,0x7b,0x43,0xe6,
14741   0x31,0x5b,0x43,0xe0,0x02,0x7b,0x43,0xa8,0x4f,0x56,0x43,0x09,0x06,0x39,0x4f,0x7d,0x43,0x3e,0x8f,0x5c,
14742   0x43,0x08,0xe9,0xe0,0x7c,0x43,0x03,0x9c,0x58,0x43,0x1e,0x2b,0x81,0x43,0x7f,0x30,0x5a,0x43,0xff,0x73,
14743   0x7d,0x43,0xf6,0xb6,0x51,0x43,0x09,0x06,0x5c,0xb8,0x52,0x43,0x28,0x21,0x87,0x43,0x08,0xae,0x3e,0x57,
14744   0x43,0x12,0x9a,0x88,0x43,0x23,0xf5,0x56,0x43,0x04,0xf1,0x8b,0x43,0x25,0xfc,0x5b,0x43,0x85,0x74,0x8e,
14745   0x43,0x08,0x2f,0xf2,0x61,0x43,0x8e,0x52,0x90,0x43,0xd9,0xdc,0x6c,0x43,0x85,0x74,0x8e,0x43,0xc6,0x20,
14746   0x69,0x43,0x3d,0xd8,0x8d,0x43,0x08,0x6d,0x8c,0x5a,0x43,0xf5,0x3b,0x8d,0x43,0x3d,0x77,0x58,0x43,0xa1,
14747   0xc6,0x87,0x43,0xf8,0xed,0x5e,0x43,0x5e,0x0d,0x86,0x43,0x09,0x06,0xde,0xcc,0x92,0x43,0xf7,0x17,0x87,
14748   0x43,0x08,0xb6,0x89,0x90,0x43,0xae,0x87,0x88,0x43,0x4a,0xa5,0x90,0x43,0xa1,0xde,0x8b,0x43,0xf9,0x2a,
14749   0x8e,0x43,0x23,0x62,0x8e,0x43,0x08,0xf5,0x2f,0x8b,0x43,0x5c,0x49,0x90,0x43,0x35,0xd6,0x85,0x43,0x8e,
14750   0x46,0x8e,0x43,0x3d,0xb4,0x87,0x43,0x47,0xaa,0x8d,0x43,0x08,0x6a,0xfe,0x8e,0x43,0xff,0x0d,0x8d,0x43,
14751   0xbb,0x6c,0x8f,0x43,0xf7,0x17,0x87,0x43,0x5c,0x31,0x8c,0x43,0xb2,0x5e,0x85,0x43,0x09,0x06,0x60,0x38,
14752   0x91,0x43,0x69,0x5d,0x7a,0x43,0x08,0x34,0x1e,0x92,0x43,0x1e,0x5b,0x89,0x43,0x04,0x63,0x7e,0x43,0x5e,
14753   0x01,0x84,0x43,0x59,0x2a,0x87,0x43,0x0d,0xcf,0x8d,0x43,0x09,0x03,0x04,0x06,0x5a,0x18,0x63,0x43,0x82,
14754   0x79,0x8b,0x43,0x08,0x25,0x2c,0x64,0x43,0x82,0x79,0x8b,0x43,0x2a,0x1b,0x65,0x43,0x9d,0xef,0x8a,0x43,
14755   0x2a,0x1b,0x65,0x43,0xc1,0x37,0x8a,0x43,0x08,0x2a,0x1b,0x65,0x43,0x17,0x89,0x89,0x43,0x25,0x2c,0x64,
14756   0x43,0x31,0xff,0x88,0x43,0x5a,0x18,0x63,0x43,0x31,0xff,0x88,0x43,0x08,0xf3,0x16,0x62,0x43,0x31,0xff,
14757   0x88,0x43,0xee,0x27,0x61,0x43,0x17,0x89,0x89,0x43,0xee,0x27,0x61,0x43,0xc1,0x37,0x8a,0x43,0x08,0xee,
14758   0x27,0x61,0x43,0x9d,0xef,0x8a,0x43,0xf3,0x16,0x62,0x43,0x82,0x79,0x8b,0x43,0x5a,0x18,0x63,0x43,0x82,
14759   0x79,0x8b,0x43,0x09,0x06,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x08,0x34,0xee,0x89,0x43,0x82,0x79,
14760   0x8b,0x43,0x85,0x5c,0x8a,0x43,0x9d,0xef,0x8a,0x43,0x85,0x5c,0x8a,0x43,0xc1,0x37,0x8a,0x43,0x08,0x85,
14761   0x5c,0x8a,0x43,0x17,0x89,0x89,0x43,0x34,0xee,0x89,0x43,0x31,0xff,0x88,0x43,0x4f,0x64,0x89,0x43,0x31,
14762   0xff,0x88,0x43,0x08,0x9c,0xe3,0x88,0x43,0x31,0xff,0x88,0x43,0x19,0x6c,0x88,0x43,0x17,0x89,0x89,0x43,
14763   0x19,0x6c,0x88,0x43,0xc1,0x37,0x8a,0x43,0x08,0x19,0x6c,0x88,0x43,0x9d,0xef,0x8a,0x43,0x9c,0xe3,0x88,
14764   0x43,0x82,0x79,0x8b,0x43,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x09,0x02,0x04,0x06,0x19,0x60,0x86,
14765   0x43,0xec,0xed,0xa3,0x43,0x08,0x35,0xd6,0x85,0x43,0x76,0x43,0xa6,0x43,0x93,0xe1,0x80,0x43,0x57,0x02,
14766   0xac,0x43,0x61,0xd8,0x80,0x43,0x87,0x17,0xae,0x43,0x08,0xa5,0x85,0x80,0x43,0xc3,0xfe,0xaf,0x43,0xce,
14767   0xbc,0x80,0x43,0x83,0x40,0xb1,0x43,0xa5,0x91,0x82,0x43,0x79,0x6e,0xb1,0x43,0x08,0x23,0x26,0x84,0x43,
14768   0x40,0x93,0xb1,0x43,0x30,0xe7,0x84,0x43,0xbe,0x1b,0xb1,0x43,0x11,0x82,0x84,0x43,0xab,0x6b,0xaf,0x43,
14769   0x08,0xb7,0x41,0x84,0x43,0x3b,0x98,0xae,0x43,0xb7,0x41,0x84,0x43,0xc3,0xf2,0xad,0x43,0xa1,0xae,0x83,
14770   0x43,0x83,0x28,0xad,0x43,0x08,0xb2,0x52,0x83,0x43,0x80,0x39,0xac,0x43,0x81,0x49,0x83,0x43,0xf0,0x00,
14771   0xab,0x43,0xe4,0x67,0x85,0x43,0x76,0x4f,0xa8,0x43,0x08,0x9c,0xd7,0x86,0x43,0xd1,0x83,0xa6,0x43,0xec,
14772   0x45,0x87,0x43,0x01,0x75,0xa2,0x43,0x19,0x60,0x86,0x43,0xec,0xed,0xa3,0x43,0x09,0x06,0xd9,0xdc,0x6c,
14773   0x43,0x14,0x25,0xa4,0x43,0x08,0xa2,0xf0,0x6d,0x43,0x9f,0x7a,0xa6,0x43,0x47,0xec,0x77,0x43,0x80,0x39,
14774   0xac,0x43,0xa9,0xfe,0x77,0x43,0xb0,0x4e,0xae,0x43,0x08,0x23,0xa4,0x78,0x43,0xea,0x35,0xb0,0x43,0xd2,
14775   0x35,0x78,0x43,0xab,0x77,0xb1,0x43,0xc1,0x79,0x74,0x43,0xa2,0xa5,0xb1,0x43,0x08,0xc6,0x50,0x71,0x43,
14776   0x68,0xca,0xb1,0x43,0xab,0xce,0x6f,0x43,0xe7,0x52,0xb1,0x43,0xea,0x98,0x70,0x43,0xd4,0xa2,0xaf,0x43,
14777   0x08,0x9d,0x19,0x71,0x43,0x96,0xd8,0xae,0x43,0x9d,0x19,0x71,0x43,0xec,0x29,0xae,0x43,0xca,0x3f,0x72,
14778   0x43,0xab,0x5f,0xad,0x43,0x08,0xa6,0xf7,0x72,0x43,0xa7,0x70,0xac,0x43,0x09,0x0a,0x73,0x43,0x17,0x38,
14779   0xab,0x43,0x44,0xcd,0x6e,0x43,0x9f,0x86,0xa8,0x43,0x08,0xd4,0xed,0x6b,0x43,0xf8,0xba,0xa6,0x43,0x31,
14780   0x11,0x6b,0x43,0x2a,0xac,0xa2,0x43,0xd9,0xdc,0x6c,0x43,0x14,0x25,0xa4,0x43,0x09,0x01,0x05,0x06,0x66,
14781   0x5d,0x7a,0x43,0x74,0xeb,0xc2,0x43,0x08,0x09,0x22,0x77,0x43,0x50,0xbb,0xc7,0x43,0xe9,0xe0,0x7c,0x43,
14782   0xf5,0x86,0xc9,0x43,0x8f,0x94,0x7a,0x43,0xc5,0x95,0xcd,0x43,0x09,0x06,0x08,0x98,0x80,0x43,0x6b,0x19,
14783   0xc3,0x43,0x08,0xb7,0x35,0x82,0x43,0x79,0xf2,0xc7,0x43,0xf1,0xbe,0x7e,0x43,0x1e,0xbe,0xc9,0x43,0x73,
14784   0x7c,0x80,0x43,0xec,0xcc,0xcd,0x43,0x09,0x06,0x28,0xab,0x7d,0x43,0xae,0xde,0xc6,0x43,0x08,0x1e,0xcd,
14785   0x7b,0x43,0x8a,0xa2,0xc9,0x43,0x30,0x89,0x7f,0x43,0x5c,0x94,0xcc,0x43,0x28,0xab,0x7d,0x43,0x42,0x2a,
14786   0xcf,0x43,0x09,0x01,0x05,0x06,0x24,0x14,0xe0,0x42,0xf5,0x77,0x97,0x43,0x08,0xf7,0x1d,0xe7,0x42,0x74,
14787   0x00,0x97,0x43,0x4d,0x93,0xec,0x42,0xdb,0xf5,0x95,0x43,0x29,0x4b,0xed,0x42,0xcd,0x34,0x95,0x43,0x09,
14788   0x06,0x29,0x7b,0xf5,0x42,0x6f,0x1d,0x98,0x43,0x08,0xe4,0xf1,0xfb,0x42,0x61,0x5c,0x97,0x43,0xdb,0x7d,
14789   0x01,0x43,0xb2,0xbe,0x95,0x43,0x55,0x23,0x02,0x43,0xe7,0xaa,0x94,0x43,0x09,0x06,0x98,0xdc,0x03,0x43,
14790   0xbe,0x8b,0x98,0x43,0x08,0x66,0xdf,0x05,0x43,0x47,0xe6,0x97,0x43,0xae,0x87,0x08,0x43,0x98,0x48,0x96,
14791   0x43,0x61,0x08,0x09,0x43,0xd6,0x06,0x95,0x43,0x09,0x06,0x31,0x0b,0x0b,0x43,0x8e,0x82,0x98,0x43,0x08,
14792   0xdb,0xc5,0x0d,0x43,0x80,0xc1,0x97,0x43,0xd6,0xee,0x10,0x43,0xa9,0xec,0x95,0x43,0x79,0xcb,0x11,0x43,
14793   0x55,0x8f,0x94,0x43,0x09,0x06,0xd1,0x2f,0x18,0x43,0xdb,0x01,0x98,0x43,0x08,0xad,0xe7,0x18,0x43,0x38,
14794   0x25,0x97,0x43,0x8a,0x9f,0x19,0x43,0x80,0xb5,0x95,0x43,0xd6,0x1e,0x19,0x43,0xe0,0xd8,0x94,0x43,0x09,
14795   0x06,0x9a,0x5b,0x1d,0x43,0x58,0x8a,0x97,0x43,0x08,0x01,0x5d,0x1e,0x43,0xf1,0x88,0x96,0x43,0x2f,0x83,
14796   0x1f,0x43,0x19,0xb4,0x94,0x43,0x19,0xf0,0x1e,0x43,0x6f,0x05,0x94,0x43,0x09,0x06,0x0b,0x53,0x24,0x43,
14797   0xae,0xdb,0x96,0x43,0x08,0x25,0xd5,0x25,0x43,0x50,0xac,0x95,0x43,0x53,0xfb,0x26,0x43,0x8a,0x7b,0x93,
14798   0x43,0x76,0x43,0x26,0x43,0xb7,0x95,0x92,0x43,0x09,0x06,0x76,0x5b,0x2a,0x43,0x47,0xda,0x95,0x43,0x08,
14799   0xf3,0xef,0x2b,0x43,0x10,0xe2,0x94,0x43,0x6d,0x95,0x2c,0x43,0xae,0xc3,0x92,0x43,0x68,0xa6,0x2b,0x43,
14800   0x47,0xc2,0x91,0x43,0x09,0x06,0x36,0xc1,0x31,0x43,0x2c,0x58,0x94,0x43,0x08,0x8c,0x1e,0x33,0x43,0x31,
14801   0x3b,0x93,0x43,0x79,0x7a,0x33,0x43,0xff,0x25,0x91,0x43,0xd9,0x9d,0x32,0x43,0xc1,0x5b,0x90,0x43,0x09,
14802   0x06,0x25,0x35,0x36,0x43,0x31,0x3b,0x93,0x43,0x08,0x3f,0xb7,0x37,0x43,0xc1,0x67,0x92,0x43,0xe0,0x93,
14803   0x38,0x43,0xae,0xb7,0x90,0x43,0x7e,0x81,0x38,0x43,0x0d,0xdb,0x8f,0x43,0x09,0x06,0xb5,0x85,0x3b,0x43,
14804   0xe4,0xaf,0x91,0x43,0x08,0xcf,0x07,0x3d,0x43,0x9d,0x13,0x91,0x43,0xbc,0x63,0x3d,0x43,0x47,0xb6,0x8f,
14805   0x43,0xe5,0x9a,0x3d,0x43,0x74,0xd0,0x8e,0x43,0x09,0x06,0xae,0xc6,0x42,0x43,0xa4,0xd9,0x8e,0x43,0x08,
14806   0xca,0x48,0x44,0x43,0xfa,0x2a,0x8e,0x43,0xa2,0x11,0x44,0x43,0x9d,0xfb,0x8c,0x43,0x55,0x92,0x44,0x43,
14807   0x0d,0xc3,0x8b,0x43,0x09,0x06,0x39,0x10,0xc3,0x43,0x34,0x36,0x96,0x43,0x08,0x92,0x44,0xc1,0x43,0xe4,
14808   0xc7,0x95,0x43,0x6f,0xf0,0xbf,0x43,0x4b,0xbd,0x94,0x43,0x47,0xb9,0xbf,0x43,0x0b,0xf3,0x93,0x43,0x09,
14809   0x06,0x8f,0x49,0xbe,0x43,0xb7,0xad,0x96,0x43,0x08,0x11,0xb5,0xbc,0x43,0x77,0xe3,0x95,0x43,0x9c,0xf2,
14810   0xba,0x43,0xfa,0x4e,0x94,0x43,0xae,0x96,0xba,0x43,0x31,0x3b,0x93,0x43,0x09,0x06,0xdb,0xb0,0xb9,0x43,
14811   0x10,0xee,0x96,0x43,0x08,0x42,0xa6,0xb8,0x43,0xc8,0x51,0x96,0x43,0x50,0x5b,0xb7,0x43,0x19,0xb4,0x94,
14812   0x43,0xf7,0x1a,0xb7,0x43,0x58,0x72,0x93,0x43,0x09,0x06,0xf2,0x2b,0xb6,0x43,0x10,0xee,0x96,0x43,0x08,
14813   0x9d,0xce,0xb4,0x43,0x04,0x2d,0x96,0x43,0xed,0x30,0xb3,0x43,0x2c,0x58,0x94,0x43,0xce,0xcb,0xb2,0x43,
14814   0xd6,0xfa,0x92,0x43,0x09,0x06,0x5a,0x09,0xb1,0x43,0x19,0xc0,0x96,0x43,0x08,0x6c,0xad,0xb0,0x43,0x77,
14815   0xe3,0x95,0x43,0x7e,0x51,0xb0,0x43,0xc0,0x73,0x94,0x43,0xd8,0x91,0xb0,0x43,0x1e,0x97,0x93,0x43,0x09,
14816   0x06,0x48,0x4d,0xad,0x43,0xbe,0x7f,0x96,0x43,0x08,0x95,0xcc,0xac,0x43,0x58,0x7e,0x95,0x43,0x4d,0x30,
14817   0xac,0x43,0x80,0xa9,0x93,0x43,0xd8,0x79,0xac,0x43,0xd6,0xfa,0x92,0x43,0x09,0x06,0x90,0xd1,0xa9,0x43,
14818   0x14,0xd1,0x95,0x43,0x08,0x83,0x10,0xa9,0x43,0xb7,0xa1,0x94,0x43,0x3b,0x74,0xa8,0x43,0xf1,0x70,0x92,
14819   0x43,0x29,0xd0,0xa8,0x43,0x1e,0x8b,0x91,0x43,0x09,0x06,0x5a,0xcd,0xa6,0x43,0x8a,0x87,0x95,0x43,0x08,
14820   0x1c,0x03,0xa6,0x43,0x23,0x86,0x94,0x43,0x5f,0xb0,0xa5,0x43,0xc1,0x67,0x92,0x43,0xe1,0x27,0xa6,0x43,
14821   0x8a,0x6f,0x91,0x43,0x09,0x06,0xd4,0x5a,0xa3,0x43,0x2c,0x58,0x94,0x43,0x08,0x29,0xac,0xa2,0x43,0x31,
14822   0x3b,0x93,0x43,0x32,0x7e,0xa2,0x43,0xff,0x25,0x91,0x43,0x83,0xec,0xa2,0x43,0x8e,0x52,0x90,0x43,0x09,
14823   0x06,0xf8,0x96,0xa0,0x43,0x1e,0x97,0x93,0x43,0x08,0xeb,0xd5,0x9f,0x43,0x7b,0xba,0x92,0x43,0x99,0x67,
14824   0x9f,0x43,0x9d,0x13,0x91,0x43,0x99,0x67,0x9f,0x43,0xfa,0x36,0x90,0x43,0x09,0x06,0xeb,0xc9,0x9d,0x43,
14825   0xc8,0x39,0x92,0x43,0x08,0xde,0x08,0x9d,0x43,0xb2,0xa6,0x91,0x43,0xe6,0xda,0x9c,0x43,0x2c,0x40,0x90,
14826   0x43,0x52,0xbf,0x9c,0x43,0x5a,0x5a,0x8f,0x43,0x09,0x06,0x37,0x3d,0x9b,0x43,0x85,0x80,0x90,0x43,0x08,
14827   0x2a,0x7c,0x9a,0x43,0xdb,0xd1,0x8f,0x43,0xf0,0xa0,0x9a,0x43,0x7d,0xa2,0x8e,0x43,0x65,0x57,0x9a,0x43,
14828   0xee,0x69,0x8d,0x43,0x09,0x02,0x04,0x06,0x2a,0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x08,0x0d,0x8a,0x31,
14829   0x42,0x9f,0x0e,0x94,0x43,0xf3,0x1f,0x34,0x42,0x3d,0xfc,0x93,0x43,0x63,0xff,0x36,0x42,0xa9,0xe0,0x93,
14830   0x43,0x08,0xb5,0x34,0x5d,0x42,0x0b,0xf3,0x93,0x43,0x6d,0xa4,0x5e,0x42,0x03,0x39,0x98,0x43,0xe7,0x31,
14831   0x5b,0x42,0x93,0x89,0x9d,0x43,0x08,0x02,0x9c,0x58,0x42,0xd4,0x5a,0xa3,0x43,0x38,0x70,0x53,0x42,0x14,
14832   0x49,0xaa,0x43,0xf8,0xed,0x5e,0x42,0x83,0x28,0xad,0x43,0x08,0xea,0x68,0x68,0x42,0x20,0x22,0xaf,0x43,
14833   0x12,0xb8,0x6c,0x42,0xb5,0x49,0xb1,0x43,0x2a,0x4b,0x6d,0x42,0x0d,0x96,0xb3,0x43,0x07,0x2a,0x4b,0x6d,
14834   0x42,0xc6,0x05,0xb5,0x43,0x08,0x87,0x6e,0x6c,0x42,0x68,0xee,0xb7,0x43,0x1c,0x66,0x66,0x42,0x31,0x0e,
14835   0xbb,0x43,0x57,0x11,0x5e,0x42,0x8f,0x49,0xbe,0x43,0x08,0x66,0x96,0x54,0x42,0xb9,0x5c,0xb8,0x43,0x2c,
14836   0x2b,0x3c,0x42,0x68,0xd6,0xb3,0x43,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x07,0x2a,0xf4,0x2e,0x42,
14837   0x61,0xa4,0xa3,0x43,0x08,0x55,0x1a,0x30,0x42,0xf0,0xd0,0xa2,0x43,0xf8,0xf6,0x30,0x42,0xb2,0x06,0xa2,
14838   0x43,0x98,0xd3,0x31,0x42,0xd6,0x4e,0xa1,0x43,0x08,0x1c,0x6f,0x38,0x42,0x2a,0x94,0x9e,0x43,0xc1,0x22,
14839   0x36,0x42,0xf5,0x9b,0x9d,0x43,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x57,
14840   0xa2,0x9b,0x43,0x08,0xab,0x8f,0x35,0x42,0x8a,0xab,0x9b,0x43,0xe9,0x71,0x3a,0x42,0xb2,0xe2,0x9b,0x43,
14841   0xb7,0x74,0x3c,0x42,0x34,0x5a,0x9c,0x43,0x08,0x23,0x7d,0x42,0x42,0x0b,0x2f,0x9e,0x43,0xe5,0x9a,0x3d,
14842   0x42,0x38,0x6d,0xa3,0x43,0x36,0xd9,0x35,0x42,0xf3,0xd7,0xa7,0x43,0x08,0x12,0x61,0x2e,0x42,0xb0,0x42,
14843   0xac,0x43,0x63,0xff,0x36,0x42,0xdd,0x74,0xaf,0x43,0x1e,0xa6,0x45,0x42,0x44,0x82,0xb2,0x43,0x08,0x74,
14844   0x1b,0x4b,0x42,0x79,0x7a,0xb3,0x43,0x10,0x21,0x4f,0x42,0x2a,0x18,0xb5,0x43,0xdb,0x4c,0x54,0x42,0x91,
14845   0x19,0xb6,0x43,0x08,0xee,0x3f,0x65,0x42,0x5f,0x28,0xba,0x43,0xa7,0xaf,0x66,0x42,0xb9,0x50,0xb6,0x43,
14846   0x14,0x58,0x5c,0x42,0xca,0xdc,0xb1,0x43,0x08,0x2c,0x8b,0x4c,0x42,0x4e,0x30,0xac,0x43,0x19,0xcf,0x48,
14847   0x42,0x2a,0xd0,0xa8,0x43,0xbc,0xab,0x49,0x42,0xa9,0x4c,0xa6,0x43,0x08,0x61,0x5f,0x47,0x42,0xfa,0xa2,
14848   0xa2,0x43,0xa7,0xaf,0x66,0x42,0x85,0x98,0x94,0x43,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x07,0x2a,
14849   0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x09,0x06,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x08,0xdc,0xe3,
14850   0xf1,0x41,0xe9,0x9e,0x92,0x43,0xd2,0xe7,0x0b,0x42,0xd6,0x06,0x95,0x43,0x2a,0xf4,0x2e,0x42,0x04,0x21,
14851   0x94,0x43,0x07,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x08,0x87,0x17,0x2e,0x42,0xc3,0x62,0x95,0x43,
14852   0xe7,0x3a,0x2d,0x42,0xf5,0x6b,0x95,0x43,0x44,0x5e,0x2c,0x42,0xf5,0x6b,0x95,0x43,0x08,0xd1,0x47,0x1c,
14853   0x42,0x19,0xc0,0x96,0x43,0x66,0xdf,0x05,0x42,0x38,0x19,0x95,0x43,0x12,0x6a,0x00,0x42,0xb2,0xbe,0x95,
14854   0x43,0x08,0xbb,0x6b,0xea,0x41,0xd6,0x12,0x97,0x43,0x2d,0x82,0xfa,0x41,0x61,0x74,0x9b,0x43,0x7e,0x72,
14855   0x06,0x42,0x8a,0xab,0x9b,0x43,0x08,0xc8,0x39,0x12,0x42,0x4e,0xd0,0x9b,0x43,0x53,0xe3,0x22,0x42,0xc3,
14856   0x86,0x9b,0x43,0x2a,0xf4,0x2e,0x42,0x57,0xa2,0x9b,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,
14857   0x08,0x01,0xa5,0x2a,0x42,0xa4,0x2d,0x9d,0x43,0x96,0x9c,0x24,0x42,0x06,0x40,0x9d,0x43,0x8a,0xb7,0x1d,
14858   0x42,0x9a,0x5b,0x9d,0x43,0x08,0x6b,0x16,0x13,0x42,0xcd,0x64,0x9d,0x43,0x42,0xc7,0x0e,0x42,0x9a,0x5b,
14859   0x9d,0x43,0x23,0x26,0x04,0x42,0xcd,0x64,0x9d,0x43,0x08,0xe6,0x91,0xeb,0x41,0x38,0x49,0x9d,0x43,0x73,
14860   0x7b,0xdb,0x41,0xf5,0x83,0x99,0x43,0x7f,0x60,0xe2,0x41,0x0b,0x0b,0x98,0x43,0x08,0x7f,0x60,0xe2,0x41,
14861   0xec,0x99,0x95,0x43,0xe3,0x5a,0xde,0x41,0xbe,0x7f,0x96,0x43,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,
14862   0x07,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x09,0x06,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x08,
14863   0xd4,0x7e,0x29,0x42,0xab,0x6b,0xaf,0x43,0x4e,0x0c,0x26,0x42,0x44,0x6a,0xae,0x43,0x38,0x79,0x25,0x42,
14864   0xd4,0x96,0xad,0x43,0x08,0x25,0xbd,0x21,0x42,0xe2,0x4b,0xac,0x43,0x49,0x35,0x29,0x42,0x9a,0x97,0xa7,
14865   0x43,0x2a,0xf4,0x2e,0x42,0x61,0xa4,0xa3,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x09,0x06,
14866   0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x08,0x86,0x20,0x80,0x43,0x57,0x41,0xe6,0x43,0x7d,0x4e,0x80,
14867   0x43,0x25,0x38,0xe6,0x43,0xa5,0x85,0x80,0x43,0xf3,0x2e,0xe6,0x43,0x08,0x35,0xca,0x83,0x43,0xd4,0xc9,
14868   0xe5,0x43,0x9c,0xd7,0x86,0x43,0x44,0x91,0xe4,0x43,0xd5,0xca,0x8a,0x43,0x91,0x1c,0xe6,0x43,0x08,0x53,
14869   0x5f,0x8c,0x43,0xf8,0x1d,0xe7,0x43,0x2f,0x17,0x8d,0x43,0x4e,0x7b,0xe8,0x43,0x92,0x29,0x8d,0x43,0x2f,
14870   0x22,0xea,0x43,0x07,0x92,0x29,0x8d,0x43,0x44,0xb5,0xea,0x43,0x08,0xfe,0x0d,0x8d,0x43,0x2a,0x4b,0xed,
14871   0x43,0xe3,0x8b,0x8b,0x43,0x55,0x7d,0xf0,0x43,0xec,0x51,0x89,0x43,0x72,0x0b,0xf4,0x43,0x08,0xcd,0xd4,
14872   0x84,0x43,0x9d,0x55,0xfb,0x43,0xc9,0xe5,0x83,0x43,0x74,0x1e,0xfb,0x43,0x73,0x94,0x84,0x43,0x5a,0x90,
14873   0xf7,0x43,0x08,0xe8,0x62,0x88,0x43,0xfd,0x30,0xee,0x43,0x39,0xc5,0x86,0x43,0xdd,0xbf,0xeb,0x43,0x35,
14874   0xbe,0x81,0x43,0x40,0xde,0xed,0x43,0x08,0x4f,0x34,0x81,0x43,0x36,0x0c,0xee,0x43,0x08,0x98,0x80,0x43,
14875   0xfd,0x30,0xee,0x43,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x40,0xec,
14876   0x43,0x08,0x35,0xbe,0x81,0x43,0x06,0xf7,0xeb,0x43,0x15,0x65,0x83,0x43,0x49,0xa4,0xeb,0x43,0x1e,0x43,
14877   0x85,0x43,0xbe,0x5a,0xeb,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0x18,0xea,0x43,0x42,0x97,0x86,0x43,0x5f,
14878   0x67,0xf4,0x43,0xa9,0x98,0x87,0x43,0xd4,0x1d,0xf4,0x43,0x08,0x5c,0x25,0x8a,0x43,0xcf,0x16,0xef,0x43,
14879   0x46,0xaa,0x8d,0x43,0x5a,0x3c,0xe9,0x43,0x19,0x6c,0x88,0x43,0x53,0x5e,0xe7,0x43,0x08,0xc4,0x02,0x85,
14880   0x43,0x96,0x0b,0xe7,0x43,0x85,0x2c,0x82,0x43,0x83,0x67,0xe7,0x43,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,
14881   0x43,0x07,0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x09,0x06,0xfd,0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,
14882   0x08,0xfa,0x6c,0x78,0x43,0xd1,0xc2,0xe0,0x43,0x25,0x5c,0x6c,0x43,0x25,0x44,0xe8,0x43,0x1d,0xe5,0x7f,
14883   0x43,0x87,0x4a,0xe6,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,0x43,0x08,0xa6,0x27,0x7b,0x43,0x91,
14884   0x28,0xe8,0x43,0xbc,0xa2,0x77,0x43,0xb0,0x8d,0xe8,0x43,0xc6,0x68,0x75,0x43,0x57,0x4d,0xe8,0x43,0x08,
14885   0xe0,0xd2,0x72,0x43,0xab,0x9e,0xe7,0x43,0x50,0x9a,0x71,0x43,0x2a,0x27,0xe7,0x43,0xea,0x98,0x70,0x43,
14886   0x57,0x35,0xe4,0x43,0x08,0x94,0x3b,0x6f,0x43,0x14,0x7c,0xe2,0x43,0xff,0x13,0x6d,0x43,0x06,0xbb,0xe1,
14887   0x43,0xcf,0xfe,0x6a,0x43,0x06,0xbb,0xe1,0x43,0x08,0x44,0x9d,0x66,0x43,0x77,0x8e,0xe2,0x43,0x3b,0xef,
14888   0x6c,0x43,0x91,0x10,0xe4,0x43,0xfd,0x24,0x6c,0x43,0xb0,0x81,0xe6,0x43,0x08,0x96,0x23,0x6b,0x43,0xee,
14889   0x57,0xe9,0x43,0xca,0x0f,0x6a,0x43,0x5f,0x37,0xec,0x43,0x55,0x71,0x6e,0x43,0x9f,0x01,0xed,0x43,0x08,
14890   0xdb,0xfb,0x75,0x43,0x3b,0xef,0xec,0x43,0x09,0x3a,0x7b,0x43,0xb0,0xa5,0xec,0x43,0x1d,0xe5,0x7f,0x43,
14891   0x91,0x40,0xec,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x08,0xa9,0x16,0x7c,0x43,0xb0,0xb1,
14892   0xee,0x43,0x47,0xec,0x77,0x43,0xd9,0xe8,0xee,0x43,0x1e,0x9d,0x73,0x43,0xcf,0x16,0xef,0x43,0x08,0x0e,
14893   0xc9,0x6b,0x43,0xee,0x7b,0xef,0x43,0x7e,0x90,0x6a,0x43,0xfd,0x30,0xee,0x43,0x01,0xfc,0x68,0x43,0x4e,
14894   0x93,0xec,0x43,0x08,0x31,0xf9,0x66,0x43,0x4e,0x87,0xea,0x43,0x31,0x11,0x6b,0x43,0xd4,0xd5,0xe7,0x43,
14895   0xd9,0xc4,0x68,0x43,0xd4,0xc9,0xe5,0x43,0x08,0xe5,0x79,0x67,0x43,0x77,0x9a,0xe4,0x43,0x44,0x9d,0x66,
14896   0x43,0xab,0x86,0xe3,0x43,0x7e,0x78,0x66,0x43,0x0b,0xaa,0xe2,0x43,0x07,0x7e,0x78,0x66,0x43,0x57,0x29,
14897   0xe2,0x43,0x08,0xa7,0xaf,0x66,0x43,0xbe,0x1e,0xe1,0x43,0x87,0x56,0x68,0x43,0x77,0x82,0xe0,0x43,0xfd,
14898   0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,0x09,0x06,0xc4,0x41,0xbf,0x43,0x85,0xc0,0x72,0x42,0x08,0x73,0xdf,
14899   0xc0,0x43,0xf4,0x76,0x72,0x42,0x97,0x33,0xc2,0x43,0x85,0xc0,0x72,0x42,0xb2,0xb5,0xc3,0x43,0x64,0x56,
14900   0x75,0x42,0x08,0x03,0x24,0xc4,0x43,0x5e,0x7f,0x78,0x42,0xfa,0x51,0xc4,0x43,0x01,0x85,0x7c,0x42,0x5c,
14901   0x64,0xc4,0x43,0xa0,0xb3,0x80,0x42,0x07,0x5c,0x64,0xc4,0x43,0x10,0x93,0x83,0x42,0x08,0xc8,0x48,0xc4,
14902   0x43,0x1c,0x78,0x8a,0x42,0x27,0x6c,0xc3,0x43,0xaf,0xcf,0x94,0x42,0x23,0x7d,0xc2,0x43,0x99,0x9c,0xa4,
14903   0x42,0x08,0x3d,0xe7,0xbf,0x43,0xfb,0xfd,0xb5,0x42,0xb3,0x9d,0xbf,0x43,0x88,0x17,0xae,0x42,0xc4,0x41,
14904   0xbf,0x43,0x69,0x76,0xa3,0x42,0x07,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x08,0x4f,0x8b,0xbf,0x43,
14905   0xed,0x81,0x91,0x42,0xe4,0xa6,0xbf,0x43,0x5d,0x61,0x94,0x42,0xfa,0x39,0xc0,0x43,0x3b,0x49,0x9d,0x42,
14906   0x08,0x2b,0x43,0xc0,0x43,0x28,0xed,0xa9,0x42,0x61,0x3b,0xc1,0x43,0x00,0x9e,0xa5,0x42,0xe4,0xb2,0xc1,
14907   0x43,0x5d,0x91,0x9c,0x42,0x08,0x78,0xce,0xc1,0x43,0xfd,0x36,0x90,0x42,0x22,0x89,0xc4,0x43,0x81,0x72,
14908   0x86,0x42,0xae,0xc6,0xc2,0x43,0xa0,0xb3,0x80,0x42,0x08,0x54,0x86,0xc2,0x43,0x58,0xd1,0x7e,0x42,0x30,
14909   0x32,0xc1,0x43,0xce,0x5e,0x7b,0x42,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x07,0xc4,0x41,0xbf,0x43,
14910   0x85,0xc0,0x72,0x42,0x09,0x06,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x08,0x35,0xfd,0xbb,0x43,0xa4,
14911   0xa1,0x5c,0x42,0x5e,0x34,0xbc,0x43,0x9d,0x2a,0x70,0x42,0x5e,0x40,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,
14912   0x4c,0x9c,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,0xef,0xbe,0x43,0x0e,0x0a,0x73,0x42,0xc4,0x41,0xbf,0x43,
14913   0x85,0xc0,0x72,0x42,0x07,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x08,0xcd,0x13,0xbf,0x43,0xe8,0xf1,
14914   0x7b,0x42,0xd6,0xe5,0xbe,0x43,0x71,0x3b,0x7c,0x42,0xdf,0xb7,0xbe,0x43,0x71,0x3b,0x7c,0x42,0x08,0x08,
14915   0xe3,0xbc,0x43,0xa4,0x61,0x7d,0x42,0x28,0x3c,0xbb,0x43,0x91,0x45,0x69,0x42,0x28,0x3c,0xbb,0x43,0x58,
14916   0x71,0x6e,0x42,0x08,0xce,0xfb,0xba,0x43,0xd5,0x35,0x78,0x42,0x59,0x45,0xbb,0x43,0x58,0x23,0x82,0x42,
14917   0xa1,0xe1,0xbb,0x43,0xd7,0xbe,0x88,0x42,0x08,0xc9,0x18,0xbc,0x43,0xaf,0x9f,0x8c,0x42,0x1e,0x76,0xbd,
14918   0x43,0x51,0x7c,0x8d,0x42,0xd6,0xe5,0xbe,0x43,0xf4,0x58,0x8e,0x42,0x08,0x9c,0x0a,0xbf,0x43,0x45,0xc7,
14919   0x8e,0x42,0x30,0x26,0xbf,0x43,0x96,0x35,0x8f,0x42,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x07,0xc4,
14920   0x41,0xbf,0x43,0x69,0x76,0xa3,0x42,0x08,0x08,0xef,0xbe,0x43,0xb1,0xd6,0x99,0x42,0xe8,0x89,0xbe,0x43,
14921   0xde,0xc5,0x8d,0x42,0xc0,0x46,0xbc,0x43,0xc2,0x5b,0x90,0x42,0x08,0x9c,0xf2,0xba,0x43,0x86,0x80,0x90,
14922   0x42,0xf2,0x43,0xba,0x43,0xe8,0x73,0x87,0x42,0x8f,0x31,0xba,0x43,0xb6,0xf4,0x7d,0x42,0x07,0x8f,0x31,
14923   0xba,0x43,0x21,0xc6,0x76,0x42,0x08,0xc0,0x3a,0xba,0x43,0x5f,0x48,0x6b,0x42,0xae,0x96,0xba,0x43,0xe3,
14924   0x83,0x61,0x42,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x09,0x06,0xea,0x74,0xea,0x43,0x61,0x44,0x93,
14925   0x43,0x08,0x24,0x5c,0xec,0x43,0x31,0x3b,0x93,0x43,0xfb,0x30,0xee,0x43,0x93,0x4d,0x93,0x43,0x0d,0xe1,
14926   0xef,0x43,0x80,0xa9,0x93,0x43,0x08,0x8f,0x58,0xf0,0x43,0xd1,0x17,0x94,0x43,0xb7,0x8f,0xf0,0x43,0x10,
14927   0xe2,0x94,0x43,0xea,0x98,0xf0,0x43,0xa9,0xec,0x95,0x43,0x07,0xea,0x98,0xf0,0x43,0x38,0x25,0x97,0x43,
14928   0x08,0x23,0x74,0xf0,0x43,0x9f,0x32,0x9a,0x43,0x5a,0x60,0xef,0x43,0x53,0xcb,0x9e,0x43,0x2d,0x3a,0xee,
14929   0x43,0xfd,0x91,0xa3,0x43,0x08,0xa2,0xf0,0xed,0x43,0xdd,0x38,0xa5,0x43,0x17,0xa7,0xed,0x43,0xbe,0xdf,
14930   0xa6,0x43,0x5a,0x54,0xed,0x43,0x9f,0x86,0xa8,0x43,0x08,0xfc,0x24,0xec,0x43,0xca,0xc4,0xad,0x43,0x48,
14931   0xa4,0xeb,0x43,0x40,0x6f,0xab,0x43,0x28,0x3f,0xeb,0x43,0x1c,0x0f,0xa8,0x43,0x08,0x1f,0x6d,0xeb,0x43,
14932   0x72,0x48,0xa3,0x43,0x67,0x09,0xec,0x43,0xd1,0x53,0x9e,0x43,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,0x43,
14933   0x07,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x08,0x7e,0x90,0xea,0x43,0x8a,0x9f,0x99,0x43,0x12,0xac,
14934   0xea,0x43,0xbc,0xa8,0x99,0x43,0xa7,0xc7,0xea,0x43,0xbc,0xa8,0x99,0x43,0x08,0x51,0x76,0xeb,0x43,0x9f,
14935   0x32,0x9a,0x43,0x5e,0x37,0xec,0x43,0x49,0xed,0x9c,0x43,0xb0,0xa5,0xec,0x43,0x2a,0xa0,0xa0,0x43,0x08,
14936   0x09,0xe6,0xec,0x43,0xd1,0x77,0xa4,0x43,0x28,0x4b,0xed,0x43,0x61,0xa4,0xa3,0x43,0xab,0xc2,0xed,0x43,
14937   0x8e,0xb2,0xa0,0x43,0x08,0x70,0xe7,0xed,0x43,0xde,0x08,0x9d,0x43,0x87,0x86,0xf0,0x43,0x2f,0x53,0x97,
14938   0x43,0x87,0x7a,0xee,0x43,0xec,0x99,0x95,0x43,0x08,0xca,0x27,0xee,0x43,0xff,0x3d,0x95,0x43,0x74,0xca,
14939   0xec,0x43,0x55,0x8f,0x94,0x43,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x07,0xea,0x74,0xea,0x43,0x61,
14940   0x44,0x93,0x43,0x09,0x06,0x05,0xd3,0xe5,0x43,0x19,0x9c,0x90,0x43,0x08,0x09,0xc2,0xe6,0x43,0xd1,0xff,
14941   0x8f,0x43,0x4d,0x6f,0xe6,0x43,0x74,0xe8,0x92,0x43,0x3b,0xd7,0xe8,0x43,0xc3,0x56,0x93,0x43,0x08,0x1f,
14942   0x61,0xe9,0x43,0x93,0x4d,0x93,0x43,0x05,0xeb,0xe9,0x43,0x93,0x4d,0x93,0x43,0xea,0x74,0xea,0x43,0x61,
14943   0x44,0x93,0x43,0x07,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x08,0x24,0x50,0xea,0x43,0xe7,0xaa,0x94,
14944   0x43,0x2d,0x22,0xea,0x43,0xe7,0xaa,0x94,0x43,0x36,0xf4,0xe9,0x43,0xe7,0xaa,0x94,0x43,0x08,0xa2,0xcc,
14945   0xe7,0x43,0xe0,0xd8,0x94,0x43,0xd4,0xc9,0xe5,0x43,0x19,0xa8,0x92,0x43,0xd4,0xc9,0xe5,0x43,0x27,0x69,
14946   0x93,0x43,0x08,0x17,0x77,0xe5,0x43,0xe0,0xd8,0x94,0x43,0x67,0xe5,0xe5,0x43,0x47,0xda,0x95,0x43,0x43,
14947   0x9d,0xe6,0x43,0xe2,0xd3,0x97,0x43,0x08,0x9d,0xdd,0xe6,0x43,0xad,0xe7,0x98,0x43,0x09,0xce,0xe8,0x43,
14948   0xff,0x55,0x99,0x43,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x07,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,
14949   0x43,0x08,0x71,0xcf,0xe9,0x43,0x53,0xb3,0x9a,0x43,0xa7,0xbb,0xe8,0x43,0xdb,0x0d,0x9a,0x43,0xc6,0x14,
14950   0xe7,0x43,0xdb,0x0d,0x9a,0x43,0x08,0x48,0x80,0xe5,0x43,0xdb,0x0d,0x9a,0x43,0x0a,0xb6,0xe4,0x43,0xc3,
14951   0x6e,0x97,0x43,0x76,0x9a,0xe4,0x43,0x74,0xf4,0x94,0x43,0x07,0x76,0x9a,0xe4,0x43,0x79,0xd7,0x93,0x43,
14952   0x08,0xd8,0xac,0xe4,0x43,0x66,0x27,0x92,0x43,0x29,0x1b,0xe5,0x43,0xe0,0xc0,0x90,0x43,0x05,0xd3,0xe5,
14953   0x43,0x19,0x9c,0x90,0x43,0x09,0x06,0x1b,0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x08,0x71,0x0b,0xf4,0x42,
14954   0x00,0x0e,0x8d,0x42,0x8c,0x0f,0x01,0x43,0x3e,0xc0,0x89,0x42,0xf3,0x28,0x06,0x43,0x48,0x9e,0x8b,0x42,
14955   0x08,0x15,0x89,0x09,0x43,0x00,0x0e,0x8d,0x42,0xe0,0x9c,0x0a,0x43,0xc1,0x8b,0x98,0x42,0xa6,0xc1,0x0a,
14956   0x43,0x02,0xa5,0xaa,0x42,0x07,0xa6,0xc1,0x0a,0x43,0xf9,0xf6,0xb0,0x42,0x08,0xa6,0xc1,0x0a,0x43,0x47,
14957   0x8e,0xb4,0x42,0x42,0xaf,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0xe0,0x9c,0x0a,0x43,0xba,0x74,0xbc,0x42,0x08,
14958   0xa1,0xd2,0x09,0x43,0x40,0x47,0xd0,0x42,0x0d,0xab,0x07,0x43,0x91,0xb5,0xd0,0x42,0x3b,0xb9,0x04,0x43,
14959   0xec,0x71,0xba,0x42,0x08,0xe5,0x5b,0x03,0x43,0xe3,0x33,0xa8,0x42,0x63,0xd8,0x00,0x43,0xce,0x70,0x9f,
14960   0x42,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x07,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,0x42,0x08,0xed,
14961   0x6f,0xed,0x42,0x73,0x24,0x9d,0x42,0xd8,0x0c,0xf5,0x42,0x99,0x6c,0x9c,0x42,0x27,0xab,0xfd,0x42,0xea,
14962   0xda,0x9c,0x42,0x08,0x36,0xca,0x03,0x43,0x2b,0x94,0x9e,0x42,0x68,0xc7,0x01,0x43,0x8f,0xbe,0xa2,0x42,
14963   0xfa,0x06,0x08,0x43,0x73,0xb4,0xb5,0x42,0x08,0x8e,0x2e,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0x9d,0xe3,0x08,
14964   0x43,0xd7,0x1e,0x99,0x42,0x28,0x15,0x05,0x43,0x32,0x3b,0x93,0x42,0x08,0x63,0xf0,0x04,0x43,0x70,0xed,
14965   0x8f,0x42,0x71,0x0b,0xf4,0x42,0x32,0x3b,0x93,0x42,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x07,0x1b,
14966   0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x09,0x06,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,0x08,0x8e,0x55,
14967   0xc0,0x42,0xb8,0x4d,0x86,0x42,0x60,0xbf,0xd7,0x42,0x3e,0xf0,0x91,0x42,0x63,0xf6,0xe4,0x42,0x70,0xed,
14968   0x8f,0x42,0x08,0x7a,0x89,0xe5,0x42,0xac,0xc8,0x8f,0x42,0xcc,0xf7,0xe5,0x42,0xac,0xc8,0x8f,0x42,0x1b,
14969   0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x07,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x08,0x63,0xf6,0xe4,
14970   0x42,0x3b,0x19,0x95,0x42,0xe6,0x61,0xe3,0x42,0x00,0x3e,0x95,0x42,0xf4,0x16,0xe2,0x42,0xc4,0x62,0x95,
14971   0x42,0x08,0x6e,0x74,0xd6,0x42,0x15,0xd1,0x95,0x42,0x97,0x63,0xca,0x42,0xaf,0xcf,0x94,0x42,0xfb,0x2d,
14972   0xbe,0x42,0x86,0x80,0x90,0x42,0x08,0x97,0x03,0xba,0x42,0xce,0x10,0x8f,0x42,0x5e,0x28,0xba,0x42,0x3e,
14973   0xf0,0x91,0x42,0xf2,0x4f,0xbc,0x42,0x45,0xf7,0x96,0x42,0x08,0x27,0x54,0xbf,0x42,0x73,0x24,0x9d,0x42,
14974   0xa5,0xe8,0xc0,0x42,0x86,0xe0,0xa0,0x42,0xe4,0xca,0xc5,0x42,0xed,0x11,0xaa,0x42,0x08,0x54,0xaa,0xc8,
14975   0x42,0x86,0x40,0xb1,0x42,0x59,0x81,0xc5,0x42,0xa1,0x11,0xc4,0x42,0x3e,0xe7,0xbf,0x42,0xfb,0x8d,0xce,
14976   0x42,0x08,0xb4,0x6d,0xb7,0x42,0x30,0xc2,0xd9,0x42,0x46,0xf5,0xc9,0x42,0xdf,0x53,0xd9,0x42,0x38,0x40,
14977   0xcb,0x42,0x62,0x8f,0xcf,0x42,0x08,0x7d,0xf9,0xcc,0x42,0xec,0xa1,0xc2,0x42,0x07,0x43,0xcd,0x42,0x6c,
14978   0xdd,0xb8,0x42,0x2b,0x8b,0xcc,0x42,0x92,0xf5,0xaf,0x42,0x08,0xf9,0x8d,0xce,0x42,0x41,0x57,0xa7,0x42,
14979   0x5b,0xb8,0xd2,0x42,0xae,0x2f,0xa5,0x42,0x18,0x2f,0xd9,0x42,0x13,0x2a,0xa1,0x42,0x08,0x41,0x7e,0xdd,
14980   0x42,0xe3,0x03,0xa0,0x42,0x2e,0xf2,0xe1,0x42,0x7c,0x02,0x9f,0x42,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,
14981   0x42,0x07,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x08,0x4d,0x63,0xe4,0x42,0x00,0x9e,0xa5,0x42,0xf4,
14982   0x16,0xe2,0x42,0x15,0x31,0xa6,0x42,0x99,0xca,0xdf,0x42,0x2b,0xc4,0xa6,0x42,0x08,0xc0,0x82,0xc6,0x42,
14983   0xc4,0xc2,0xa5,0x42,0x57,0xe1,0xd5,0x42,0x91,0xb5,0xd0,0x42,0x54,0xda,0xd0,0x42,0x97,0x93,0xd2,0x42,
14984   0x08,0x9c,0x3a,0xc7,0x42,0x17,0x58,0xdc,0x42,0x9c,0x0a,0xbf,0x42,0x6e,0xa4,0xde,0x42,0x90,0x25,0xb8,
14985   0x42,0xdf,0x53,0xd9,0x42,0x08,0x59,0x21,0xb5,0x42,0xf2,0xdf,0xd4,0x42,0x51,0x43,0xb3,0x42,0x91,0xb5,
14986   0xd0,0x42,0xc5,0x29,0xbb,0x42,0x0e,0x1a,0xca,0x42,0x08,0x65,0x36,0xc4,0x42,0xd0,0x07,0xbd,0x42,0x3e,
14987   0xe7,0xbf,0x42,0x37,0x09,0xbe,0x42,0x0c,0xea,0xc1,0x42,0xcd,0xd0,0xaf,0x42,0x08,0x2b,0x5b,0xc4,0x42,
14988   0x18,0x08,0xa3,0x42,0x67,0xa6,0xab,0x42,0x99,0x3c,0x94,0x42,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,
14989   0x09,];
14990 
14991 private struct ThePath {
14992 public:
14993   enum Command {
14994     Bounds, // always first, has 4 args (x0, y0, x1, y1)
14995     StrokeMode,
14996     FillMode,
14997     StrokeFillMode,
14998     NormalStroke,
14999     ThinStroke,
15000     MoveTo,
15001     LineTo,
15002     CubicTo, // cubic bezier
15003     EndPath,
15004   }
15005 
15006 public:
15007   const(ubyte)[] path;
15008   uint ppos;
15009 
15010 public:
15011   this (const(void)[] apath) pure nothrow @trusted @nogc {
15012     path = cast(const(ubyte)[])apath;
15013   }
15014 
15015   @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (ppos >= path.length); }
15016 
15017   Command getCommand () nothrow @trusted @nogc {
15018     pragma(inline, true);
15019     if (ppos >= cast(uint)path.length) assert(0, "invalid path");
15020     return cast(Command)(path.ptr[ppos++]);
15021   }
15022 
15023   // number of (x,y) pairs for this command
15024   static int argCount (in Command cmd) nothrow @safe @nogc {
15025     version(aliced) pragma(inline, true);
15026          if (cmd == Command.Bounds) return 2;
15027     else if (cmd == Command.MoveTo || cmd == Command.LineTo) return 1;
15028     else if (cmd == Command.CubicTo) return 3;
15029     else return 0;
15030   }
15031 
15032   void skipArgs (int argc) nothrow @trusted @nogc {
15033     pragma(inline, true);
15034     ppos += cast(uint)(float.sizeof*2*argc);
15035   }
15036 
15037   float getFloat () nothrow @trusted @nogc {
15038     pragma(inline, true);
15039     if (ppos >= cast(uint)path.length || cast(uint)path.length-ppos < float.sizeof) assert(0, "invalid path");
15040     version(LittleEndian) {
15041       float res = *cast(const(float)*)(&path.ptr[ppos]);
15042       ppos += cast(uint)float.sizeof;
15043       return res;
15044     } else {
15045       static assert(float.sizeof == 4);
15046       uint xp = path.ptr[ppos]|(path.ptr[ppos+1]<<8)|(path.ptr[ppos+2]<<16)|(path.ptr[ppos+3]<<24);
15047       ppos += cast(uint)float.sizeof;
15048       return *cast(const(float)*)(&xp);
15049     }
15050   }
15051 }
15052 
15053 // this will add baphomet's background path to the current NanoVega path, so you can fill it.
15054 public void addBaphometBack (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc {
15055   if (nvg is null) return;
15056 
15057   auto path = ThePath(baphometPath);
15058 
15059   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15060   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15061 
15062   bool inPath = false;
15063   while (!path.empty) {
15064     auto cmd = path.getCommand();
15065     switch (cmd) {
15066       case ThePath.Command.MoveTo:
15067         inPath = true;
15068         immutable float ex = getScaledX();
15069         immutable float ey = getScaledY();
15070         nvg.moveTo(ex, ey);
15071         break;
15072       case ThePath.Command.LineTo:
15073         inPath = true;
15074         immutable float ex = getScaledX();
15075         immutable float ey = getScaledY();
15076         nvg.lineTo(ex, ey);
15077         break;
15078       case ThePath.Command.CubicTo: // cubic bezier
15079         inPath = true;
15080         immutable float x1 = getScaledX();
15081         immutable float y1 = getScaledY();
15082         immutable float x2 = getScaledX();
15083         immutable float y2 = getScaledY();
15084         immutable float ex = getScaledX();
15085         immutable float ey = getScaledY();
15086         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15087         break;
15088       case ThePath.Command.EndPath:
15089         if (inPath) return;
15090         break;
15091       default:
15092         path.skipArgs(path.argCount(cmd));
15093         break;
15094     }
15095   }
15096 }
15097 
15098 // this will add baphomet's pupil paths to the current NanoVega path, so you can fill it.
15099 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 {
15100   // pupils starts with "fill-and-stroke" mode
15101   if (nvg is null) return;
15102 
15103   auto path = ThePath(baphometPath);
15104 
15105   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15106   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15107 
15108   bool inPath = false;
15109   bool pupLeft = true;
15110   while (!path.empty) {
15111     auto cmd = path.getCommand();
15112     switch (cmd) {
15113       case ThePath.Command.StrokeFillMode: inPath = true; break;
15114       case ThePath.Command.MoveTo:
15115         if (!inPath) goto default;
15116         static if (!left) { if (pupLeft) goto default; }
15117         static if (!right) { if (!pupLeft) goto default; }
15118         immutable float ex = getScaledX();
15119         immutable float ey = getScaledY();
15120         nvg.moveTo(ex, ey);
15121         break;
15122       case ThePath.Command.LineTo:
15123         if (!inPath) goto default;
15124         static if (!left) { if (pupLeft) goto default; }
15125         static if (!right) { if (!pupLeft) goto default; }
15126         immutable float ex = getScaledX();
15127         immutable float ey = getScaledY();
15128         nvg.lineTo(ex, ey);
15129         break;
15130       case ThePath.Command.CubicTo: // cubic bezier
15131         if (!inPath) goto default;
15132         static if (!left) { if (pupLeft) goto default; }
15133         static if (!right) { if (!pupLeft) goto default; }
15134         immutable float x1 = getScaledX();
15135         immutable float y1 = getScaledY();
15136         immutable float x2 = getScaledX();
15137         immutable float y2 = getScaledY();
15138         immutable float ex = getScaledX();
15139         immutable float ey = getScaledY();
15140         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15141         break;
15142       case ThePath.Command.EndPath:
15143         if (inPath) {
15144           if (pupLeft) pupLeft = false; else return;
15145         }
15146         break;
15147       default:
15148         path.skipArgs(path.argCount(cmd));
15149         break;
15150     }
15151   }
15152 }
15153 
15154 // mode: 'f' to allow fills; 's' to allow strokes; 'w' to allow stroke widths; 'c' to replace fills with strokes
15155 public void renderBaphomet(string mode="fs") (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc {
15156   template hasChar(char ch, string s) {
15157          static if (s.length == 0) enum hasChar = false;
15158     else static if (s[0] == ch) enum hasChar = true;
15159     else enum hasChar = hasChar!(ch, s[1..$]);
15160   }
15161   enum AllowStroke = hasChar!('s', mode);
15162   enum AllowFill = hasChar!('f', mode);
15163   enum AllowWidth = hasChar!('w', mode);
15164   enum Contour = hasChar!('c', mode);
15165   //static assert(AllowWidth || AllowFill);
15166 
15167   if (nvg is null) return;
15168 
15169   auto path = ThePath(baphometPath);
15170 
15171   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15172   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15173 
15174   int mode = 0;
15175   int sw = ThePath.Command.NormalStroke;
15176   nvg.beginPath();
15177   while (!path.empty) {
15178     auto cmd = path.getCommand();
15179     switch (cmd) {
15180       case ThePath.Command.StrokeMode: mode = ThePath.Command.StrokeMode; break;
15181       case ThePath.Command.FillMode: mode = ThePath.Command.FillMode; break;
15182       case ThePath.Command.StrokeFillMode: mode = ThePath.Command.StrokeFillMode; break;
15183       case ThePath.Command.NormalStroke: sw = ThePath.Command.NormalStroke; break;
15184       case ThePath.Command.ThinStroke: sw = ThePath.Command.ThinStroke; break;
15185       case ThePath.Command.MoveTo:
15186         immutable float ex = getScaledX();
15187         immutable float ey = getScaledY();
15188         nvg.moveTo(ex, ey);
15189         break;
15190       case ThePath.Command.LineTo:
15191         immutable float ex = getScaledX();
15192         immutable float ey = getScaledY();
15193         nvg.lineTo(ex, ey);
15194         break;
15195       case ThePath.Command.CubicTo: // cubic bezier
15196         immutable float x1 = getScaledX();
15197         immutable float y1 = getScaledY();
15198         immutable float x2 = getScaledX();
15199         immutable float y2 = getScaledY();
15200         immutable float ex = getScaledX();
15201         immutable float ey = getScaledY();
15202         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15203         break;
15204       case ThePath.Command.EndPath:
15205         if (mode == ThePath.Command.FillMode || mode == ThePath.Command.StrokeFillMode) {
15206           static if (AllowFill || Contour) {
15207             static if (Contour) {
15208               if (mode == ThePath.Command.FillMode) { nvg.strokeWidth = 1; nvg.stroke(); }
15209             } else {
15210               nvg.fill();
15211             }
15212           }
15213         }
15214         if (mode == ThePath.Command.StrokeMode || mode == ThePath.Command.StrokeFillMode) {
15215           static if (AllowStroke || Contour) {
15216             static if (AllowWidth) {
15217                    if (sw == ThePath.Command.NormalStroke) nvg.strokeWidth = 1;
15218               else if (sw == ThePath.Command.ThinStroke) nvg.strokeWidth = 0.5;
15219               else assert(0, "wtf?!");
15220             }
15221             nvg.stroke();
15222           }
15223         }
15224         nvg.newPath();
15225         break;
15226       default:
15227         path.skipArgs(path.argCount(cmd));
15228         break;
15229     }
15230   }
15231   nvg.newPath();
15232 }