nanovega: removed unused fontstach scratch area (it is meant for C version of stb_ttf...
[iv.d.git] / nanovega / nanovega.d
blobcae7456c4c28dd556e3f16852378b83e5c445128
1 //
2 // Copyright (c) 2013 Mikko Mononen memon@inside.org
3 //
4 // This software is provided 'as-is', without any express or implied
5 // warranty. In no event will the authors be held liable for any damages
6 // arising from the use of this software.
7 // Permission is granted to anyone to use this software for any purpose,
8 // including commercial applications, and to alter it and redistribute it
9 // freely, subject to the following restrictions:
10 // 1. The origin of this software must not be misrepresented; you must not
11 // claim that you wrote the original software. If you use this software
12 // in a product, an acknowledgment in the product documentation would be
13 // appreciated but is not required.
14 // 2. Altered source versions must be plainly marked as such, and must not be
15 // misrepresented as being the original software.
16 // 3. This notice may not be removed or altered from any source distribution.
18 /* Invisible Vector Library
19 * ported by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
20 * Understanding is not required. Only obedience.
21 * yes, this D port is GPLed.
23 * This program is free software: you can redistribute it and/or modify
24 * it under the terms of the GNU General Public License as published by
25 * the Free Software Foundation, either version 3 of the License, or
26 * (at your option) any later version.
28 * This program is distributed in the hope that it will be useful,
29 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 * GNU General Public License for more details.
33 * You should have received a copy of the GNU General Public License
34 * along with this program. If not, see <http://www.gnu.org/licenses/>.
36 /**
37 The NanoVega API is modeled loosely on HTML5 canvas API.
38 If you know canvas, you're up to speed with NanoVega in no time.
41 Creating drawing context
42 ========================
44 The drawing context is created using platform specific constructor function.
46 ---
47 NVGContext vg = nvgCreateContext();
48 ---
50 $(WARNING You must use created context ONLY in that thread where you created it.
51 There is no way to "transfer" context between threads. Trying to do so
52 will lead to UB.)
54 $(WARNING Never issue any commands outside of [beginFrame]/[endFrame]. Trying to
55 do so will lead to UB.)
58 Drawing shapes with NanoVega
59 ============================
61 Drawing a simple shape using NanoVega consists of four steps:
62 $(LIST
63 * begin a new shape,
64 * define the path to draw,
65 * set fill or stroke,
66 * and finally fill or stroke the path.
69 ---
70 vg.beginPath();
71 vg.rect(100, 100, 120, 30);
72 vg.fillColor(nvgRGBA(255, 192, 0, 255));
73 vg.fill();
74 ---
76 Calling [beginPath] will clear any existing paths and start drawing from blank slate.
77 There are number of number of functions to define the path to draw, such as rectangle,
78 rounded rectangle and ellipse, or you can use the common moveTo, lineTo, bezierTo and
79 arcTo API to compose the paths step by step.
82 Understanding Composite Paths
83 =============================
85 Because of the way the rendering backend is built in NanoVega, drawing a composite path,
86 that is path consisting from multiple paths defining holes and fills, is a bit more
87 involved. NanoVega uses non-zero filling rule and by default, and paths are wound in counter
88 clockwise order. Keep that in mind when drawing using the low level draw API. In order to
89 wind one of the predefined shapes as a hole, you should call `pathWinding(NVGSolidity.Hole)`,
90 or `pathWinding(NVGSolidity.Solid)` $(B after) defining the path.
92 ---
93 vg.beginPath();
94 vg.rect(100, 100, 120, 30);
95 vg.circle(120, 120, 5);
96 vg.pathWinding(NVGSolidity.Hole); // mark circle as a hole
97 vg.fillColor(nvgRGBA(255, 192, 0, 255));
98 vg.fill();
99 ---
102 Rendering is wrong, what to do?
103 ===============================
105 $(LIST
106 * make sure you have created NanoVega context using [nvgCreateContext] call
107 * make sure you have initialised OpenGL with $(B stencil buffer)
108 * make sure you have cleared stencil buffer
109 * make sure all rendering calls happen between [beginFrame] and [endFrame]
110 * to enable more checks for OpenGL errors, add `NVGContextFlag.Debug` flag to [nvgCreateContext]
114 OpenGL state touched by the backend
115 ===================================
117 The OpenGL back-end touches following states:
119 When textures are uploaded or updated, the following pixel store is set to defaults:
120 `GL_UNPACK_ALIGNMENT`, `GL_UNPACK_ROW_LENGTH`, `GL_UNPACK_SKIP_PIXELS`, `GL_UNPACK_SKIP_ROWS`.
121 Texture binding is also affected. Texture updates can happen when the user loads images,
122 or when new font glyphs are added. Glyphs are added as needed between calls to [beginFrame]
123 and [endFrame].
125 The data for the whole frame is buffered and flushed in [endFrame].
126 The following code illustrates the OpenGL state touched by the rendering code:
129 glUseProgram(prog);
130 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
131 glEnable(GL_CULL_FACE);
132 glCullFace(GL_BACK);
133 glFrontFace(GL_CCW);
134 glEnable(GL_BLEND);
135 glDisable(GL_DEPTH_TEST);
136 glDisable(GL_SCISSOR_TEST);
137 glDisable(GL_COLOR_LOGIC_OP);
138 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
139 glStencilMask(0xffffffff);
140 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
141 glStencilFunc(GL_ALWAYS, 0, 0xffffffff);
142 glActiveTexture(GL_TEXTURE1);
143 glActiveTexture(GL_TEXTURE0);
144 glBindBuffer(GL_UNIFORM_BUFFER, buf);
145 glBindVertexArray(arr);
146 glBindBuffer(GL_ARRAY_BUFFER, buf);
147 glBindTexture(GL_TEXTURE_2D, tex);
148 glUniformBlockBinding(... , GLNVG_FRAG_BINDING);
151 Symbol_groups:
153 context_management =
154 ## Context Management
156 Functions to create and destory NanoVega context.
158 frame_management =
159 ## Frame Management
161 To start drawing with NanoVega context, you have to "begin frame", and then
162 "end frame" to flush your rendering commands to GPU.
164 composite_operation =
165 ## Composite Operation
167 The composite operations in NanoVega are modeled after HTML Canvas API, and
168 the blend func is based on OpenGL (see corresponding manuals for more info).
169 The colors in the blending state have premultiplied alpha.
171 color_utils =
172 ## Color Utils
174 Colors in NanoVega are stored as ARGB. Zero alpha means "transparent color".
176 matrices =
177 ## Matrices and Transformations
179 The paths, gradients, patterns and scissor region are transformed by an transformation
180 matrix at the time when they are passed to the API.
181 The current transformation matrix is an affine matrix:
183 ----------------------
184 [sx kx tx]
185 [ky sy ty]
186 [ 0 0 1]
187 ----------------------
189 Where: (sx, sy) define scaling, (kx, ky) skewing, and (tx, ty) translation.
190 The last row is assumed to be (0, 0, 1) and is not stored.
192 Apart from [resetTransform], each transformation function first creates
193 specific transformation matrix and pre-multiplies the current transformation by it.
195 Current coordinate system (transformation) can be saved and restored using [save] and [restore].
197 The following functions can be used to make calculations on 2x3 transformation matrices.
198 A 2x3 matrix is represented as float[6].
200 state_handling =
201 ## State Handling
203 NanoVega contains state which represents how paths will be rendered.
204 The state contains transform, fill and stroke styles, text and font styles,
205 and scissor clipping.
207 render_styles =
208 ## Render Styles
210 Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern.
211 Solid color is simply defined as a color value, different kinds of paints can be created
212 using [linearGradient], [boxGradient], [radialGradient] and [imagePattern].
214 Current render style can be saved and restored using [save] and [restore].
216 Note that if you want "almost perfect" pixel rendering, you should set aspect ratio to 1,
217 and use `integerCoord+0.5f` as pixel coordinates.
219 render_transformations =
220 ## Render Transformations
222 Transformation matrix management for the current rendering style. Transformations are applied in
223 backwards order. I.e. if you first translate, and then rotate, your path will be rotated around
224 it's origin, and then translated to the destination point.
226 scissoring =
227 ## Scissoring
229 Scissoring allows you to clip the rendering into a rectangle. This is useful for various
230 user interface cases like rendering a text edit or a timeline.
232 images =
233 ## Images
235 NanoVega allows you to load image files in various formats (if arsd loaders are in place) to be used for rendering.
236 In addition you can upload your own image.
237 The parameter imageFlagsList is a list of flags defined in [NVGImageFlag].
239 If you will use your image as fill pattern, it will be scaled by default. To make it repeat, pass
240 [NVGImageFlag.RepeatX] and [NVGImageFlag.RepeatY] flags to image creation function respectively.
242 paints =
243 ## Paints
245 NanoVega supports four types of paints: linear gradient, box gradient, radial gradient and image pattern.
246 These can be used as paints for strokes and fills.
248 gpu_affine =
249 ## Render-Time Affine Transformations
251 It is possible to set affine transformation matrix for GPU. That matrix will
252 be applied by the shader code. This can be used to quickly translate and rotate
253 saved paths. Call this $(B only) between [beginFrame] and [endFrame].
255 Note that [beginFrame] resets this matrix to identity one.
257 $(WARNING Don't use this for scaling or skewing, or your image will be heavily distorted!)
259 paths =
260 ## Paths
262 Drawing a new shape starts with [beginPath], it clears all the currently defined paths.
263 Then you define one or more paths and sub-paths which describe the shape. The are functions
264 to draw common shapes like rectangles and circles, and lower level step-by-step functions,
265 which allow to define a path curve by curve.
267 NanoVega uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise
268 winding and holes should have counter clockwise order. To specify winding of a path you can
269 call [pathWinding]. This is useful especially for the common shapes, which are drawn CCW.
271 Finally you can fill the path using current fill style by calling [fill], and stroke it
272 with current stroke style by calling [stroke].
274 The curve segments and sub-paths are transformed by the current transform.
276 picking_api =
277 ## Picking API
279 This is picking API that works directly on paths, without rasterizing them first.
281 [beginFrame] resets picking state. Then you can create paths as usual, but
282 there is a possibility to perform hit checks $(B before) rasterizing a path.
283 Call either id assigning functions ([currFillHitId]/[currStrokeHitId]), or
284 immediate hit test functions ([hitTestCurrFill]/[hitTestCurrStroke])
285 before rasterizing (i.e. calling [fill] or [stroke]) to perform hover
286 effects, for example.
288 Also note that picking API is ignoring GPU affine transformation matrix.
289 You can "untransform" picking coordinates before checking with [gpuUntransformPoint].
291 $(WARNING Picking API completely ignores clipping. If you want to check for
292 clip regions, you have to manually register them as fill/stroke paths,
293 and perform the necessary logic. See [hitTestForId] function.)
295 clipping =
296 ## Clipping with paths
298 If scissoring is not enough for you, you can clip rendering with arbitrary path,
299 or with combination of paths. Clip region is saved by [save] and restored by
300 [restore] NanoVega functions. You can combine clip paths with various logic
301 operations, see [NVGClipMode].
303 Note that both [clip] and [clipStroke] are ignoring scissoring (i.e. clip mask
304 is created as if there was no scissor set). Actual rendering is affected by
305 scissors, though.
307 text_api =
308 ## Text
310 NanoVega allows you to load .ttf files and use the font to render text.
311 You have to load some font, and set proper font size before doing anything
312 with text, as there is no "default" font provided by NanoVega. Also, don't
313 forget to check return value of `createFont()`, 'cause NanoVega won't fail
314 if it cannot load font, it will silently try to render nothing.
316 The appearance of the text can be defined by setting the current text style
317 and by specifying the fill color. Common text and font settings such as
318 font size, letter spacing and text align are supported. Font blur allows you
319 to create simple text effects such as drop shadows.
321 At render time the font face can be set based on the font handles or name.
323 Font measure functions return values in local space, the calculations are
324 carried in the same resolution as the final rendering. This is done because
325 the text glyph positions are snapped to the nearest pixels sharp rendering.
327 The local space means that values are not rotated or scale as per the current
328 transformation. For example if you set font size to 12, which would mean that
329 line height is 16, then regardless of the current scaling and rotation, the
330 returned line height is always 16. Some measures may vary because of the scaling
331 since aforementioned pixel snapping.
333 While this may sound a little odd, the setup allows you to always render the
334 same way regardless of scaling. I.e. following works regardless of scaling:
336 ----------------------
337 string txt = "Text me up.";
338 vg.textBounds(x, y, txt, bounds);
339 vg.beginPath();
340 vg.roundedRect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1], 6);
341 vg.fill();
342 ----------------------
344 Note: currently only solid color fill is supported for text.
346 font_stash =
347 ## Low-Level Font Engine (FontStash)
349 FontStash is used to load fonts, to manage font atlases, and to get various text metrics.
350 You don't need any graphics context to use FontStash, so you can do things like text
351 layouting outside of your rendering code. Loaded fonts are refcounted, so it is cheap
352 to create new FontStash, copy fonts from NanoVega context into it, and use that new
353 FontStash to do some UI layouting, for example. Also note that you can get text metrics
354 without creating glyph bitmaps (by using [FONSTextBoundsIterator], for example); this way
355 you don't need to waste CPU and memory resources to render unneeded images into font atlas,
356 and you can layout alot of text very fast.
358 Note that "FontStash" is abbrevated as "FONS". So when you see some API that contains
359 word "fons" in it, this is not a typo, and it should not read "font" intead.
361 TODO for Ketmar: write some nice example code here, and finish documenting FontStash API.
363 module iv.nanovega.nanovega;
364 private:
366 version(aliced) {
367 import iv.meta;
368 import iv.vfs;
369 } else {
370 private alias usize = size_t;
371 // i fear phobos!
372 private template Unqual(T) {
373 static if (is(T U == immutable U)) alias Unqual = U;
374 else static if (is(T U == shared inout const U)) alias Unqual = U;
375 else static if (is(T U == shared inout U)) alias Unqual = U;
376 else static if (is(T U == shared const U)) alias Unqual = U;
377 else static if (is(T U == shared U)) alias Unqual = U;
378 else static if (is(T U == inout const U)) alias Unqual = U;
379 else static if (is(T U == inout U)) alias Unqual = U;
380 else static if (is(T U == const U)) alias Unqual = U;
381 else alias Unqual = T;
383 private template isAnyCharType(T, bool unqual=false) {
384 static if (unqual) private alias UT = Unqual!T; else private alias UT = T;
385 enum isAnyCharType = is(UT == char) || is(UT == wchar) || is(UT == dchar);
387 private template isWideCharType(T, bool unqual=false) {
388 static if (unqual) private alias UT = Unqual!T; else private alias UT = T;
389 enum isWideCharType = is(UT == wchar) || is(UT == dchar);
392 version(nanovg_disable_vfs) {
393 enum NanoVegaHasIVVFS = false;
394 } else {
395 static if (is(typeof((){import iv.vfs;}))) {
396 enum NanoVegaHasIVVFS = true;
397 import iv.vfs;
398 } else {
399 enum NanoVegaHasIVVFS = false;
403 // ////////////////////////////////////////////////////////////////////////// //
404 // engine
405 // ////////////////////////////////////////////////////////////////////////// //
406 import core.stdc.stdlib : malloc, realloc, free;
407 import core.stdc.string : memset, memcpy, strlen;
408 import std.math : PI;
410 //version = nanovg_force_stb_ttf;
412 version(Posix) {
413 version = nanovg_use_freetype;
414 } else {
415 version = nanovg_disable_fontconfig;
417 version(aliced) {
418 version = nanovg_default_no_font_aa;
419 version = nanovg_builtin_fontconfig_bindings;
420 version = nanovg_builtin_freetype_bindings;
421 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings
422 } else {
423 version = nanovg_builtin_fontconfig_bindings;
424 version = nanovg_builtin_freetype_bindings;
425 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings
428 version(nanovg_disable_fontconfig) {
429 public enum NanoVegaHasFontConfig = false;
430 } else {
431 public enum NanoVegaHasFontConfig = true;
432 version(nanovg_builtin_fontconfig_bindings) {} else import iv.fontconfig;
435 //version = nanovg_bench_flatten;
438 Annotation to indicate it is compatible with [arsd.script]
440 package(iv) enum scriptable = "arsd_jsvar_compatible";
442 public:
443 alias NVG_PI = PI;
445 enum NanoVegaHasArsdColor = (is(typeof((){ import arsd.color; })));
446 enum NanoVegaHasArsdImage = (is(typeof((){ import arsd.color; import arsd.image; })));
448 static if (NanoVegaHasArsdColor) private import arsd.color;
449 static if (NanoVegaHasArsdImage) {
450 private import arsd.image;
451 } else {
452 void stbi_set_unpremultiply_on_load (int flag_true_if_should_unpremultiply) {}
453 void stbi_convert_iphone_png_to_rgb (int flag_true_if_should_convert) {}
454 ubyte* stbi_load (const(char)* filename, int* x, int* y, int* comp, int req_comp) { return null; }
455 ubyte* stbi_load_from_memory (const(void)* buffer, int len, int* x, int* y, int* comp, int req_comp) { return null; }
456 void stbi_image_free (void* retval_from_stbi_load) {}
459 version(nanovg_default_no_font_aa) {
460 __gshared bool NVG_INVERT_FONT_AA = false;
461 } else {
462 __gshared bool NVG_INVERT_FONT_AA = true;
466 /// this is branchless for ints on x86, and even for longs on x86_64
467 public ubyte nvgClampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) {
468 static if (__VERSION__ > 2067) pragma(inline, true);
469 static if (T.sizeof == 2 || T.sizeof == 4) {
470 static if (__traits(isUnsigned, T)) {
471 return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24)));
472 } else {
473 n &= -cast(int)(n >= 0);
474 return cast(ubyte)(n|((255-cast(int)n)>>31));
476 } else static if (T.sizeof == 1) {
477 static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?");
478 return cast(ubyte)n;
479 } else static if (T.sizeof == 8) {
480 static if (__traits(isUnsigned, T)) {
481 return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56)));
482 } else {
483 n &= -cast(long)(n >= 0);
484 return cast(ubyte)(n|((255-cast(long)n)>>63));
486 } else {
487 static assert(false, "clampToByte: integer too big");
492 /// NanoVega RGBA color
493 /// Group: color_utils
494 public align(1) struct NVGColor {
495 align(1):
496 public:
497 float[4] rgba = 0; /// default color is transparent (a=1 is opaque)
499 public:
500 @property string toString () const @safe { import std.string : format; return "NVGColor(%s,%s,%s,%s)".format(r, g, b, a); }
502 public:
503 enum transparent = NVGColor(0.0f, 0.0f, 0.0f, 0.0f);
504 enum k8orange = NVGColor(1.0f, 0.5f, 0.0f, 1.0f);
506 enum aliceblue = NVGColor(240, 248, 255);
507 enum antiquewhite = NVGColor(250, 235, 215);
508 enum aqua = NVGColor(0, 255, 255);
509 enum aquamarine = NVGColor(127, 255, 212);
510 enum azure = NVGColor(240, 255, 255);
511 enum beige = NVGColor(245, 245, 220);
512 enum bisque = NVGColor(255, 228, 196);
513 enum black = NVGColor(0, 0, 0); // basic color
514 enum blanchedalmond = NVGColor(255, 235, 205);
515 enum blue = NVGColor(0, 0, 255); // basic color
516 enum blueviolet = NVGColor(138, 43, 226);
517 enum brown = NVGColor(165, 42, 42);
518 enum burlywood = NVGColor(222, 184, 135);
519 enum cadetblue = NVGColor(95, 158, 160);
520 enum chartreuse = NVGColor(127, 255, 0);
521 enum chocolate = NVGColor(210, 105, 30);
522 enum coral = NVGColor(255, 127, 80);
523 enum cornflowerblue = NVGColor(100, 149, 237);
524 enum cornsilk = NVGColor(255, 248, 220);
525 enum crimson = NVGColor(220, 20, 60);
526 enum cyan = NVGColor(0, 255, 255); // basic color
527 enum darkblue = NVGColor(0, 0, 139);
528 enum darkcyan = NVGColor(0, 139, 139);
529 enum darkgoldenrod = NVGColor(184, 134, 11);
530 enum darkgray = NVGColor(169, 169, 169);
531 enum darkgreen = NVGColor(0, 100, 0);
532 enum darkgrey = NVGColor(169, 169, 169);
533 enum darkkhaki = NVGColor(189, 183, 107);
534 enum darkmagenta = NVGColor(139, 0, 139);
535 enum darkolivegreen = NVGColor(85, 107, 47);
536 enum darkorange = NVGColor(255, 140, 0);
537 enum darkorchid = NVGColor(153, 50, 204);
538 enum darkred = NVGColor(139, 0, 0);
539 enum darksalmon = NVGColor(233, 150, 122);
540 enum darkseagreen = NVGColor(143, 188, 143);
541 enum darkslateblue = NVGColor(72, 61, 139);
542 enum darkslategray = NVGColor(47, 79, 79);
543 enum darkslategrey = NVGColor(47, 79, 79);
544 enum darkturquoise = NVGColor(0, 206, 209);
545 enum darkviolet = NVGColor(148, 0, 211);
546 enum deeppink = NVGColor(255, 20, 147);
547 enum deepskyblue = NVGColor(0, 191, 255);
548 enum dimgray = NVGColor(105, 105, 105);
549 enum dimgrey = NVGColor(105, 105, 105);
550 enum dodgerblue = NVGColor(30, 144, 255);
551 enum firebrick = NVGColor(178, 34, 34);
552 enum floralwhite = NVGColor(255, 250, 240);
553 enum forestgreen = NVGColor(34, 139, 34);
554 enum fuchsia = NVGColor(255, 0, 255);
555 enum gainsboro = NVGColor(220, 220, 220);
556 enum ghostwhite = NVGColor(248, 248, 255);
557 enum gold = NVGColor(255, 215, 0);
558 enum goldenrod = NVGColor(218, 165, 32);
559 enum gray = NVGColor(128, 128, 128); // basic color
560 enum green = NVGColor(0, 128, 0); // basic color
561 enum greenyellow = NVGColor(173, 255, 47);
562 enum grey = NVGColor(128, 128, 128); // basic color
563 enum honeydew = NVGColor(240, 255, 240);
564 enum hotpink = NVGColor(255, 105, 180);
565 enum indianred = NVGColor(205, 92, 92);
566 enum indigo = NVGColor(75, 0, 130);
567 enum ivory = NVGColor(255, 255, 240);
568 enum khaki = NVGColor(240, 230, 140);
569 enum lavender = NVGColor(230, 230, 250);
570 enum lavenderblush = NVGColor(255, 240, 245);
571 enum lawngreen = NVGColor(124, 252, 0);
572 enum lemonchiffon = NVGColor(255, 250, 205);
573 enum lightblue = NVGColor(173, 216, 230);
574 enum lightcoral = NVGColor(240, 128, 128);
575 enum lightcyan = NVGColor(224, 255, 255);
576 enum lightgoldenrodyellow = NVGColor(250, 250, 210);
577 enum lightgray = NVGColor(211, 211, 211);
578 enum lightgreen = NVGColor(144, 238, 144);
579 enum lightgrey = NVGColor(211, 211, 211);
580 enum lightpink = NVGColor(255, 182, 193);
581 enum lightsalmon = NVGColor(255, 160, 122);
582 enum lightseagreen = NVGColor(32, 178, 170);
583 enum lightskyblue = NVGColor(135, 206, 250);
584 enum lightslategray = NVGColor(119, 136, 153);
585 enum lightslategrey = NVGColor(119, 136, 153);
586 enum lightsteelblue = NVGColor(176, 196, 222);
587 enum lightyellow = NVGColor(255, 255, 224);
588 enum lime = NVGColor(0, 255, 0);
589 enum limegreen = NVGColor(50, 205, 50);
590 enum linen = NVGColor(250, 240, 230);
591 enum magenta = NVGColor(255, 0, 255); // basic color
592 enum maroon = NVGColor(128, 0, 0);
593 enum mediumaquamarine = NVGColor(102, 205, 170);
594 enum mediumblue = NVGColor(0, 0, 205);
595 enum mediumorchid = NVGColor(186, 85, 211);
596 enum mediumpurple = NVGColor(147, 112, 219);
597 enum mediumseagreen = NVGColor(60, 179, 113);
598 enum mediumslateblue = NVGColor(123, 104, 238);
599 enum mediumspringgreen = NVGColor(0, 250, 154);
600 enum mediumturquoise = NVGColor(72, 209, 204);
601 enum mediumvioletred = NVGColor(199, 21, 133);
602 enum midnightblue = NVGColor(25, 25, 112);
603 enum mintcream = NVGColor(245, 255, 250);
604 enum mistyrose = NVGColor(255, 228, 225);
605 enum moccasin = NVGColor(255, 228, 181);
606 enum navajowhite = NVGColor(255, 222, 173);
607 enum navy = NVGColor(0, 0, 128);
608 enum oldlace = NVGColor(253, 245, 230);
609 enum olive = NVGColor(128, 128, 0);
610 enum olivedrab = NVGColor(107, 142, 35);
611 enum orange = NVGColor(255, 165, 0);
612 enum orangered = NVGColor(255, 69, 0);
613 enum orchid = NVGColor(218, 112, 214);
614 enum palegoldenrod = NVGColor(238, 232, 170);
615 enum palegreen = NVGColor(152, 251, 152);
616 enum paleturquoise = NVGColor(175, 238, 238);
617 enum palevioletred = NVGColor(219, 112, 147);
618 enum papayawhip = NVGColor(255, 239, 213);
619 enum peachpuff = NVGColor(255, 218, 185);
620 enum peru = NVGColor(205, 133, 63);
621 enum pink = NVGColor(255, 192, 203);
622 enum plum = NVGColor(221, 160, 221);
623 enum powderblue = NVGColor(176, 224, 230);
624 enum purple = NVGColor(128, 0, 128);
625 enum red = NVGColor(255, 0, 0); // basic color
626 enum rosybrown = NVGColor(188, 143, 143);
627 enum royalblue = NVGColor(65, 105, 225);
628 enum saddlebrown = NVGColor(139, 69, 19);
629 enum salmon = NVGColor(250, 128, 114);
630 enum sandybrown = NVGColor(244, 164, 96);
631 enum seagreen = NVGColor(46, 139, 87);
632 enum seashell = NVGColor(255, 245, 238);
633 enum sienna = NVGColor(160, 82, 45);
634 enum silver = NVGColor(192, 192, 192);
635 enum skyblue = NVGColor(135, 206, 235);
636 enum slateblue = NVGColor(106, 90, 205);
637 enum slategray = NVGColor(112, 128, 144);
638 enum slategrey = NVGColor(112, 128, 144);
639 enum snow = NVGColor(255, 250, 250);
640 enum springgreen = NVGColor(0, 255, 127);
641 enum steelblue = NVGColor(70, 130, 180);
642 enum tan = NVGColor(210, 180, 140);
643 enum teal = NVGColor(0, 128, 128);
644 enum thistle = NVGColor(216, 191, 216);
645 enum tomato = NVGColor(255, 99, 71);
646 enum turquoise = NVGColor(64, 224, 208);
647 enum violet = NVGColor(238, 130, 238);
648 enum wheat = NVGColor(245, 222, 179);
649 enum white = NVGColor(255, 255, 255); // basic color
650 enum whitesmoke = NVGColor(245, 245, 245);
651 enum yellow = NVGColor(255, 255, 0); // basic color
652 enum yellowgreen = NVGColor(154, 205, 50);
654 nothrow @safe @nogc:
655 public:
657 this (ubyte ar, ubyte ag, ubyte ab, ubyte aa=255) pure {
658 pragma(inline, true);
659 r = ar/255.0f;
660 g = ag/255.0f;
661 b = ab/255.0f;
662 a = aa/255.0f;
666 this (float ar, float ag, float ab, float aa=1.0f) pure {
667 pragma(inline, true);
668 r = ar;
669 g = ag;
670 b = ab;
671 a = aa;
674 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
675 this (uint c) pure {
676 pragma(inline, true);
677 r = (c&0xff)/255.0f;
678 g = ((c>>8)&0xff)/255.0f;
679 b = ((c>>16)&0xff)/255.0f;
680 a = ((c>>24)&0xff)/255.0f;
683 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb"
684 this (const(char)[] srgb) {
685 static int c2d (char ch) pure nothrow @safe @nogc {
686 pragma(inline, true);
687 return
688 ch >= '0' && ch <= '9' ? ch-'0' :
689 ch >= 'A' && ch <= 'F' ? ch-'A'+10 :
690 ch >= 'a' && ch <= 'f' ? ch-'a'+10 :
693 int[8] digs;
694 int dc = -1;
695 foreach (immutable char ch; srgb) {
696 if (ch <= ' ') continue;
697 if (ch == '#') {
698 if (dc != -1) { dc = -1; break; }
699 dc = 0;
700 } else {
701 if (dc >= digs.length) { dc = -1; break; }
702 if ((digs[dc++] = c2d(ch)) < 0) { dc = -1; break; }
705 switch (dc) {
706 case 3: // rgb
707 a = 1.0f;
708 r = digs[0]/15.0f;
709 g = digs[1]/15.0f;
710 b = digs[2]/15.0f;
711 break;
712 case 4: // argb
713 a = digs[0]/15.0f;
714 r = digs[1]/15.0f;
715 g = digs[2]/15.0f;
716 b = digs[3]/15.0f;
717 break;
718 case 6: // rrggbb
719 a = 1.0f;
720 r = (digs[0]*16+digs[1])/255.0f;
721 g = (digs[2]*16+digs[3])/255.0f;
722 b = (digs[4]*16+digs[5])/255.0f;
723 break;
724 case 8: // aarrggbb
725 a = (digs[0]*16+digs[1])/255.0f;
726 r = (digs[2]*16+digs[3])/255.0f;
727 g = (digs[4]*16+digs[5])/255.0f;
728 b = (digs[6]*16+digs[7])/255.0f;
729 break;
730 default:
731 break;
735 /// Is this color completely opaque?
736 @property bool isOpaque () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] >= 1.0f); }
737 /// Is this color completely transparent?
738 @property bool isTransparent () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] <= 0.0f); }
740 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
741 @property uint asUint () const pure {
742 pragma(inline, true);
743 return
744 cast(uint)(r*255)|
745 (cast(uint)(g*255)<<8)|
746 (cast(uint)(b*255)<<16)|
747 (cast(uint)(a*255)<<24);
750 alias asUintABGR = asUint; /// Ditto.
752 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
753 static NVGColor fromUint (uint c) pure { pragma(inline, true); return NVGColor(c); }
755 alias fromUintABGR = fromUint; /// Ditto.
757 /// AARRGGBB
758 @property uint asUintARGB () const pure {
759 pragma(inline, true);
760 return
761 cast(uint)(b*255)|
762 (cast(uint)(g*255)<<8)|
763 (cast(uint)(r*255)<<16)|
764 (cast(uint)(a*255)<<24);
767 /// AARRGGBB
768 static NVGColor fromUintARGB (uint c) pure { pragma(inline, true); return NVGColor((c>>16)&0xff, (c>>8)&0xff, c&0xff, (c>>24)&0xff); }
770 @property ref inout(float) r () inout pure @trusted { pragma(inline, true); return rgba.ptr[0]; } ///
771 @property ref inout(float) g () inout pure @trusted { pragma(inline, true); return rgba.ptr[1]; } ///
772 @property ref inout(float) b () inout pure @trusted { pragma(inline, true); return rgba.ptr[2]; } ///
773 @property ref inout(float) a () inout pure @trusted { pragma(inline, true); return rgba.ptr[3]; } ///
775 ref NVGColor applyTint() (in auto ref NVGColor tint) nothrow @trusted @nogc {
776 if (tint.a == 0) return this;
777 foreach (immutable idx, ref float v; rgba[0..4]) {
778 v = nvg__clamp(v*tint.rgba.ptr[idx], 0.0f, 1.0f);
780 return this;
783 NVGHSL asHSL() (bool useWeightedLightness=false) const { pragma(inline, true); return NVGHSL.fromColor(this, useWeightedLightness); } ///
784 static fromHSL() (in auto ref NVGHSL hsl) { pragma(inline, true); return hsl.asColor; } ///
786 static if (NanoVegaHasArsdColor) {
787 Color toArsd () const { pragma(inline, true); return Color(cast(int)(r*255), cast(int)(g*255), cast(int)(b*255), cast(int)(a*255)); } ///
788 static NVGColor fromArsd (in Color c) { pragma(inline, true); return NVGColor(c.r, c.g, c.b, c.a); } ///
790 this (in Color c) {
791 version(aliced) pragma(inline, true);
792 r = c.r/255.0f;
793 g = c.g/255.0f;
794 b = c.b/255.0f;
795 a = c.a/255.0f;
801 /// NanoVega A-HSL color
802 /// Group: color_utils
803 public align(1) struct NVGHSL {
804 align(1):
805 float h=0, s=0, l=1, a=1; ///
807 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)); }
809 nothrow @safe @nogc:
810 public:
812 this (float ah, float as, float al, float aa=1) pure { pragma(inline, true); h = ah; s = as; l = al; a = aa; }
814 NVGColor asColor () const { pragma(inline, true); return nvgHSLA(h, s, l, a); } ///
816 // taken from Adam's arsd.color
817 /** Converts an RGB color into an HSL triplet.
818 * [useWeightedLightness] will try to get a better value for luminosity for the human eye,
819 * which is more sensitive to green than red and more to red than blue.
820 * If it is false, it just does average of the rgb. */
821 static NVGHSL fromColor() (in auto ref NVGColor c, bool useWeightedLightness=false) pure {
822 NVGHSL res;
823 res.a = c.a;
824 float r1 = c.r;
825 float g1 = c.g;
826 float b1 = c.b;
828 float maxColor = r1;
829 if (g1 > maxColor) maxColor = g1;
830 if (b1 > maxColor) maxColor = b1;
831 float minColor = r1;
832 if (g1 < minColor) minColor = g1;
833 if (b1 < minColor) minColor = b1;
835 res.l = (maxColor+minColor)/2;
836 if (useWeightedLightness) {
837 // the colors don't affect the eye equally
838 // this is a little more accurate than plain HSL numbers
839 res.l = 0.2126*r1+0.7152*g1+0.0722*b1;
841 if (maxColor != minColor) {
842 if (res.l < 0.5) {
843 res.s = (maxColor-minColor)/(maxColor+minColor);
844 } else {
845 res.s = (maxColor-minColor)/(2.0-maxColor-minColor);
847 if (r1 == maxColor) {
848 res.h = (g1-b1)/(maxColor-minColor);
849 } else if(g1 == maxColor) {
850 res.h = 2.0+(b1-r1)/(maxColor-minColor);
851 } else {
852 res.h = 4.0+(r1-g1)/(maxColor-minColor);
856 res.h = res.h*60;
857 if (res.h < 0) res.h += 360;
858 res.h /= 360;
860 return res;
865 //version = nanovega_debug_image_manager;
866 //version = nanovega_debug_image_manager_rc;
868 /** NanoVega image handle.
870 * This is refcounted struct, so you don't need to do anything special to free it once it is allocated.
872 * Group: images
874 struct NVGImage {
875 private:
876 NVGContext ctx;
877 int id; // backend image id
879 public:
881 this() (in auto ref NVGImage src) nothrow @trusted @nogc {
882 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); }
883 if (src.id > 0 && src.ctx !is null) {
884 ctx = cast(NVGContext)src.ctx;
885 id = src.id;
886 ctx.nvg__imageIncRef(id);
891 ~this () nothrow @trusted @nogc { version(aliced) pragma(inline, true); clear(); }
894 this (this) nothrow @trusted @nogc {
895 version(aliced) pragma(inline, true);
896 if (id > 0 && ctx !is null) {
897 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p postblit (imgid=%d)\n", &this, id); }
898 ctx.nvg__imageIncRef(id);
903 void opAssign() (in auto ref NVGImage src) nothrow @trusted @nogc {
904 if (src.id <= 0 || src.ctx is null) {
905 clear();
906 } else {
907 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); }
908 if (src.id > 0 && src.ctx !is null) (cast(NVGContext)src.ctx).nvg__imageIncRef(src.id);
909 if (id > 0 && ctx !is null) ctx.nvg__imageDecRef(id);
910 ctx = cast(NVGContext)src.ctx;
911 id = src.id;
915 /// Free this image.
916 void clear () nothrow @trusted @nogc {
917 if (id > 0 && ctx !is null) {
918 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p cleared (imgid=%d)\n", &this, id); }
919 ctx.nvg__imageDecRef(id);
921 id = 0;
922 ctx = null;
925 /// Is this image valid?
926 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (id > 0 && ctx.valid); }
928 /// Is the given image valid and comes from the same context?
929 @property bool isSameContext (const(NVGContext) actx) const pure nothrow @safe @nogc { pragma(inline, true); return (actx !is null && ctx is actx); }
931 /// Returns image width, or zero for invalid image.
932 int width () const nothrow @trusted @nogc {
933 int w = 0;
934 if (valid) {
935 int h = void;
936 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h);
938 return w;
941 /// Returns image height, or zero for invalid image.
942 int height () const nothrow @trusted @nogc {
943 int h = 0;
944 if (valid) {
945 int w = void;
946 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h);
948 return h;
953 /// Paint parameters for various fills. Don't change anything here!
954 /// Group: render_styles
955 public struct NVGPaint {
956 NVGMatrix xform;
957 float[2] extent = 0.0f;
958 float radius = 0.0f;
959 float feather = 0.0f;
960 NVGColor innerColor; /// this can be used to modulate images (fill/font)
961 NVGColor middleColor;
962 NVGColor outerColor;
963 float midp = -1; // middle stop for 3-color gradient
964 NVGImage image;
965 bool simpleColor; /// if `true`, only innerColor is used, and this is solid-color paint
967 this() (in auto ref NVGPaint p) nothrow @trusted @nogc {
968 xform = p.xform;
969 extent[] = p.extent[];
970 radius = p.radius;
971 feather = p.feather;
972 innerColor = p.innerColor;
973 middleColor = p.middleColor;
974 midp = p.midp;
975 outerColor = p.outerColor;
976 image = p.image;
977 simpleColor = p.simpleColor;
980 void opAssign() (in auto ref NVGPaint p) nothrow @trusted @nogc {
981 xform = p.xform;
982 extent[] = p.extent[];
983 radius = p.radius;
984 feather = p.feather;
985 innerColor = p.innerColor;
986 middleColor = p.middleColor;
987 midp = p.midp;
988 outerColor = p.outerColor;
989 image = p.image;
990 simpleColor = p.simpleColor;
993 void clear () nothrow @trusted @nogc {
994 version(aliced) pragma(inline, true);
995 import core.stdc.string : memset;
996 image.clear();
997 memset(&this, 0, this.sizeof);
998 simpleColor = true;
1002 /// Path winding.
1003 /// Group: paths
1004 public enum NVGWinding {
1005 CCW = 1, /// Winding for solid shapes
1006 CW = 2, /// Winding for holes
1009 /// Path solidity.
1010 /// Group: paths
1011 public enum NVGSolidity {
1012 Solid = 1, /// Solid shape (CCW winding).
1013 Hole = 2, /// Hole (CW winding).
1016 /// Line cap style.
1017 /// Group: render_styles
1018 public enum NVGLineCap {
1019 Butt, ///
1020 Round, ///
1021 Square, ///
1022 Bevel, ///
1023 Miter, ///
1026 /// Text align.
1027 /// Group: text_api
1028 public align(1) struct NVGTextAlign {
1029 align(1):
1030 /// Horizontal align.
1031 enum H : ubyte {
1032 Left = 0, /// Default, align text horizontally to left.
1033 Center = 1, /// Align text horizontally to center.
1034 Right = 2, /// Align text horizontally to right.
1037 /// Vertical align.
1038 enum V : ubyte {
1039 Baseline = 0, /// Default, align text vertically to baseline.
1040 Top = 1, /// Align text vertically to top.
1041 Middle = 2, /// Align text vertically to middle.
1042 Bottom = 3, /// Align text vertically to bottom.
1045 pure nothrow @safe @nogc:
1046 public:
1047 this (H h) { pragma(inline, true); value = h; } ///
1048 this (V v) { pragma(inline, true); value = cast(ubyte)(v<<4); } ///
1049 this (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1050 this (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1051 void reset () { pragma(inline, true); value = 0; } ///
1052 void reset (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1053 void reset (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1054 @property:
1055 bool left () const { pragma(inline, true); return ((value&0x0f) == H.Left); } ///
1056 void left (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Left : 0)); } ///
1057 bool center () const { pragma(inline, true); return ((value&0x0f) == H.Center); } ///
1058 void center (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Center : 0)); } ///
1059 bool right () const { pragma(inline, true); return ((value&0x0f) == H.Right); } ///
1060 void right (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Right : 0)); } ///
1062 bool baseline () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Baseline); } ///
1063 void baseline (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Baseline<<4 : 0)); } ///
1064 bool top () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Top); } ///
1065 void top (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Top<<4 : 0)); } ///
1066 bool middle () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Middle); } ///
1067 void middle (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Middle<<4 : 0)); } ///
1068 bool bottom () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Bottom); } ///
1069 void bottom (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Bottom<<4 : 0)); } ///
1071 H horizontal () const { pragma(inline, true); return cast(H)(value&0x0f); } ///
1072 void horizontal (H v) { pragma(inline, true); value = (value&0xf0)|v; } ///
1074 V vertical () const { pragma(inline, true); return cast(V)((value>>4)&0x0f); } ///
1075 void vertical (V v) { pragma(inline, true); value = (value&0x0f)|cast(ubyte)(v<<4); } ///
1077 private:
1078 ubyte value = 0; // low nibble: horizontal; high nibble: vertical
1081 /// Blending type.
1082 /// Group: composite_operation
1083 public enum NVGBlendFactor {
1084 Zero = 1<<0, ///
1085 One = 1<<1, ///
1086 SrcColor = 1<<2, ///
1087 OneMinusSrcColor = 1<<3, ///
1088 DstColor = 1<<4, ///
1089 OneMinusDstColor = 1<<5, ///
1090 SrcAlpha = 1<<6, ///
1091 OneMinusSrcAlpha = 1<<7, ///
1092 DstAlpha = 1<<8, ///
1093 OneMinusDstAlpha = 1<<9, ///
1094 SrcAlphaSaturate = 1<<10, ///
1097 /// Composite operation (HTML5-alike).
1098 /// Group: composite_operation
1099 public enum NVGCompositeOperation {
1100 SourceOver, ///
1101 SourceIn, ///
1102 SourceOut, ///
1103 SourceAtop, ///
1104 DestinationOver, ///
1105 DestinationIn, ///
1106 DestinationOut, ///
1107 DestinationAtop, ///
1108 Lighter, ///
1109 Copy, ///
1110 Xor, ///
1113 /// Composite operation state.
1114 /// Group: composite_operation
1115 public struct NVGCompositeOperationState {
1116 bool simple; /// `true`: use `glBlendFunc()` instead of `glBlendFuncSeparate()`
1117 NVGBlendFactor srcRGB; ///
1118 NVGBlendFactor dstRGB; ///
1119 NVGBlendFactor srcAlpha; ///
1120 NVGBlendFactor dstAlpha; ///
1123 /// Mask combining more
1124 /// Group: clipping
1125 public enum NVGClipMode {
1126 None, /// normal rendering (i.e. render path instead of modifying clip region)
1127 Union, /// old mask will be masked with the current one; this is the default mode for [clip]
1128 Or, /// new mask will be added to the current one (logical `OR` operation);
1129 Xor, /// new mask will be logically `XOR`ed with the current one
1130 Sub, /// "subtract" current path from mask
1131 Replace, /// replace current mask
1132 Add = Or, /// Synonym
1135 /// Glyph position info.
1136 /// Group: text_api
1137 public struct NVGGlyphPosition {
1138 usize strpos; /// Position of the glyph in the input string.
1139 float x; /// The x-coordinate of the logical glyph position.
1140 float minx, maxx; /// The bounds of the glyph shape.
1143 /// Text row storage.
1144 /// Group: text_api
1145 public struct NVGTextRow(CT) if (isAnyCharType!CT) {
1146 alias CharType = CT;
1147 const(CT)[] s;
1148 int start; /// Index in the input text where the row starts.
1149 int end; /// Index in the input text where the row ends (one past the last character).
1150 float width; /// Logical width of the row.
1151 float minx, maxx; /// Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending.
1152 /// Get rest of the string.
1153 @property const(CT)[] rest () const pure nothrow @trusted @nogc { pragma(inline, true); return (end <= s.length ? s[end..$] : null); }
1154 /// Get current row.
1155 @property const(CT)[] row () const pure nothrow @trusted @nogc { pragma(inline, true); return s[start..end]; }
1156 @property const(CT)[] string () const pure nothrow @trusted @nogc { pragma(inline, true); return s; }
1157 @property void string(CT) (const(CT)[] v) pure nothrow @trusted @nogc { pragma(inline, true); s = v; }
1160 /// Image creation flags.
1161 /// Group: images
1162 public enum NVGImageFlag : uint {
1163 None = 0, /// Nothing special.
1164 GenerateMipmaps = 1<<0, /// Generate mipmaps during creation of the image.
1165 RepeatX = 1<<1, /// Repeat image in X direction.
1166 RepeatY = 1<<2, /// Repeat image in Y direction.
1167 FlipY = 1<<3, /// Flips (inverses) image in Y direction when rendered.
1168 Premultiplied = 1<<4, /// Image data has premultiplied alpha.
1169 NoFiltering = 1<<8, /// use GL_NEAREST instead of GL_LINEAR
1170 Nearest = NoFiltering, /// compatibility with original NanoVG
1171 NoDelete = 1<<16,/// Do not delete GL texture handle.
1174 alias NVGImageFlags = NVGImageFlag; /// Backwards compatibility for [NVGImageFlag].
1177 // ////////////////////////////////////////////////////////////////////////// //
1178 private:
1180 static T* xdup(T) (const(T)* ptr, int count) nothrow @trusted @nogc {
1181 import core.stdc.stdlib : malloc;
1182 import core.stdc.string : memcpy;
1183 if (count == 0) return null;
1184 T* res = cast(T*)malloc(T.sizeof*count);
1185 if (res is null) assert(0, "NanoVega: out of memory");
1186 memcpy(res, ptr, T.sizeof*count);
1187 return res;
1190 // Internal Render API
1191 enum NVGtexture {
1192 Alpha = 0x01,
1193 RGBA = 0x02,
1196 struct NVGscissor {
1197 NVGMatrix xform;
1198 float[2] extent = -1.0f;
1201 /// General NanoVega vertex struct. Contains geometry coordinates, and (sometimes unused) texture coordinates.
1202 public struct NVGVertex {
1203 float x, y, u, v;
1206 struct NVGpath {
1207 int first;
1208 int count;
1209 bool closed;
1210 int nbevel;
1211 NVGVertex* fill;
1212 int nfill;
1213 NVGVertex* stroke;
1214 int nstroke;
1215 NVGWinding mWinding;
1216 bool convex;
1217 bool cloned;
1219 @disable this (this); // no copies
1220 void opAssign() (in auto ref NVGpath a) { static assert(0, "no copies!"); }
1222 void clear () nothrow @trusted @nogc {
1223 import core.stdc.stdlib : free;
1224 import core.stdc.string : memset;
1225 if (cloned) {
1226 if (stroke !is null && stroke !is fill) free(stroke);
1227 if (fill !is null) free(fill);
1229 memset(&this, 0, this.sizeof);
1232 // won't clear current path
1233 void copyFrom (const NVGpath* src) nothrow @trusted @nogc {
1234 import core.stdc.string : memcpy;
1235 assert(src !is null);
1236 memcpy(&this, src, NVGpath.sizeof);
1237 this.fill = xdup(src.fill, src.nfill);
1238 if (src.stroke is src.fill) {
1239 this.stroke = this.fill;
1240 } else {
1241 this.stroke = xdup(src.stroke, src.nstroke);
1243 this.cloned = true;
1246 public @property const(NVGVertex)[] fillVertices () const pure nothrow @trusted @nogc {
1247 pragma(inline, true);
1248 return (nfill > 0 ? fill[0..nfill] : null);
1251 public @property const(NVGVertex)[] strokeVertices () const pure nothrow @trusted @nogc {
1252 pragma(inline, true);
1253 return (nstroke > 0 ? stroke[0..nstroke] : null);
1256 public @property NVGWinding winding () const pure nothrow @trusted @nogc { pragma(inline, true); return mWinding; }
1257 public @property bool complex () const pure nothrow @trusted @nogc { pragma(inline, true); return !convex; }
1261 struct NVGparams {
1262 void* userPtr;
1263 bool edgeAntiAlias;
1264 bool fontAA;
1265 bool function (void* uptr) nothrow @trusted @nogc renderCreate;
1266 int function (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc renderCreateTexture;
1267 bool function (void* uptr, int image) nothrow @trusted @nogc renderTextureIncRef;
1268 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
1269 bool function (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc renderUpdateTexture;
1270 bool function (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc renderGetTextureSize;
1271 void function (void* uptr, int width, int height) nothrow @trusted @nogc renderViewport; // called in [beginFrame]
1272 void function (void* uptr) nothrow @trusted @nogc renderCancel;
1273 void function (void* uptr) nothrow @trusted @nogc renderFlush;
1274 void function (void* uptr) nothrow @trusted @nogc renderPushClip; // backend should support stack of at least [NVG_MAX_STATES] elements
1275 void function (void* uptr) nothrow @trusted @nogc renderPopClip; // backend should support stack of at least [NVG_MAX_STATES] elements
1276 void function (void* uptr) nothrow @trusted @nogc renderResetClip; // reset current clip region to `non-clipped`
1277 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;
1278 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;
1279 void function (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc renderTriangles;
1280 void function (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc renderSetAffine;
1281 void function (void* uptr) nothrow @trusted @nogc renderDelete;
1284 // ////////////////////////////////////////////////////////////////////////// //
1285 private:
1287 enum NVG_INIT_FONTIMAGE_SIZE = 512;
1288 enum NVG_MAX_FONTIMAGE_SIZE = 2048;
1289 enum NVG_MAX_FONTIMAGES = 4;
1291 enum NVG_INIT_COMMANDS_SIZE = 256;
1292 enum NVG_INIT_POINTS_SIZE = 128;
1293 enum NVG_INIT_PATHS_SIZE = 16;
1294 enum NVG_INIT_VERTS_SIZE = 256;
1295 enum NVG_MAX_STATES = 32;
1297 public enum NVG_KAPPA90 = 0.5522847493f; /// Length proportional to radius of a cubic bezier handle for 90deg arcs.
1298 enum NVG_MIN_FEATHER = 0.001f; // it should be greater than zero, 'cause it is used in shader for divisions
1300 enum Command {
1301 MoveTo = 0,
1302 LineTo = 1,
1303 BezierTo = 2,
1304 Close = 3,
1305 Winding = 4,
1308 enum PointFlag : int {
1309 Corner = 0x01,
1310 Left = 0x02,
1311 Bevel = 0x04,
1312 InnerBevelPR = 0x08,
1315 struct NVGstate {
1316 NVGCompositeOperationState compositeOperation;
1317 bool shapeAntiAlias = true;
1318 NVGPaint fill;
1319 NVGPaint stroke;
1320 float strokeWidth = 1.0f;
1321 float miterLimit = 10.0f;
1322 NVGLineCap lineJoin = NVGLineCap.Miter;
1323 NVGLineCap lineCap = NVGLineCap.Butt;
1324 float alpha = 1.0f;
1325 NVGMatrix xform;
1326 NVGscissor scissor;
1327 float fontSize = 16.0f;
1328 float letterSpacing = 0.0f;
1329 float lineHeight = 1.0f;
1330 float fontBlur = 0.0f;
1331 NVGTextAlign textAlign;
1332 int fontId = 0;
1333 bool evenOddMode = false; // use even-odd filling rule (required for some svgs); otherwise use non-zero fill
1334 // dashing
1335 enum MaxDashes = 32; // max 16 dashes
1336 float[MaxDashes] dashes;
1337 uint dashCount = 0;
1338 uint lastFlattenDashCount = 0;
1339 float dashStart = 0;
1340 float totalDashLen;
1341 bool firstDashIsGap = false;
1342 // dasher state for flattener
1343 bool dasherActive = false;
1345 void clearPaint () nothrow @trusted @nogc {
1346 fill.clear();
1347 stroke.clear();
1351 struct NVGpoint {
1352 float x, y;
1353 float dx, dy;
1354 float len;
1355 float dmx, dmy;
1356 ubyte flags;
1359 struct NVGpathCache {
1360 NVGpoint* points;
1361 int npoints;
1362 int cpoints;
1363 NVGpath* paths;
1364 int npaths;
1365 int cpaths;
1366 NVGVertex* verts;
1367 int nverts;
1368 int cverts;
1369 float[4] bounds;
1370 // this is required for saved paths
1371 bool strokeReady;
1372 bool fillReady;
1373 float strokeAlphaMul;
1374 float strokeWidth;
1375 float fringeWidth;
1376 bool evenOddMode;
1377 NVGClipMode clipmode;
1378 // non-saved path will not have this
1379 float* commands;
1380 int ncommands;
1382 @disable this (this); // no copies
1383 void opAssign() (in auto ref NVGpathCache a) { static assert(0, "no copies!"); }
1385 // won't clear current path
1386 void copyFrom (const NVGpathCache* src) nothrow @trusted @nogc {
1387 import core.stdc.stdlib : malloc;
1388 import core.stdc.string : memcpy, memset;
1389 assert(src !is null);
1390 memcpy(&this, src, NVGpathCache.sizeof);
1391 this.points = xdup(src.points, src.npoints);
1392 this.cpoints = src.npoints;
1393 this.verts = xdup(src.verts, src.nverts);
1394 this.cverts = src.nverts;
1395 this.commands = xdup(src.commands, src.ncommands);
1396 if (src.npaths > 0) {
1397 this.paths = cast(NVGpath*)malloc(src.npaths*NVGpath.sizeof);
1398 memset(this.paths, 0, npaths*NVGpath.sizeof);
1399 foreach (immutable pidx; 0..npaths) this.paths[pidx].copyFrom(&src.paths[pidx]);
1400 this.cpaths = src.npaths;
1401 } else {
1402 this.npaths = this.cpaths = 0;
1406 void clear () nothrow @trusted @nogc {
1407 import core.stdc.stdlib : free;
1408 import core.stdc.string : memset;
1409 if (paths !is null) {
1410 foreach (ref p; paths[0..npaths]) p.clear();
1411 free(paths);
1413 if (points !is null) free(points);
1414 if (verts !is null) free(verts);
1415 if (commands !is null) free(commands);
1416 memset(&this, 0, this.sizeof);
1420 /// Pointer to opaque NanoVega context structure.
1421 /// Group: context_management
1422 public alias NVGContext = NVGcontextinternal*;
1424 /// FontStash context
1425 /// Group: font_stash
1426 public alias FONSContext = FONScontextInternal*;
1428 /// Returns FontStash context of the given NanoVega context.
1429 /// Group: font_stash
1430 public FONSContext fonsContext (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.fs : null); }
1432 /// Returns scale that should be applied to FontStash parameters due to matrix transformations on the context (or 1)
1433 /// Group: font_stash
1434 public float fonsScale (NVGContext ctx) /*pure*/ nothrow @trusted @nogc {
1435 pragma(inline, true);
1436 return (ctx !is null && ctx.contextAlive && ctx.nstates > 0 ? nvg__getFontScale(&ctx.states.ptr[ctx.nstates-1])*ctx.devicePxRatio : 1);
1439 /// Setup FontStash from the given NanoVega context font parameters. Note that this will apply transformation scale too.
1440 /// Returns `false` if FontStash or NanoVega context is not active.
1441 /// Group: font_stash
1442 public bool setupFonsFrom (FONSContext stash, NVGContext ctx) nothrow @trusted @nogc {
1443 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false;
1444 NVGstate* state = nvg__getState(ctx);
1445 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
1446 stash.size = state.fontSize*scale;
1447 stash.spacing = state.letterSpacing*scale;
1448 stash.blur = state.fontBlur*scale;
1449 stash.textAlign = state.textAlign;
1450 stash.fontId = state.fontId;
1451 return true;
1454 /// Setup NanoVega context font parameters from the given FontStash. Note that NanoVega can apply transformation scale later.
1455 /// Returns `false` if FontStash or NanoVega context is not active.
1456 /// Group: font_stash
1457 public bool setupCtxFrom (NVGContext ctx, FONSContext stash) nothrow @trusted @nogc {
1458 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false;
1459 NVGstate* state = nvg__getState(ctx);
1460 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
1461 state.fontSize = stash.size;
1462 state.letterSpacing = stash.spacing;
1463 state.fontBlur = stash.blur;
1464 state.textAlign = stash.textAlign;
1465 state.fontId = stash.fontId;
1466 return true;
1469 /** Bezier curve rasterizer.
1471 * De Casteljau Bezier rasterizer is faster, but currently rasterizing curves with cusps sligtly wrong.
1472 * It doesn't really matter in practice.
1474 * AFD tesselator is somewhat slower, but does cusps better.
1476 * McSeem rasterizer should have the best quality, bit it is the slowest method. Basically, you will
1477 * never notice any visial difference (and this code is not really debugged), so you probably should
1478 * not use it. It is there for further experiments.
1480 public enum NVGTesselation {
1481 DeCasteljau, /// default: standard well-known tesselation algorithm
1482 AFD, /// adaptive forward differencing
1483 DeCasteljauMcSeem, /// standard well-known tesselation algorithm, with improvements from Maxim Shemanarev; slowest one, but should give best results
1486 /// Default tesselator for Bezier curves.
1487 public __gshared NVGTesselation NVG_DEFAULT_TESSELATOR = NVGTesselation.DeCasteljau;
1490 // some public info
1492 /// valid only inside [beginFrame]/[endFrame]
1493 /// Group: context_management
1494 public int width (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mWidth : 0); }
1496 /// valid only inside [beginFrame]/[endFrame]
1497 /// Group: context_management
1498 public int height (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mHeight : 0); }
1500 /// valid only inside [beginFrame]/[endFrame]
1501 /// Group: context_management
1502 public float devicePixelRatio (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.devicePxRatio : float.nan); }
1504 /// Returns `true` if [beginFrame] was called, and neither [endFrame], nor [cancelFrame] were.
1505 /// Group: context_management
1506 public bool inFrame (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.nstates > 0 : false); }
1508 // path autoregistration
1510 /// [pickid] to stop autoregistration.
1511 /// Group: context_management
1512 public enum NVGNoPick = -1;
1514 /// >=0: this pickid will be assigned to all filled/stroked paths
1515 /// Group: context_management
1516 public int pickid (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickId : NVGNoPick); }
1518 /// >=0: this pickid will be assigned to all filled/stroked paths
1519 /// Group: context_management
1520 public void pickid (NVGContext ctx, int v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.pathPickId = v; }
1522 /// pick autoregistration mode; see [NVGPickKind]
1523 /// Group: context_management
1524 public uint pickmode (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickRegistered&NVGPickKind.All : 0); }
1526 /// pick autoregistration mode; see [NVGPickKind]
1527 /// Group: context_management
1528 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); }
1530 // tesselator options
1532 /// Get current Bezier tesselation mode. See [NVGTesselation].
1533 /// Group: context_management
1534 public NVGTesselation tesselation (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.tesselatortype : NVGTesselation.DeCasteljau); }
1536 /// Set current Bezier tesselation mode. See [NVGTesselation].
1537 /// Group: context_management
1538 public void tesselation (NVGContext ctx, NVGTesselation v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.tesselatortype = v; }
1541 private struct NVGcontextinternal {
1542 private:
1543 NVGparams params;
1544 float* commands;
1545 int ccommands;
1546 int ncommands;
1547 float commandx, commandy;
1548 NVGstate[NVG_MAX_STATES] states;
1549 int nstates;
1550 NVGpathCache* cache;
1551 public float tessTol;
1552 public float angleTol; // 0.0f -- angle tolerance for McSeem Bezier rasterizer
1553 public float cuspLimit; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps)
1554 float distTol;
1555 public float fringeWidth;
1556 float devicePxRatio;
1557 FONSContext fs;
1558 NVGImage[NVG_MAX_FONTIMAGES] fontImages;
1559 int fontImageIdx;
1560 int drawCallCount;
1561 int fillTriCount;
1562 int strokeTriCount;
1563 int textTriCount;
1564 NVGTesselation tesselatortype;
1565 // picking API
1566 NVGpickScene* pickScene;
1567 int pathPickId; // >=0: register all paths for picking using this id
1568 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
1569 // path recording
1570 NVGPathSet recset;
1571 int recstart; // used to cancel recording
1572 bool recblockdraw;
1573 // internals
1574 NVGMatrix gpuAffine;
1575 int mWidth, mHeight;
1576 // image manager
1577 shared int imageCount; // number of alive images in this context
1578 bool contextAlive; // context can be dead, but still contain some images
1580 @disable this (this); // no copies
1581 void opAssign() (in auto ref NVGcontextinternal a) { static assert(0, "no copies!"); }
1583 // debug feature
1584 public @property int getImageCount () nothrow @trusted @nogc {
1585 import core.atomic;
1586 return atomicLoad(imageCount);
1590 /** Returns number of tesselated pathes in context.
1592 * Tesselated pathes are either triangle strips (for strokes), or
1593 * triangle fans (for fills). Note that NanoVega can generate some
1594 * surprising pathes (like fringe stroke for antialiasing, for example).
1596 * One render path can contain vertices both for fill, and for stroke triangles.
1598 public int renderPathCount (NVGContext ctx) pure nothrow @trusted @nogc {
1599 pragma(inline, true);
1600 return (ctx !is null && ctx.contextAlive ? ctx.cache.npaths : 0);
1603 /** Get vertices of "fill" triangle fan for the given render path. Can return empty slice.
1605 * $(WARNING Returned slice can be invalidated by any other NanoVega API call
1606 * (except the calls to render path accessors), and using it in such
1607 * case is UB. So copy vertices to freshly allocated array if you want
1608 * to keep them for further processing.)
1610 public const(NVGVertex)[] renderPathFillVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1611 pragma(inline, true);
1612 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].fillVertices : null);
1615 /** Get vertices of "stroke" triangle strip for the given render path. Can return empty slice.
1617 * $(WARNING Returned slice can be invalidated by any other NanoVega API call
1618 * (except the calls to render path accessors), and using it in such
1619 * case is UB. So copy vertices to freshly allocated array if you want
1620 * to keep them for further processing.)
1622 public const(NVGVertex)[] renderPathStrokeVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1623 pragma(inline, true);
1624 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].strokeVertices : null);
1628 /// Returns winding for the given render path.
1629 public NVGWinding renderPathWinding (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1630 pragma(inline, true);
1631 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].winding : NVGWinding.CCW);
1635 /// Returns "complex path" flag for the given render path.
1636 public bool renderPathComplex (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1637 pragma(inline, true);
1638 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].complex : false);
1642 void nvg__imageIncRef (NVGContext ctx, int imgid, bool increfInGL=true) nothrow @trusted @nogc {
1643 if (ctx !is null && imgid > 0) {
1644 import core.atomic : atomicOp;
1645 atomicOp!"+="(ctx.imageCount, 1);
1646 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[++]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); }
1647 if (ctx.contextAlive && increfInGL) ctx.params.renderTextureIncRef(ctx.params.userPtr, imgid);
1651 void nvg__imageDecRef (NVGContext ctx, int imgid) nothrow @trusted @nogc {
1652 if (ctx !is null && imgid > 0) {
1653 import core.atomic : atomicOp;
1654 int icnt = atomicOp!"-="(ctx.imageCount, 1);
1655 if (icnt < 0) assert(0, "NanoVega: internal image refcounting error");
1656 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[--]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); }
1657 if (ctx.contextAlive) ctx.params.renderDeleteTexture(ctx.params.userPtr, imgid);
1658 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); }
1659 if (!ctx.contextAlive && icnt == 0) {
1660 // it is finally safe to free context memory
1661 import core.stdc.stdlib : free;
1662 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("killed zombie context %p\n", ctx); }
1663 free(ctx);
1669 public import core.stdc.math :
1670 nvg__sqrtf = sqrtf,
1671 nvg__modf = fmodf,
1672 nvg__sinf = sinf,
1673 nvg__cosf = cosf,
1674 nvg__tanf = tanf,
1675 nvg__atan2f = atan2f,
1676 nvg__acosf = acosf,
1677 nvg__ceilf = ceilf;
1679 version(Windows) {
1680 public int nvg__lrintf (float f) nothrow @trusted @nogc { pragma(inline, true); return cast(int)(f+0.5); }
1681 } else {
1682 public import core.stdc.math : nvg__lrintf = lrintf;
1685 public auto nvg__min(T) (T a, T b) { pragma(inline, true); return (a < b ? a : b); }
1686 public auto nvg__max(T) (T a, T b) { pragma(inline, true); return (a > b ? a : b); }
1687 public auto nvg__clamp(T) (T a, T mn, T mx) { pragma(inline, true); return (a < mn ? mn : (a > mx ? mx : a)); }
1688 //float nvg__absf() (float a) { pragma(inline, true); return (a >= 0.0f ? a : -a); }
1689 public auto nvg__sign(T) (T a) { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); }
1690 public float nvg__cross() (float dx0, float dy0, float dx1, float dy1) { pragma(inline, true); return (dx1*dy0-dx0*dy1); }
1692 //public import core.stdc.math : nvg__absf = fabsf;
1693 public import core.math : nvg__absf = fabs;
1696 float nvg__normalize (float* x, float* y) nothrow @safe @nogc {
1697 float d = nvg__sqrtf((*x)*(*x)+(*y)*(*y));
1698 if (d > 1e-6f) {
1699 immutable float id = 1.0f/d;
1700 *x *= id;
1701 *y *= id;
1703 return d;
1706 void nvg__deletePathCache (ref NVGpathCache* c) nothrow @trusted @nogc {
1707 if (c !is null) {
1708 c.clear();
1709 free(c);
1713 NVGpathCache* nvg__allocPathCache () nothrow @trusted @nogc {
1714 NVGpathCache* c = cast(NVGpathCache*)malloc(NVGpathCache.sizeof);
1715 if (c is null) goto error;
1716 memset(c, 0, NVGpathCache.sizeof);
1718 c.points = cast(NVGpoint*)malloc(NVGpoint.sizeof*NVG_INIT_POINTS_SIZE);
1719 if (c.points is null) goto error;
1720 assert(c.npoints == 0);
1721 c.cpoints = NVG_INIT_POINTS_SIZE;
1723 c.paths = cast(NVGpath*)malloc(NVGpath.sizeof*NVG_INIT_PATHS_SIZE);
1724 if (c.paths is null) goto error;
1725 assert(c.npaths == 0);
1726 c.cpaths = NVG_INIT_PATHS_SIZE;
1728 c.verts = cast(NVGVertex*)malloc(NVGVertex.sizeof*NVG_INIT_VERTS_SIZE);
1729 if (c.verts is null) goto error;
1730 assert(c.nverts == 0);
1731 c.cverts = NVG_INIT_VERTS_SIZE;
1733 return c;
1735 error:
1736 nvg__deletePathCache(c);
1737 return null;
1740 void nvg__setDevicePixelRatio (NVGContext ctx, float ratio) pure nothrow @safe @nogc {
1741 ctx.tessTol = 0.25f/ratio;
1742 ctx.distTol = 0.01f/ratio;
1743 ctx.fringeWidth = 1.0f/ratio;
1744 ctx.devicePxRatio = ratio;
1747 NVGCompositeOperationState nvg__compositeOperationState (NVGCompositeOperation op) pure nothrow @safe @nogc {
1748 NVGCompositeOperationState state;
1749 NVGBlendFactor sfactor, dfactor;
1751 if (op == NVGCompositeOperation.SourceOver) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha;}
1752 else if (op == NVGCompositeOperation.SourceIn) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.Zero; }
1753 else if (op == NVGCompositeOperation.SourceOut) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.Zero; }
1754 else if (op == NVGCompositeOperation.SourceAtop) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.OneMinusSrcAlpha; }
1755 else if (op == NVGCompositeOperation.DestinationOver) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.One; }
1756 else if (op == NVGCompositeOperation.DestinationIn) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.SrcAlpha; }
1757 else if (op == NVGCompositeOperation.DestinationOut) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.OneMinusSrcAlpha; }
1758 else if (op == NVGCompositeOperation.DestinationAtop) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.SrcAlpha; }
1759 else if (op == NVGCompositeOperation.Lighter) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.One; }
1760 else if (op == NVGCompositeOperation.Copy) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.Zero; }
1761 else if (op == NVGCompositeOperation.Xor) {
1762 state.simple = false;
1763 state.srcRGB = NVGBlendFactor.OneMinusDstColor;
1764 state.srcAlpha = NVGBlendFactor.OneMinusDstAlpha;
1765 state.dstRGB = NVGBlendFactor.OneMinusSrcColor;
1766 state.dstAlpha = NVGBlendFactor.OneMinusSrcAlpha;
1767 return state;
1769 else { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } // default value for invalid op: SourceOver
1771 state.simple = true;
1772 state.srcAlpha = sfactor;
1773 state.dstAlpha = dfactor;
1774 return state;
1777 NVGstate* nvg__getState (NVGContext ctx) pure nothrow @trusted @nogc {
1778 pragma(inline, true);
1779 if (ctx is null || !ctx.contextAlive || ctx.nstates == 0) assert(0, "NanoVega: cannot perform commands on inactive context");
1780 return &ctx.states.ptr[ctx.nstates-1];
1783 // Constructor called by the render back-end.
1784 NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc {
1785 FONSParams fontParams;
1786 NVGContext ctx = cast(NVGContext)malloc(NVGcontextinternal.sizeof);
1787 if (ctx is null) goto error;
1788 memset(ctx, 0, NVGcontextinternal.sizeof);
1790 ctx.angleTol = 0; // angle tolerance for McSeem Bezier rasterizer
1791 ctx.cuspLimit = 0; // cusp limit for McSeem Bezier rasterizer (0: real cusps)
1793 ctx.contextAlive = true;
1795 ctx.params = *params;
1796 //ctx.fontImages[0..NVG_MAX_FONTIMAGES] = 0;
1798 ctx.commands = cast(float*)malloc(float.sizeof*NVG_INIT_COMMANDS_SIZE);
1799 if (ctx.commands is null) goto error;
1800 ctx.ncommands = 0;
1801 ctx.ccommands = NVG_INIT_COMMANDS_SIZE;
1803 ctx.cache = nvg__allocPathCache();
1804 if (ctx.cache is null) goto error;
1806 ctx.save();
1807 ctx.reset();
1809 nvg__setDevicePixelRatio(ctx, 1.0f);
1810 ctx.mWidth = ctx.mHeight = 0;
1812 if (!ctx.params.renderCreate(ctx.params.userPtr)) goto error;
1814 // init font rendering
1815 memset(&fontParams, 0, fontParams.sizeof);
1816 fontParams.width = NVG_INIT_FONTIMAGE_SIZE;
1817 fontParams.height = NVG_INIT_FONTIMAGE_SIZE;
1818 fontParams.flags = FONSParams.Flag.ZeroTopLeft;
1819 fontParams.renderCreate = null;
1820 fontParams.renderUpdate = null;
1821 fontParams.renderDelete = null;
1822 fontParams.userPtr = null;
1823 ctx.fs = FONSContext.create(fontParams);
1824 if (ctx.fs is null) goto error;
1826 // create font texture
1827 ctx.fontImages[0].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, fontParams.width, fontParams.height, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null);
1828 if (ctx.fontImages[0].id == 0) goto error;
1829 ctx.fontImages[0].ctx = ctx;
1830 ctx.nvg__imageIncRef(ctx.fontImages[0].id, false); // don't increment driver refcount
1831 ctx.fontImageIdx = 0;
1833 ctx.pathPickId = -1;
1834 ctx.tesselatortype = NVG_DEFAULT_TESSELATOR;
1836 return ctx;
1838 error:
1839 ctx.deleteInternal();
1840 return null;
1843 // Called by render backend.
1844 NVGparams* internalParams (NVGContext ctx) nothrow @trusted @nogc {
1845 return &ctx.params;
1848 // Destructor called by the render back-end.
1849 void deleteInternal (ref NVGContext ctx) nothrow @trusted @nogc {
1850 if (ctx is null) return;
1851 if (ctx.contextAlive) {
1852 if (ctx.commands !is null) free(ctx.commands);
1853 nvg__deletePathCache(ctx.cache);
1855 if (ctx.fs) ctx.fs.kill();
1857 foreach (uint i; 0..NVG_MAX_FONTIMAGES) ctx.fontImages[i].clear();
1859 if (ctx.params.renderDelete !is null) ctx.params.renderDelete(ctx.params.userPtr);
1861 if (ctx.pickScene !is null) nvg__deletePickScene(ctx.pickScene);
1863 ctx.contextAlive = false;
1865 import core.atomic : atomicLoad;
1866 if (atomicLoad(ctx.imageCount) == 0) {
1867 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("destroyed context %p\n", ctx); }
1868 free(ctx);
1869 } else {
1870 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("context %p is zombie now (%d image refs)\n", ctx, ctx.imageCount); }
1875 /// Delete NanoVega context.
1876 /// Group: context_management
1877 public void kill (ref NVGContext ctx) nothrow @trusted @nogc {
1878 if (ctx !is null) {
1879 ctx.deleteInternal();
1880 ctx = null;
1884 /// Returns `true` if the given context is not `null` and can be used for painting.
1885 /// Group: context_management
1886 public bool valid (in NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive); }
1889 // ////////////////////////////////////////////////////////////////////////// //
1890 // Frame Management
1892 /** Begin drawing a new frame.
1894 * Calls to NanoVega drawing API should be wrapped in [beginFrame] and [endFrame]
1896 * [beginFrame] defines the size of the window to render to in relation currently
1897 * set viewport (i.e. glViewport on GL backends). Device pixel ration allows to
1898 * control the rendering on Hi-DPI devices.
1900 * For example, GLFW returns two dimension for an opened window: window size and
1901 * frame buffer size. In that case you would set windowWidth/windowHeight to the window size,
1902 * devicePixelRatio to: `windowWidth/windowHeight`.
1904 * Default ratio is `1`.
1906 * Note that fractional ratio can (and will) distort your fonts and images.
1908 * This call also resets pick marks (see picking API for non-rasterized paths),
1909 * path recording, and GPU affine transformatin matrix.
1911 * see also [glNVGClearFlags], which returns necessary flags for [glClear].
1913 * Group: frame_management
1915 public void beginFrame (NVGContext ctx, int windowWidth, int windowHeight, float devicePixelRatio=1.0f) nothrow @trusted @nogc {
1916 import std.math : isNaN;
1918 printf("Tris: draws:%d fill:%d stroke:%d text:%d TOT:%d\n",
1919 ctx.drawCallCount, ctx.fillTriCount, ctx.strokeTriCount, ctx.textTriCount,
1920 ctx.fillTriCount+ctx.strokeTriCount+ctx.textTriCount);
1922 if (ctx.nstates > 0) ctx.cancelFrame();
1924 if (windowWidth < 1) windowWidth = 1;
1925 if (windowHeight < 1) windowHeight = 1;
1927 if (isNaN(devicePixelRatio)) devicePixelRatio = (windowHeight > 0 ? cast(float)windowWidth/cast(float)windowHeight : 1024.0/768.0);
1929 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
1930 ctx.nstates = 0;
1931 ctx.save();
1932 ctx.reset();
1934 nvg__setDevicePixelRatio(ctx, devicePixelRatio);
1936 ctx.params.renderViewport(ctx.params.userPtr, windowWidth, windowHeight);
1937 ctx.mWidth = windowWidth;
1938 ctx.mHeight = windowHeight;
1940 ctx.recset = null;
1941 ctx.recstart = -1;
1943 ctx.pathPickId = NVGNoPick;
1944 ctx.pathPickRegistered = 0;
1946 ctx.drawCallCount = 0;
1947 ctx.fillTriCount = 0;
1948 ctx.strokeTriCount = 0;
1949 ctx.textTriCount = 0;
1951 ctx.ncommands = 0;
1952 ctx.pathPickRegistered = 0;
1953 nvg__clearPathCache(ctx);
1955 ctx.gpuAffine = NVGMatrix.Identity;
1957 nvg__pickBeginFrame(ctx, windowWidth, windowHeight);
1960 /// Cancels drawing the current frame. Cancels path recording.
1961 /// Group: frame_management
1962 public void cancelFrame (NVGContext ctx) nothrow @trusted @nogc {
1963 ctx.cancelRecording();
1964 //ctx.mWidth = 0;
1965 //ctx.mHeight = 0;
1966 // cancel render queue
1967 ctx.params.renderCancel(ctx.params.userPtr);
1968 // clear saved states (this may free some textures)
1969 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
1970 ctx.nstates = 0;
1973 /// Ends drawing the current frame (flushing remaining render state). Commits recorded paths.
1974 /// Group: frame_management
1975 public void endFrame (NVGContext ctx) nothrow @trusted @nogc {
1976 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx);
1977 ctx.stopRecording();
1978 //ctx.mWidth = 0;
1979 //ctx.mHeight = 0;
1980 // flush render queue
1981 NVGstate* state = nvg__getState(ctx);
1982 ctx.params.renderFlush(ctx.params.userPtr);
1983 if (ctx.fontImageIdx != 0) {
1984 auto fontImage = ctx.fontImages[ctx.fontImageIdx];
1985 int j = 0, iw, ih;
1986 // delete images that smaller than current one
1987 if (!fontImage.valid) return;
1988 ctx.imageSize(fontImage, iw, ih);
1989 foreach (int i; 0..ctx.fontImageIdx) {
1990 if (ctx.fontImages[i].valid) {
1991 int nw, nh;
1992 ctx.imageSize(ctx.fontImages[i], nw, nh);
1993 if (nw < iw || nh < ih) {
1994 ctx.deleteImage(ctx.fontImages[i]);
1995 } else {
1996 ctx.fontImages[j++] = ctx.fontImages[i];
2000 // make current font image to first
2001 ctx.fontImages[j++] = ctx.fontImages[0];
2002 ctx.fontImages[0] = fontImage;
2003 ctx.fontImageIdx = 0;
2004 // clear all images after j
2005 ctx.fontImages[j..NVG_MAX_FONTIMAGES] = NVGImage.init;
2007 // clear saved states (this may free some textures)
2008 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2009 ctx.nstates = 0;
2013 // ////////////////////////////////////////////////////////////////////////// //
2014 // Recording and Replaying Pathes
2016 // Saved path set.
2017 // Group: path_recording
2018 public alias NVGPathSet = NVGPathSetS*;
2021 //TODO: save scissor info?
2022 struct NVGPathSetS {
2023 private:
2024 // either path cache, or text item
2025 static struct Node {
2026 NVGPaint paint;
2027 NVGpathCache* path;
2030 private:
2031 Node* nodes;
2032 int nnodes, cnodes;
2033 NVGpickScene* pickscene;
2034 //int npickscenes, cpickscenes;
2035 NVGContext svctx; // used to do some sanity checks, and to free resources
2037 private:
2038 Node* allocNode () nothrow @trusted @nogc {
2039 import core.stdc.string : memset;
2040 // grow buffer if necessary
2041 if (nnodes+1 > cnodes) {
2042 import core.stdc.stdlib : realloc;
2043 int newsz = (cnodes == 0 ? 8 : cnodes <= 1024 ? cnodes*2 : cnodes+1024);
2044 nodes = cast(Node*)realloc(nodes, newsz*Node.sizeof);
2045 if (nodes is null) assert(0, "NanoVega: out of memory");
2046 //memset(svp.caches+svp.ccaches, 0, (newsz-svp.ccaches)*NVGpathCache.sizeof);
2047 cnodes = newsz;
2049 assert(nnodes < cnodes);
2050 memset(nodes+nnodes, 0, Node.sizeof);
2051 return &nodes[nnodes++];
2054 Node* allocPathNode () nothrow @trusted @nogc {
2055 import core.stdc.stdlib : malloc;
2056 import core.stdc.string : memset;
2057 auto node = allocNode();
2058 // allocate path cache
2059 auto pc = cast(NVGpathCache*)malloc(NVGpathCache.sizeof);
2060 if (pc is null) assert(0, "NanoVega: out of memory");
2061 node.path = pc;
2062 return node;
2065 void clearNode (int idx) nothrow @trusted @nogc {
2066 if (idx < 0 || idx >= nnodes) return;
2067 Node* node = &nodes[idx];
2068 if (svctx !is null && node.paint.image.valid) node.paint.image.clear();
2069 if (node.path !is null) node.path.clear();
2072 private:
2073 void takeCurrentPickScene (NVGContext ctx) nothrow @trusted @nogc {
2074 NVGpickScene* ps = ctx.pickScene;
2075 if (ps is null) return; // nothing to do
2076 if (ps.npaths == 0) return; // pick scene is empty
2077 ctx.pickScene = null;
2078 pickscene = ps;
2081 void replay (NVGContext ctx, in ref NVGColor fillTint, in ref NVGColor strokeTint) nothrow @trusted @nogc {
2082 NVGstate* state = nvg__getState(ctx);
2083 foreach (ref node; nodes[0..nnodes]) {
2084 if (auto cc = node.path) {
2085 if (cc.npaths <= 0) continue;
2087 if (cc.fillReady) {
2088 NVGPaint fillPaint = node.paint;
2090 // apply global alpha
2091 fillPaint.innerColor.a *= state.alpha;
2092 fillPaint.middleColor.a *= state.alpha;
2093 fillPaint.outerColor.a *= state.alpha;
2095 fillPaint.innerColor.applyTint(fillTint);
2096 fillPaint.middleColor.applyTint(fillTint);
2097 fillPaint.outerColor.applyTint(fillTint);
2099 ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &fillPaint, &state.scissor, cc.fringeWidth, cc.bounds.ptr, cc.paths, cc.npaths, cc.evenOddMode);
2101 // count triangles
2102 foreach (int i; 0..cc.npaths) {
2103 NVGpath* path = &cc.paths[i];
2104 ctx.fillTriCount += path.nfill-2;
2105 ctx.fillTriCount += path.nstroke-2;
2106 ctx.drawCallCount += 2;
2110 if (cc.strokeReady) {
2111 NVGPaint strokePaint = node.paint;
2113 strokePaint.innerColor.a *= cc.strokeAlphaMul;
2114 strokePaint.middleColor.a *= cc.strokeAlphaMul;
2115 strokePaint.outerColor.a *= cc.strokeAlphaMul;
2117 // apply global alpha
2118 strokePaint.innerColor.a *= state.alpha;
2119 strokePaint.middleColor.a *= state.alpha;
2120 strokePaint.outerColor.a *= state.alpha;
2122 strokePaint.innerColor.applyTint(strokeTint);
2123 strokePaint.middleColor.applyTint(strokeTint);
2124 strokePaint.outerColor.applyTint(strokeTint);
2126 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &strokePaint, &state.scissor, cc.fringeWidth, cc.strokeWidth, cc.paths, cc.npaths);
2128 // count triangles
2129 foreach (int i; 0..cc.npaths) {
2130 NVGpath* path = &cc.paths[i];
2131 ctx.strokeTriCount += path.nstroke-2;
2132 ++ctx.drawCallCount;
2139 public:
2140 @disable this (this); // no copies
2141 void opAssign() (in auto ref NVGPathSetS a) { static assert(0, "no copies!"); }
2143 // pick test
2144 // Call delegate [dg] for each path under the specified position (in no particular order).
2145 // Returns the id of the path for which delegate [dg] returned true or -1.
2146 // dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending).
2147 int hitTestDG(bool bestOrder=false, DG) (in float x, in float y, NVGPickKind kind, scope DG dg) if (IsGoodHitTestDG!DG || IsGoodHitTestInternalDG!DG) {
2148 if (pickscene is null) return -1;
2150 NVGpickScene* ps = pickscene;
2151 int levelwidth = 1<<(ps.nlevels-1);
2152 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth);
2153 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth);
2154 int npicked = 0;
2156 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) {
2157 NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx];
2158 while (pp !is null) {
2159 if (nvg__pickPathTestBounds(svctx, ps, pp, x, y)) {
2160 int hit = 0;
2161 if ((kind&NVGPickKind.Stroke) && (pp.flags&NVGPathFlags.Stroke)) hit = nvg__pickPathStroke(ps, pp, x, y);
2162 if (!hit && (kind&NVGPickKind.Fill) && (pp.flags&NVGPathFlags.Fill)) hit = nvg__pickPath(ps, pp, x, y);
2163 if (hit) {
2164 static if (IsGoodHitTestDG!DG) {
2165 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) {
2166 if (dg(pp.id, cast(int)pp.order)) return pp.id;
2167 } else {
2168 dg(pp.id, cast(int)pp.order);
2170 } else {
2171 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) {
2172 if (dg(pp)) return pp.id;
2173 } else {
2174 dg(pp);
2179 pp = pp.next;
2181 cellx >>= 1;
2182 celly >>= 1;
2183 levelwidth >>= 1;
2186 return -1;
2189 // Fills ids with a list of the top most hit ids under the specified position.
2190 // Returns the slice of [ids].
2191 int[] hitTestAll (in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc {
2192 if (pickscene is null || ids.length == 0) return ids[0..0];
2194 int npicked = 0;
2195 NVGpickScene* ps = pickscene;
2197 hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
2198 if (npicked == ps.cpicked) {
2199 int cpicked = ps.cpicked+ps.cpicked;
2200 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked);
2201 if (picked is null) return true; // abort
2202 ps.cpicked = cpicked;
2203 ps.picked = picked;
2205 ps.picked[npicked] = pp;
2206 ++npicked;
2207 return false; // go on
2210 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths);
2212 assert(npicked >= 0);
2213 if (npicked > ids.length) npicked = cast(int)ids.length;
2214 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id;
2216 return ids[0..npicked];
2219 // Returns the id of the pickable shape containing x,y or -1 if no shape was found.
2220 int hitTest (in float x, in float y, NVGPickKind kind) nothrow @trusted @nogc {
2221 if (pickscene is null) return -1;
2223 int bestOrder = -1;
2224 int bestID = -1;
2226 hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
2227 if (pp.order > bestOrder) {
2228 bestOrder = pp.order;
2229 bestID = pp.id;
2233 return bestID;
2237 // Append current path to existing path set. Is is safe to call this with `null` [svp].
2238 void appendCurrentPathToCache (NVGContext ctx, NVGPathSet svp, in ref NVGPaint paint) nothrow @trusted @nogc {
2239 if (ctx is null || svp is null) return;
2240 if (ctx !is svp.svctx) assert(0, "NanoVega: cannot save paths from different contexts");
2241 if (ctx.ncommands == 0) {
2242 assert(ctx.cache.npaths == 0);
2243 return;
2245 if (!ctx.cache.fillReady && !ctx.cache.strokeReady) return;
2247 // tesselate current path
2248 //if (!ctx.cache.fillReady) nvg__prepareFill(ctx);
2249 //if (!ctx.cache.strokeReady) nvg__prepareStroke(ctx);
2251 auto node = svp.allocPathNode();
2252 NVGpathCache* cc = node.path;
2253 cc.copyFrom(ctx.cache);
2254 node.paint = paint;
2255 // copy path commands (we may need 'em for picking)
2256 version(all) {
2257 cc.ncommands = ctx.ncommands;
2258 if (cc.ncommands) {
2259 import core.stdc.stdlib : malloc;
2260 import core.stdc.string : memcpy;
2261 cc.commands = cast(float*)malloc(ctx.ncommands*float.sizeof);
2262 if (cc.commands is null) assert(0, "NanoVega: out of memory");
2263 memcpy(cc.commands, ctx.commands, ctx.ncommands*float.sizeof);
2264 } else {
2265 cc.commands = null;
2270 // Create new empty path set.
2271 // Group: path_recording
2272 public NVGPathSet newPathSet (NVGContext ctx) nothrow @trusted @nogc {
2273 import core.stdc.stdlib : malloc;
2274 import core.stdc.string : memset;
2275 if (ctx is null) return null;
2276 NVGPathSet res = cast(NVGPathSet)malloc(NVGPathSetS.sizeof);
2277 if (res is null) assert(0, "NanoVega: out of memory");
2278 memset(res, 0, NVGPathSetS.sizeof);
2279 res.svctx = ctx;
2280 return res;
2283 // Is the given path set empty? Empty path set can be `null`.
2284 // Group: path_recording
2285 public bool empty (NVGPathSet svp) pure nothrow @safe @nogc { pragma(inline, true); return (svp is null || svp.nnodes == 0); }
2287 // Clear path set contents. Will release $(B some) allocated memory (this function is meant to clear something that will be reused).
2288 // Group: path_recording
2289 public void clear (NVGPathSet svp) nothrow @trusted @nogc {
2290 if (svp !is null) {
2291 import core.stdc.stdlib : free;
2292 foreach (immutable idx; 0.. svp.nnodes) svp.clearNode(idx);
2293 svp.nnodes = 0;
2297 // Destroy path set (frees all allocated memory).
2298 // Group: path_recording
2299 public void kill (ref NVGPathSet svp) nothrow @trusted @nogc {
2300 if (svp !is null) {
2301 import core.stdc.stdlib : free;
2302 svp.clear();
2303 if (svp.nodes !is null) free(svp.nodes);
2304 free(svp);
2305 if (svp.pickscene !is null) nvg__deletePickScene(svp.pickscene);
2306 svp = null;
2310 // Start path recording. [svp] should be alive until recording is cancelled or stopped.
2311 // Group: path_recording
2312 public void startRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc {
2313 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2314 ctx.stopRecording();
2315 ctx.recset = svp;
2316 ctx.recstart = (svp !is null ? svp.nnodes : -1);
2317 ctx.recblockdraw = false;
2320 /* Start path recording. [svp] should be alive until recording is cancelled or stopped.
2322 * This will block all rendering, so you can call your rendering functions to record paths without actual drawing.
2323 * Commiting or cancelling will re-enable rendering.
2324 * You can call this with `null` svp to block rendering without recording any paths.
2326 * Group: path_recording
2328 public void startBlockingRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc {
2329 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2330 ctx.stopRecording();
2331 ctx.recset = svp;
2332 ctx.recstart = (svp !is null ? svp.nnodes : -1);
2333 ctx.recblockdraw = true;
2336 // Commit recorded paths. It is safe to call this when recording is not started.
2337 // Group: path_recording
2338 public void stopRecording (NVGContext ctx) nothrow @trusted @nogc {
2339 if (ctx.recset !is null && ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2340 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx);
2341 ctx.recset = null;
2342 ctx.recstart = -1;
2343 ctx.recblockdraw = false;
2346 // Cancel path recording.
2347 // Group: path_recording
2348 public void cancelRecording (NVGContext ctx) nothrow @trusted @nogc {
2349 if (ctx.recset !is null) {
2350 if (ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2351 assert(ctx.recstart >= 0 && ctx.recstart <= ctx.recset.nnodes);
2352 foreach (immutable idx; ctx.recstart..ctx.recset.nnodes) ctx.recset.clearNode(idx);
2353 ctx.recset.nnodes = ctx.recstart;
2354 ctx.recset = null;
2355 ctx.recstart = -1;
2357 ctx.recblockdraw = false;
2360 /* Replay saved path set.
2362 * Replaying record while you're recording another one is undefined behavior.
2364 * Group: path_recording
2366 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint, in auto ref NVGColor strokeTint) nothrow @trusted @nogc {
2367 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2368 svp.replay(ctx, fillTint, strokeTint);
2371 /// Ditto.
2372 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint) nothrow @trusted @nogc { ctx.replayRecording(svp, fillTint, NVGColor.transparent); }
2374 /// Ditto.
2375 public void replayRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { ctx.replayRecording(svp, NVGColor.transparent, NVGColor.transparent); }
2378 // ////////////////////////////////////////////////////////////////////////// //
2379 // Composite operation
2381 /// Sets the composite operation.
2382 /// Group: composite_operation
2383 public void globalCompositeOperation (NVGContext ctx, NVGCompositeOperation op) nothrow @trusted @nogc {
2384 NVGstate* state = nvg__getState(ctx);
2385 state.compositeOperation = nvg__compositeOperationState(op);
2388 /// Sets the composite operation with custom pixel arithmetic.
2389 /// Group: composite_operation
2390 public void globalCompositeBlendFunc (NVGContext ctx, NVGBlendFactor sfactor, NVGBlendFactor dfactor) nothrow @trusted @nogc {
2391 ctx.globalCompositeBlendFuncSeparate(sfactor, dfactor, sfactor, dfactor);
2394 /// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately.
2395 /// Group: composite_operation
2396 public void globalCompositeBlendFuncSeparate (NVGContext ctx, NVGBlendFactor srcRGB, NVGBlendFactor dstRGB, NVGBlendFactor srcAlpha, NVGBlendFactor dstAlpha) nothrow @trusted @nogc {
2397 NVGCompositeOperationState op;
2398 op.simple = false;
2399 op.srcRGB = srcRGB;
2400 op.dstRGB = dstRGB;
2401 op.srcAlpha = srcAlpha;
2402 op.dstAlpha = dstAlpha;
2403 NVGstate* state = nvg__getState(ctx);
2404 state.compositeOperation = op;
2408 // ////////////////////////////////////////////////////////////////////////// //
2409 // Color utils
2411 /// Returns a color value from string form.
2412 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb"
2413 /// Group: color_utils
2414 public NVGColor nvgRGB (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); }
2416 /// Ditto.
2417 public NVGColor nvgRGBA (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); }
2419 /// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f).
2420 /// Group: color_utils
2421 public NVGColor nvgRGB (int r, int g, int b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(nvgClampToByte(r), nvgClampToByte(g), nvgClampToByte(b), 255); }
2423 /// Returns a color value from red, green, blue values. Alpha will be set to 1.0f.
2424 /// Group: color_utils
2425 public NVGColor nvgRGBf (float r, float g, float b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(r, g, b, 1.0f); }
2427 /// Returns a color value from red, green, blue and alpha values.
2428 /// Group: color_utils
2429 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)); }
2431 /// Returns a color value from red, green, blue and alpha values.
2432 /// Group: color_utils
2433 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); }
2435 /// Returns new color with transparency (alpha) set to [a].
2436 /// Group: color_utils
2437 public NVGColor nvgTransRGBA (NVGColor c, ubyte a) nothrow @trusted @nogc {
2438 pragma(inline, true);
2439 c.a = a/255.0f;
2440 return c;
2443 /// Ditto.
2444 public NVGColor nvgTransRGBAf (NVGColor c, float a) nothrow @trusted @nogc {
2445 pragma(inline, true);
2446 c.a = a;
2447 return c;
2450 /// Linearly interpolates from color c0 to c1, and returns resulting color value.
2451 /// Group: color_utils
2452 public NVGColor nvgLerpRGBA() (in auto ref NVGColor c0, in auto ref NVGColor c1, float u) nothrow @trusted @nogc {
2453 NVGColor cint = void;
2454 u = nvg__clamp(u, 0.0f, 1.0f);
2455 float oneminu = 1.0f-u;
2456 foreach (uint i; 0..4) cint.rgba.ptr[i] = c0.rgba.ptr[i]*oneminu+c1.rgba.ptr[i]*u;
2457 return cint;
2460 /* see below
2461 public NVGColor nvgHSL() (float h, float s, float l) {
2462 //pragma(inline, true); // alas
2463 return nvgHSLA(h, s, l, 255);
2467 float nvg__hue (float h, float m1, float m2) pure nothrow @safe @nogc {
2468 if (h < 0) h += 1;
2469 if (h > 1) h -= 1;
2470 if (h < 1.0f/6.0f) return m1+(m2-m1)*h*6.0f;
2471 if (h < 3.0f/6.0f) return m2;
2472 if (h < 4.0f/6.0f) return m1+(m2-m1)*(2.0f/3.0f-h)*6.0f;
2473 return m1;
2476 /// Returns color value specified by hue, saturation and lightness.
2477 /// HSL values are all in range [0..1], alpha will be set to 255.
2478 /// Group: color_utils
2479 public alias nvgHSL = nvgHSLA; // trick to allow inlining
2481 /// Returns color value specified by hue, saturation and lightness and alpha.
2482 /// HSL values are all in range [0..1], alpha in range [0..255].
2483 /// Group: color_utils
2484 public NVGColor nvgHSLA (float h, float s, float l, ubyte a=255) nothrow @trusted @nogc {
2485 pragma(inline, true);
2486 NVGColor col = void;
2487 h = nvg__modf(h, 1.0f);
2488 if (h < 0.0f) h += 1.0f;
2489 s = nvg__clamp(s, 0.0f, 1.0f);
2490 l = nvg__clamp(l, 0.0f, 1.0f);
2491 immutable float m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s);
2492 immutable float m1 = 2*l-m2;
2493 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2494 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f);
2495 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2496 col.a = a/255.0f;
2497 return col;
2500 /// Returns color value specified by hue, saturation and lightness and alpha.
2501 /// HSL values and alpha are all in range [0..1].
2502 /// Group: color_utils
2503 public NVGColor nvgHSLA (float h, float s, float l, float a) nothrow @trusted @nogc {
2504 // sorry for copypasta, it is for inliner
2505 static if (__VERSION__ >= 2072) pragma(inline, true);
2506 NVGColor col = void;
2507 h = nvg__modf(h, 1.0f);
2508 if (h < 0.0f) h += 1.0f;
2509 s = nvg__clamp(s, 0.0f, 1.0f);
2510 l = nvg__clamp(l, 0.0f, 1.0f);
2511 immutable m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s);
2512 immutable m1 = 2*l-m2;
2513 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2514 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f);
2515 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2516 col.a = a;
2517 return col;
2521 // ////////////////////////////////////////////////////////////////////////// //
2522 // Matrices and Transformations
2524 /** Matrix class.
2526 * Group: matrices
2528 public align(1) struct NVGMatrix {
2529 align(1):
2530 private:
2531 static immutable float[6] IdentityMat = [
2532 1.0f, 0.0f,
2533 0.0f, 1.0f,
2534 0.0f, 0.0f,
2537 public:
2538 /// Matrix values. Initial value is identity matrix.
2539 float[6] mat = [
2540 1.0f, 0.0f,
2541 0.0f, 1.0f,
2542 0.0f, 0.0f,
2545 public nothrow @trusted @nogc:
2546 /// Create Matrix with the given values.
2547 this (const(float)[] amat...) {
2548 pragma(inline, true);
2549 if (amat.length >= 6) {
2550 mat.ptr[0..6] = amat.ptr[0..6];
2551 } else {
2552 mat.ptr[0..6] = 0;
2553 mat.ptr[0..amat.length] = amat[];
2557 /// Can be used to check validity of [inverted] result
2558 @property bool valid () const { import core.stdc.math : isfinite; return (isfinite(mat.ptr[0]) != 0); }
2560 /// Returns `true` if this matrix is identity matrix.
2561 @property bool isIdentity () const { version(aliced) pragma(inline, true); return (mat[] == IdentityMat[]); }
2563 /// Returns new inverse matrix.
2564 /// If inverted matrix cannot be calculated, `res.valid` fill be `false`.
2565 NVGMatrix inverted () const {
2566 NVGMatrix res = this;
2567 res.invert;
2568 return res;
2571 /// Inverts this matrix.
2572 /// If inverted matrix cannot be calculated, `this.valid` fill be `false`.
2573 ref NVGMatrix invert () {
2574 float[6] inv = void;
2575 immutable double det = cast(double)mat.ptr[0]*mat.ptr[3]-cast(double)mat.ptr[2]*mat.ptr[1];
2576 if (det > -1e-6 && det < 1e-6) {
2577 inv[] = float.nan;
2578 } else {
2579 immutable double invdet = 1.0/det;
2580 inv.ptr[0] = cast(float)(mat.ptr[3]*invdet);
2581 inv.ptr[2] = cast(float)(-mat.ptr[2]*invdet);
2582 inv.ptr[4] = cast(float)((cast(double)mat.ptr[2]*mat.ptr[5]-cast(double)mat.ptr[3]*mat.ptr[4])*invdet);
2583 inv.ptr[1] = cast(float)(-mat.ptr[1]*invdet);
2584 inv.ptr[3] = cast(float)(mat.ptr[0]*invdet);
2585 inv.ptr[5] = cast(float)((cast(double)mat.ptr[1]*mat.ptr[4]-cast(double)mat.ptr[0]*mat.ptr[5])*invdet);
2587 mat.ptr[0..6] = inv.ptr[0..6];
2588 return this;
2591 /// Sets this matrix to identity matrix.
2592 ref NVGMatrix identity () { version(aliced) pragma(inline, true); mat[] = IdentityMat[]; return this; }
2594 /// Translate this matrix.
2595 ref NVGMatrix translate (in float tx, in float ty) {
2596 version(aliced) pragma(inline, true);
2597 return this.mul(Translated(tx, ty));
2600 /// Scale this matrix.
2601 ref NVGMatrix scale (in float sx, in float sy) {
2602 version(aliced) pragma(inline, true);
2603 return this.mul(Scaled(sx, sy));
2606 /// Rotate this matrix.
2607 ref NVGMatrix rotate (in float a) {
2608 version(aliced) pragma(inline, true);
2609 return this.mul(Rotated(a));
2612 /// Skew this matrix by X axis.
2613 ref NVGMatrix skewX (in float a) {
2614 version(aliced) pragma(inline, true);
2615 return this.mul(SkewedX(a));
2618 /// Skew this matrix by Y axis.
2619 ref NVGMatrix skewY (in float a) {
2620 version(aliced) pragma(inline, true);
2621 return this.mul(SkewedY(a));
2624 /// Skew this matrix by both axes.
2625 ref NVGMatrix skewY (in float ax, in float ay) {
2626 version(aliced) pragma(inline, true);
2627 return this.mul(SkewedXY(ax, ay));
2630 /// Transform point with this matrix. `null` destinations are allowed.
2631 /// [sx] and [sy] is the source point. [dx] and [dy] may point to the same variables.
2632 void point (float* dx, float* dy, float sx, float sy) nothrow @trusted @nogc {
2633 version(aliced) pragma(inline, true);
2634 if (dx !is null) *dx = sx*mat.ptr[0]+sy*mat.ptr[2]+mat.ptr[4];
2635 if (dy !is null) *dy = sx*mat.ptr[1]+sy*mat.ptr[3]+mat.ptr[5];
2638 /// Transform point with this matrix.
2639 void point (ref float x, ref float y) nothrow @trusted @nogc {
2640 version(aliced) pragma(inline, true);
2641 immutable float nx = x*mat.ptr[0]+y*mat.ptr[2]+mat.ptr[4];
2642 immutable float ny = x*mat.ptr[1]+y*mat.ptr[3]+mat.ptr[5];
2643 x = nx;
2644 y = ny;
2647 /// Sets this matrix to the result of multiplication of `this` and [s] (this * S).
2648 ref NVGMatrix mul() (in auto ref NVGMatrix s) {
2649 immutable float t0 = mat.ptr[0]*s.mat.ptr[0]+mat.ptr[1]*s.mat.ptr[2];
2650 immutable float t2 = mat.ptr[2]*s.mat.ptr[0]+mat.ptr[3]*s.mat.ptr[2];
2651 immutable float t4 = mat.ptr[4]*s.mat.ptr[0]+mat.ptr[5]*s.mat.ptr[2]+s.mat.ptr[4];
2652 mat.ptr[1] = mat.ptr[0]*s.mat.ptr[1]+mat.ptr[1]*s.mat.ptr[3];
2653 mat.ptr[3] = mat.ptr[2]*s.mat.ptr[1]+mat.ptr[3]*s.mat.ptr[3];
2654 mat.ptr[5] = mat.ptr[4]*s.mat.ptr[1]+mat.ptr[5]*s.mat.ptr[3]+s.mat.ptr[5];
2655 mat.ptr[0] = t0;
2656 mat.ptr[2] = t2;
2657 mat.ptr[4] = t4;
2658 return this;
2661 /// Sets this matrix to the result of multiplication of [s] and `this` (S * this).
2662 /// Sets the transform to the result of multiplication of two transforms, of A = B*A.
2663 /// Group: matrices
2664 ref NVGMatrix premul() (in auto ref NVGMatrix s) {
2665 NVGMatrix s2 = s;
2666 s2.mul(this);
2667 mat[] = s2.mat[];
2668 return this;
2671 /// Multiply this matrix by [s], return result as new matrix.
2672 /// Performs operations in this left-to-right order.
2673 NVGMatrix opBinary(string op="*") (in auto ref NVGMatrix s) const {
2674 version(aliced) pragma(inline, true);
2675 NVGMatrix res = this;
2676 res.mul(s);
2677 return res;
2680 /// Multiply this matrix by [s].
2681 /// Performs operations in this left-to-right order.
2682 ref NVGMatrix opOpAssign(string op="*") (in auto ref NVGMatrix s) {
2683 version(aliced) pragma(inline, true);
2684 return this.mul(s);
2687 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.
2688 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.
2689 float rotation () const { pragma(inline, true); return nvg__atan2f(mat.ptr[1], mat.ptr[0]); } /// Returns rotation of this matrix.
2690 float tx () const { pragma(inline, true); return mat.ptr[4]; } /// Returns x translation of this matrix.
2691 float ty () const { pragma(inline, true); return mat.ptr[5]; } /// Returns y translation of this matrix.
2693 ref NVGMatrix scaleX (in float v) { pragma(inline, true); return scaleRotateTransform(v, scaleY, rotation, tx, ty); } /// Sets x scaling of this matrix.
2694 ref NVGMatrix scaleY (in float v) { pragma(inline, true); return scaleRotateTransform(scaleX, v, rotation, tx, ty); } /// Sets y scaling of this matrix.
2695 ref NVGMatrix rotation (in float v) { pragma(inline, true); return scaleRotateTransform(scaleX, scaleY, v, tx, ty); } /// Sets rotation of this matrix.
2696 ref NVGMatrix tx (in float v) { pragma(inline, true); mat.ptr[4] = v; return this; } /// Sets x translation of this matrix.
2697 ref NVGMatrix ty (in float v) { pragma(inline, true); mat.ptr[5] = v; return this; } /// Sets y translation of this matrix.
2699 /// Utility function to be used in `setXXX()`.
2700 /// This is the same as doing: `mat.identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster
2701 ref NVGMatrix scaleRotateTransform (in float xscale, in float yscale, in float a, in float tx, in float ty) {
2702 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2703 mat.ptr[0] = xscale*cs; mat.ptr[1] = yscale*sn;
2704 mat.ptr[2] = xscale*-sn; mat.ptr[3] = yscale*cs;
2705 mat.ptr[4] = tx; mat.ptr[5] = ty;
2706 return this;
2709 /// This is the same as doing: `mat.identity.rotate(a).translate(tx, ty)`, only faster
2710 ref NVGMatrix rotateTransform (in float a, in float tx, in float ty) {
2711 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2712 mat.ptr[0] = cs; mat.ptr[1] = sn;
2713 mat.ptr[2] = -sn; mat.ptr[3] = cs;
2714 mat.ptr[4] = tx; mat.ptr[5] = ty;
2715 return this;
2718 /// Returns new identity matrix.
2719 static NVGMatrix Identity () { pragma(inline, true); return NVGMatrix.init; }
2721 /// Returns new translation matrix.
2722 static NVGMatrix Translated (in float tx, in float ty) {
2723 version(aliced) pragma(inline, true);
2724 NVGMatrix res = void;
2725 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f;
2726 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f;
2727 res.mat.ptr[4] = tx; res.mat.ptr[5] = ty;
2728 return res;
2731 /// Returns new scaling matrix.
2732 static NVGMatrix Scaled (in float sx, in float sy) {
2733 version(aliced) pragma(inline, true);
2734 NVGMatrix res = void;
2735 res.mat.ptr[0] = sx; res.mat.ptr[1] = 0.0f;
2736 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = sy;
2737 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2738 return res;
2741 /// Returns new rotation matrix. Angle is specified in radians.
2742 static NVGMatrix Rotated (in float a) {
2743 version(aliced) pragma(inline, true);
2744 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2745 NVGMatrix res = void;
2746 res.mat.ptr[0] = cs; res.mat.ptr[1] = sn;
2747 res.mat.ptr[2] = -sn; res.mat.ptr[3] = cs;
2748 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2749 return res;
2752 /// Returns new x-skewing matrix. Angle is specified in radians.
2753 static NVGMatrix SkewedX (in float a) {
2754 version(aliced) pragma(inline, true);
2755 NVGMatrix res = void;
2756 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f;
2757 res.mat.ptr[2] = nvg__tanf(a); res.mat.ptr[3] = 1.0f;
2758 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2759 return res;
2762 /// Returns new y-skewing matrix. Angle is specified in radians.
2763 static NVGMatrix SkewedY (in float a) {
2764 version(aliced) pragma(inline, true);
2765 NVGMatrix res = void;
2766 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(a);
2767 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f;
2768 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2769 return res;
2772 /// Returns new xy-skewing matrix. Angles are specified in radians.
2773 static NVGMatrix SkewedXY (in float ax, in float ay) {
2774 version(aliced) pragma(inline, true);
2775 NVGMatrix res = void;
2776 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(ay);
2777 res.mat.ptr[2] = nvg__tanf(ax); res.mat.ptr[3] = 1.0f;
2778 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2779 return res;
2782 /// Utility function to be used in `setXXX()`.
2783 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster
2784 static NVGMatrix ScaledRotatedTransformed (in float xscale, in float yscale, in float a, in float tx, in float ty) {
2785 NVGMatrix res = void;
2786 res.scaleRotateTransform(xscale, yscale, a, tx, ty);
2787 return res;
2790 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).translate(tx, ty)`, only faster
2791 static NVGMatrix RotatedTransformed (in float a, in float tx, in float ty) {
2792 NVGMatrix res = void;
2793 res.rotateTransform(a, tx, ty);
2794 return res;
2799 /// Converts degrees to radians.
2800 /// Group: matrices
2801 public float nvgDegToRad() (in float deg) pure nothrow @safe @nogc { pragma(inline, true); return deg/180.0f*NVG_PI; }
2803 /// Converts radians to degrees.
2804 /// Group: matrices
2805 public float nvgRadToDeg() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad/NVG_PI*180.0f; }
2807 public alias nvgDegrees = nvgDegToRad; /// Use this like `42.nvgDegrees`
2808 public float nvgRadians() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad; } /// Use this like `0.1.nvgRadians`
2811 // ////////////////////////////////////////////////////////////////////////// //
2812 void nvg__setPaintColor() (ref NVGPaint p, in auto ref NVGColor color) nothrow @trusted @nogc {
2813 p.clear();
2814 p.xform.identity;
2815 p.radius = 0.0f;
2816 p.feather = 1.0f;
2817 p.innerColor = p.middleColor = p.outerColor = color;
2818 p.midp = -1;
2819 p.simpleColor = true;
2823 // ////////////////////////////////////////////////////////////////////////// //
2824 // State handling
2826 version(nanovega_debug_clipping) {
2827 public void nvgClipDumpOn (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, true); }
2828 public void nvgClipDumpOff (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, false); }
2831 /** Pushes and saves the current render state into a state stack.
2832 * A matching [restore] must be used to restore the state.
2833 * Returns `false` if state stack overflowed.
2835 * Group: state_handling
2837 public bool save (NVGContext ctx) nothrow @trusted @nogc {
2838 if (ctx.nstates >= NVG_MAX_STATES) return false;
2839 if (ctx.nstates > 0) {
2840 //memcpy(&ctx.states[ctx.nstates], &ctx.states[ctx.nstates-1], NVGstate.sizeof);
2841 ctx.states[ctx.nstates] = ctx.states[ctx.nstates-1];
2842 ctx.params.renderPushClip(ctx.params.userPtr);
2844 ++ctx.nstates;
2845 return true;
2848 /// Pops and restores current render state.
2849 /// Group: state_handling
2850 public bool restore (NVGContext ctx) nothrow @trusted @nogc {
2851 if (ctx.nstates <= 1) return false;
2852 ctx.states[ctx.nstates-1].clearPaint();
2853 ctx.params.renderPopClip(ctx.params.userPtr);
2854 --ctx.nstates;
2855 return true;
2858 /// Resets current render state to default values. Does not affect the render state stack.
2859 /// Group: state_handling
2860 public void reset (NVGContext ctx) nothrow @trusted @nogc {
2861 NVGstate* state = nvg__getState(ctx);
2862 state.clearPaint();
2864 nvg__setPaintColor(state.fill, nvgRGBA(255, 255, 255, 255));
2865 nvg__setPaintColor(state.stroke, nvgRGBA(0, 0, 0, 255));
2866 state.compositeOperation = nvg__compositeOperationState(NVGCompositeOperation.SourceOver);
2867 state.shapeAntiAlias = true;
2868 state.strokeWidth = 1.0f;
2869 state.miterLimit = 10.0f;
2870 state.lineCap = NVGLineCap.Butt;
2871 state.lineJoin = NVGLineCap.Miter;
2872 state.alpha = 1.0f;
2873 state.xform.identity;
2875 state.scissor.extent[] = -1.0f;
2877 state.fontSize = 16.0f;
2878 state.letterSpacing = 0.0f;
2879 state.lineHeight = 1.0f;
2880 state.fontBlur = 0.0f;
2881 state.textAlign.reset;
2882 state.fontId = 0;
2883 state.evenOddMode = false;
2884 state.dashCount = 0;
2885 state.lastFlattenDashCount = 0;
2886 state.dashStart = 0;
2887 state.firstDashIsGap = false;
2888 state.dasherActive = false;
2890 ctx.params.renderResetClip(ctx.params.userPtr);
2893 /** Returns `true` if we have any room in state stack.
2894 * It is guaranteed to have at least 32 stack slots.
2896 * Group: state_handling
2898 public bool canSave (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates < NVG_MAX_STATES); }
2900 /** Returns `true` if we have any saved state.
2902 * Group: state_handling
2904 public bool canRestore (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates > 1); }
2906 /// Returns `true` if rendering is currently blocked.
2907 /// Group: state_handling
2908 public bool renderBlocked (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.recblockdraw : false); }
2910 /// Blocks/unblocks rendering
2911 /// Group: state_handling
2912 public void renderBlocked (NVGContext ctx, bool v) pure nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null && ctx.contextAlive) ctx.recblockdraw = v; }
2914 /// Blocks/unblocks rendering; returns previous state.
2915 /// Group: state_handling
2916 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; }
2919 // ////////////////////////////////////////////////////////////////////////// //
2920 // Render styles
2922 /// Sets filling mode to "even-odd".
2923 /// Group: render_styles
2924 public void evenOddFill (NVGContext ctx) nothrow @trusted @nogc {
2925 NVGstate* state = nvg__getState(ctx);
2926 state.evenOddMode = true;
2929 /// Sets filling mode to "non-zero" (this is default mode).
2930 /// Group: render_styles
2931 public void nonZeroFill (NVGContext ctx) nothrow @trusted @nogc {
2932 NVGstate* state = nvg__getState(ctx);
2933 state.evenOddMode = false;
2936 /// Sets whether to draw antialias for [stroke] and [fill]. It's enabled by default.
2937 /// Group: render_styles
2938 public void shapeAntiAlias (NVGContext ctx, bool enabled) {
2939 NVGstate* state = nvg__getState(ctx);
2940 state.shapeAntiAlias = enabled;
2943 /// Sets the stroke width of the stroke style.
2944 /// Group: render_styles
2945 @scriptable
2946 public void strokeWidth (NVGContext ctx, float width) nothrow @trusted @nogc {
2947 NVGstate* state = nvg__getState(ctx);
2948 state.strokeWidth = width;
2951 /// Sets the miter limit of the stroke style. Miter limit controls when a sharp corner is beveled.
2952 /// Group: render_styles
2953 public void miterLimit (NVGContext ctx, float limit) nothrow @trusted @nogc {
2954 NVGstate* state = nvg__getState(ctx);
2955 state.miterLimit = limit;
2958 /// Sets how the end of the line (cap) is drawn,
2959 /// Can be one of: NVGLineCap.Butt (default), NVGLineCap.Round, NVGLineCap.Square.
2960 /// Group: render_styles
2961 public void lineCap (NVGContext ctx, NVGLineCap cap) nothrow @trusted @nogc {
2962 NVGstate* state = nvg__getState(ctx);
2963 state.lineCap = cap;
2966 /// Sets how sharp path corners are drawn.
2967 /// Can be one of NVGLineCap.Miter (default), NVGLineCap.Round, NVGLineCap.Bevel.
2968 /// Group: render_styles
2969 public void lineJoin (NVGContext ctx, NVGLineCap join) nothrow @trusted @nogc {
2970 NVGstate* state = nvg__getState(ctx);
2971 state.lineJoin = join;
2974 /// Sets stroke dashing, using (dash_length, gap_length) pairs.
2975 /// Current limit is 16 pairs.
2976 /// Resets dash start to zero.
2977 /// Group: render_styles
2978 public void setLineDash (NVGContext ctx, const(float)[] dashdata) nothrow @trusted @nogc {
2979 NVGstate* state = nvg__getState(ctx);
2980 state.dashCount = 0;
2981 state.dashStart = 0;
2982 state.firstDashIsGap = false;
2983 if (dashdata.length >= 2) {
2984 bool curFIsGap = true; // trick
2985 foreach (immutable idx, float f; dashdata) {
2986 curFIsGap = !curFIsGap;
2987 if (f < 0.01f) continue; // skip it
2988 if (idx == 0) {
2989 // register first dash
2990 state.firstDashIsGap = curFIsGap;
2991 state.dashes.ptr[state.dashCount++] = f;
2992 } else {
2993 if ((idx&1) != ((state.dashCount&1)^cast(uint)state.firstDashIsGap)) {
2994 // oops, continuation
2995 state.dashes[state.dashCount-1] += f;
2996 } else {
2997 if (state.dashCount == state.dashes.length) break;
2998 state.dashes[state.dashCount++] = f;
3002 if (state.dashCount&1) {
3003 if (state.dashCount == 1) {
3004 state.dashCount = 0;
3005 } else {
3006 assert(state.dashCount < state.dashes.length);
3007 state.dashes[state.dashCount++] = 0;
3010 // calculate total dash path length
3011 state.totalDashLen = 0;
3012 foreach (float f; state.dashes.ptr[0..state.dashCount]) state.totalDashLen += f;
3013 if (state.totalDashLen < 0.01f) {
3014 state.dashCount = 0; // nothing to do
3015 } else {
3016 if (state.lastFlattenDashCount != 0) state.lastFlattenDashCount = uint.max; // force re-flattening
3021 public alias lineDash = setLineDash; /// Ditto.
3023 /// Sets stroke dashing, using (dash_length, gap_length) pairs.
3024 /// Current limit is 16 pairs.
3025 /// Group: render_styles
3026 public void setLineDashStart (NVGContext ctx, in float dashStart) nothrow @trusted @nogc {
3027 NVGstate* state = nvg__getState(ctx);
3028 if (state.lastFlattenDashCount != 0 && state.dashStart != dashStart) {
3029 state.lastFlattenDashCount = uint.max; // force re-flattening
3031 state.dashStart = dashStart;
3034 public alias lineDashStart = setLineDashStart; /// Ditto.
3036 /// Sets the transparency applied to all rendered shapes.
3037 /// Already transparent paths will get proportionally more transparent as well.
3038 /// Group: render_styles
3039 public void globalAlpha (NVGContext ctx, float alpha) nothrow @trusted @nogc {
3040 NVGstate* state = nvg__getState(ctx);
3041 state.alpha = alpha;
3044 static if (NanoVegaHasArsdColor) {
3045 /// Sets current stroke style to a solid color.
3046 /// Group: render_styles
3047 public void strokeColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
3048 NVGstate* state = nvg__getState(ctx);
3049 nvg__setPaintColor(state.stroke, NVGColor(color));
3053 /// Sets current stroke style to a solid color.
3054 /// Group: render_styles
3055 public void strokeColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc {
3056 NVGstate* state = nvg__getState(ctx);
3057 nvg__setPaintColor(state.stroke, color);
3060 /// Sets current stroke style to a paint, which can be a one of the gradients or a pattern.
3061 /// Group: render_styles
3062 public void strokePaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc {
3063 NVGstate* state = nvg__getState(ctx);
3064 state.stroke = paint;
3065 //nvgTransformMultiply(state.stroke.xform[], state.xform[]);
3066 state.stroke.xform.mul(state.xform);
3069 // this is a hack to work around https://issues.dlang.org/show_bug.cgi?id=16206
3070 // for scriptable reflection. it just needs to be declared first among the overloads
3071 private void fillColor (NVGContext ctx) nothrow @trusted @nogc { }
3073 static if (NanoVegaHasArsdColor) {
3074 /// Sets current fill style to a solid color.
3075 /// Group: render_styles
3076 @scriptable
3077 public void fillColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
3078 NVGstate* state = nvg__getState(ctx);
3079 nvg__setPaintColor(state.fill, NVGColor(color));
3083 /// Sets current fill style to a solid color.
3084 /// Group: render_styles
3085 public void fillColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc {
3086 NVGstate* state = nvg__getState(ctx);
3087 nvg__setPaintColor(state.fill, color);
3090 /// Sets current fill style to a paint, which can be a one of the gradients or a pattern.
3091 /// Group: render_styles
3092 public void fillPaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc {
3093 NVGstate* state = nvg__getState(ctx);
3094 state.fill = paint;
3095 //nvgTransformMultiply(state.fill.xform[], state.xform[]);
3096 state.fill.xform.mul(state.xform);
3099 /// Sets current fill style to a multistop linear gradient.
3100 /// Group: render_styles
3101 public void fillPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc {
3102 if (!lgs.valid) {
3103 NVGPaint p = void;
3104 memset(&p, 0, p.sizeof);
3105 nvg__setPaintColor(p, NVGColor.red);
3106 ctx.fillPaint = p;
3107 } else if (lgs.midp >= -1) {
3108 //{ import core.stdc.stdio; printf("SIMPLE! midp=%f\n", cast(double)lgs.midp); }
3109 ctx.fillPaint = ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
3110 } else {
3111 ctx.fillPaint = ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
3115 /// Returns current transformation matrix.
3116 /// Group: render_transformations
3117 public NVGMatrix currTransform (NVGContext ctx) pure nothrow @trusted @nogc {
3118 NVGstate* state = nvg__getState(ctx);
3119 return state.xform;
3122 /// Sets current transformation matrix.
3123 /// Group: render_transformations
3124 public void currTransform() (NVGContext ctx, in auto ref NVGMatrix m) nothrow @trusted @nogc {
3125 NVGstate* state = nvg__getState(ctx);
3126 state.xform = m;
3129 /// Resets current transform to an identity matrix.
3130 /// Group: render_transformations
3131 @scriptable
3132 public void resetTransform (NVGContext ctx) nothrow @trusted @nogc {
3133 NVGstate* state = nvg__getState(ctx);
3134 state.xform.identity;
3137 /// Premultiplies current coordinate system by specified matrix.
3138 /// Group: render_transformations
3139 public void transform() (NVGContext ctx, in auto ref NVGMatrix mt) nothrow @trusted @nogc {
3140 NVGstate* state = nvg__getState(ctx);
3141 //nvgTransformPremultiply(state.xform[], t[]);
3142 state.xform *= mt;
3145 /// Translates current coordinate system.
3146 /// Group: render_transformations
3147 @scriptable
3148 public void translate (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
3149 NVGstate* state = nvg__getState(ctx);
3150 //NVGMatrix t = void;
3151 //nvgTransformTranslate(t[], x, y);
3152 //nvgTransformPremultiply(state.xform[], t[]);
3153 state.xform.premul(NVGMatrix.Translated(x, y));
3156 /// Rotates current coordinate system. Angle is specified in radians.
3157 /// Group: render_transformations
3158 @scriptable
3159 public void rotate (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3160 NVGstate* state = nvg__getState(ctx);
3161 //NVGMatrix t = void;
3162 //nvgTransformRotate(t[], angle);
3163 //nvgTransformPremultiply(state.xform[], t[]);
3164 state.xform.premul(NVGMatrix.Rotated(angle));
3167 /// Skews the current coordinate system along X axis. Angle is specified in radians.
3168 /// Group: render_transformations
3169 @scriptable
3170 public void skewX (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3171 NVGstate* state = nvg__getState(ctx);
3172 //NVGMatrix t = void;
3173 //nvgTransformSkewX(t[], angle);
3174 //nvgTransformPremultiply(state.xform[], t[]);
3175 state.xform.premul(NVGMatrix.SkewedX(angle));
3178 /// Skews the current coordinate system along Y axis. Angle is specified in radians.
3179 /// Group: render_transformations
3180 @scriptable
3181 public void skewY (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3182 NVGstate* state = nvg__getState(ctx);
3183 //NVGMatrix t = void;
3184 //nvgTransformSkewY(t[], angle);
3185 //nvgTransformPremultiply(state.xform[], t[]);
3186 state.xform.premul(NVGMatrix.SkewedY(angle));
3189 /// Scales the current coordinate system.
3190 /// Group: render_transformations
3191 @scriptable
3192 public void scale (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
3193 NVGstate* state = nvg__getState(ctx);
3194 //NVGMatrix t = void;
3195 //nvgTransformScale(t[], x, y);
3196 //nvgTransformPremultiply(state.xform[], t[]);
3197 state.xform.premul(NVGMatrix.Scaled(x, y));
3201 // ////////////////////////////////////////////////////////////////////////// //
3202 // Images
3204 /// Creates image by loading it from the disk from specified file name.
3205 /// Returns handle to the image or 0 on error.
3206 /// Group: images
3207 public NVGImage createImage() (NVGContext ctx, const(char)[] filename, const(NVGImageFlag)[] imageFlagsList...) {
3208 static if (NanoVegaHasArsdImage) {
3209 import arsd.image;
3210 // do we have new arsd API to load images?
3211 static if (!is(typeof(MemoryImage.fromImageFile)) || !is(typeof(MemoryImage.clearInternal))) {
3212 static assert(0, "Sorry, your ARSD is too old. Please, update it.");
3214 try {
3215 auto oimg = MemoryImage.fromImageFile(filename);
3216 if (auto img = cast(TrueColorImage)oimg) {
3217 scope(exit) oimg.clearInternal();
3218 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList);
3219 } else {
3220 TrueColorImage img = oimg.getAsTrueColorImage;
3221 scope(exit) img.clearInternal();
3222 oimg.clearInternal(); // drop original image, as `getAsTrueColorImage()` MUST create a new one here
3223 oimg = null;
3224 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList);
3226 } catch (Exception) {}
3227 return NVGImage.init;
3228 } else {
3229 import std.internal.cstring;
3230 ubyte* img;
3231 int w, h, n;
3232 stbi_set_unpremultiply_on_load(1);
3233 stbi_convert_iphone_png_to_rgb(1);
3234 img = stbi_load(filename.tempCString, &w, &h, &n, 4);
3235 if (img is null) {
3236 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
3237 return NVGImage.init;
3239 auto image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList);
3240 stbi_image_free(img);
3241 return image;
3245 static if (NanoVegaHasArsdImage) {
3246 /// Creates image by loading it from the specified memory image.
3247 /// Returns handle to the image or 0 on error.
3248 /// Group: images
3249 public NVGImage createImageFromMemoryImage() (NVGContext ctx, MemoryImage img, const(NVGImageFlag)[] imageFlagsList...) {
3250 if (img is null) return NVGImage.init;
3251 if (auto tc = cast(TrueColorImage)img) {
3252 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList);
3253 } else {
3254 auto tc = img.getAsTrueColorImage;
3255 scope(exit) tc.clearInternal(); // here, it is guaranteed that `tc` is newly allocated image, so it is safe to kill it
3256 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList);
3259 } else {
3260 /// Creates image by loading it from the specified chunk of memory.
3261 /// Returns handle to the image or 0 on error.
3262 /// Group: images
3263 public NVGImage createImageMem() (NVGContext ctx, const(ubyte)* data, int ndata, const(NVGImageFlag)[] imageFlagsList...) {
3264 int w, h, n, image;
3265 ubyte* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4);
3266 if (img is null) {
3267 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
3268 return NVGImage.init;
3270 image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList);
3271 stbi_image_free(img);
3272 return image;
3276 /// Creates image from specified image data.
3277 /// Returns handle to the image or 0 on error.
3278 /// Group: images
3279 public NVGImage createImageRGBA (NVGContext ctx, int w, int h, const(void)[] data, const(NVGImageFlag)[] imageFlagsList...) nothrow @trusted @nogc {
3280 if (w < 1 || h < 1 || data.length < w*h*4) return NVGImage.init;
3281 uint imageFlags = 0;
3282 foreach (immutable uint flag; imageFlagsList) imageFlags |= flag;
3283 NVGImage res;
3284 res.id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.RGBA, w, h, imageFlags, cast(const(ubyte)*)data.ptr);
3285 if (res.id > 0) {
3286 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); }
3287 res.ctx = ctx;
3288 ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount
3290 return res;
3293 /// Updates image data specified by image handle.
3294 /// Group: images
3295 public void updateImage() (NVGContext ctx, auto ref NVGImage image, const(void)[] data) nothrow @trusted @nogc {
3296 if (image.valid) {
3297 int w, h;
3298 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3299 ctx.params.renderGetTextureSize(ctx.params.userPtr, image.id, &w, &h);
3300 ctx.params.renderUpdateTexture(ctx.params.userPtr, image.id, 0, 0, w, h, cast(const(ubyte)*)data.ptr);
3304 /// Returns the dimensions of a created image.
3305 /// Group: images
3306 public void imageSize() (NVGContext ctx, in auto ref NVGImage image, out int w, out int h) nothrow @trusted @nogc {
3307 if (image.valid) {
3308 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3309 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, image.id, &w, &h);
3313 /// Deletes created image.
3314 /// Group: images
3315 public void deleteImage() (NVGContext ctx, ref NVGImage image) nothrow @trusted @nogc {
3316 if (ctx is null || !image.valid) return;
3317 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3318 image.clear();
3322 // ////////////////////////////////////////////////////////////////////////// //
3323 // Paints
3325 static if (NanoVegaHasArsdColor) {
3326 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates
3327 * of the linear gradient, icol specifies the start color and ocol the end color.
3328 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3330 * Group: paints
3332 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 {
3333 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), NVGColor(ocol));
3335 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
3336 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
3337 * range `(0..1)`, and ocol the end color.
3338 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3340 * Group: paints
3342 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 {
3343 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), midp, NVGColor(mcol), NVGColor(ocol));
3347 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates
3348 * of the linear gradient, icol specifies the start color and ocol the end color.
3349 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3351 * Group: paints
3353 public NVGPaint linearGradient() (NVGContext ctx, float sx, float sy, float ex, float ey, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc {
3354 enum large = 1e5f;
3356 NVGPaint p = void;
3357 memset(&p, 0, p.sizeof);
3358 p.simpleColor = false;
3360 // Calculate transform aligned to the line
3361 float dx = ex-sx;
3362 float dy = ey-sy;
3363 immutable float d = nvg__sqrtf(dx*dx+dy*dy);
3364 if (d > 0.0001f) {
3365 dx /= d;
3366 dy /= d;
3367 } else {
3368 dx = 0;
3369 dy = 1;
3372 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx;
3373 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy;
3374 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large;
3376 p.extent.ptr[0] = large;
3377 p.extent.ptr[1] = large+d*0.5f;
3379 p.radius = 0.0f;
3381 p.feather = nvg__max(NVG_MIN_FEATHER, d);
3383 p.innerColor = p.middleColor = icol;
3384 p.outerColor = ocol;
3385 p.midp = -1;
3387 return p;
3390 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
3391 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
3392 * range `(0..1)`, and ocol the end color.
3393 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3395 * Group: paints
3397 public NVGPaint linearGradient() (NVGContext ctx, float sx, float sy, float ex, float ey, in auto ref NVGColor icol, in float midp, in auto ref NVGColor mcol, in auto ref NVGColor ocol) nothrow @trusted @nogc {
3398 enum large = 1e5f;
3400 NVGPaint p = void;
3401 memset(&p, 0, p.sizeof);
3402 p.simpleColor = false;
3404 // Calculate transform aligned to the line
3405 float dx = ex-sx;
3406 float dy = ey-sy;
3407 immutable float d = nvg__sqrtf(dx*dx+dy*dy);
3408 if (d > 0.0001f) {
3409 dx /= d;
3410 dy /= d;
3411 } else {
3412 dx = 0;
3413 dy = 1;
3416 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx;
3417 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy;
3418 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large;
3420 p.extent.ptr[0] = large;
3421 p.extent.ptr[1] = large+d*0.5f;
3423 p.radius = 0.0f;
3425 p.feather = nvg__max(NVG_MIN_FEATHER, d);
3427 if (midp <= 0) {
3428 p.innerColor = p.middleColor = mcol;
3429 p.midp = -1;
3430 } else if (midp > 1) {
3431 p.innerColor = p.middleColor = icol;
3432 p.midp = -1;
3433 } else {
3434 p.innerColor = icol;
3435 p.middleColor = mcol;
3436 p.midp = midp;
3438 p.outerColor = ocol;
3440 return p;
3443 static if (NanoVegaHasArsdColor) {
3444 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify
3445 * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
3446 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3448 * Group: paints
3450 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 {
3451 return ctx.radialGradient(cx, cy, inr, outr, NVGColor(icol), NVGColor(ocol));
3455 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify
3456 * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
3457 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3459 * Group: paints
3461 public NVGPaint radialGradient() (NVGContext ctx, float cx, float cy, float inr, float outr, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc {
3462 immutable float r = (inr+outr)*0.5f;
3463 immutable float f = (outr-inr);
3465 NVGPaint p = void;
3466 memset(&p, 0, p.sizeof);
3467 p.simpleColor = false;
3469 p.xform.identity;
3470 p.xform.mat.ptr[4] = cx;
3471 p.xform.mat.ptr[5] = cy;
3473 p.extent.ptr[0] = r;
3474 p.extent.ptr[1] = r;
3476 p.radius = r;
3478 p.feather = nvg__max(NVG_MIN_FEATHER, f);
3480 p.innerColor = p.middleColor = icol;
3481 p.outerColor = ocol;
3482 p.midp = -1;
3484 return p;
3487 static if (NanoVegaHasArsdColor) {
3488 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
3489 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle,
3490 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
3491 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
3492 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3494 * Group: paints
3496 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 {
3497 return ctx.boxGradient(x, y, w, h, r, f, NVGColor(icol), NVGColor(ocol));
3501 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
3502 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle,
3503 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
3504 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
3505 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3507 * Group: paints
3509 public NVGPaint boxGradient() (NVGContext ctx, float x, float y, float w, float h, float r, float f, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc {
3510 NVGPaint p = void;
3511 memset(&p, 0, p.sizeof);
3512 p.simpleColor = false;
3514 p.xform.identity;
3515 p.xform.mat.ptr[4] = x+w*0.5f;
3516 p.xform.mat.ptr[5] = y+h*0.5f;
3518 p.extent.ptr[0] = w*0.5f;
3519 p.extent.ptr[1] = h*0.5f;
3521 p.radius = r;
3523 p.feather = nvg__max(NVG_MIN_FEATHER, f);
3525 p.innerColor = p.middleColor = icol;
3526 p.outerColor = ocol;
3527 p.midp = -1;
3529 return p;
3532 /** Creates and returns an image pattern. Parameters `(cx, cy)` specify the left-top location of the image pattern,
3533 * `(w, h)` the size of one image, [angle] rotation around the top-left corner, [image] is handle to the image to render.
3534 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3536 * Group: paints
3538 public NVGPaint imagePattern() (NVGContext ctx, float cx, float cy, float w, float h, float angle, in auto ref NVGImage image, float alpha=1) nothrow @trusted @nogc {
3539 NVGPaint p = void;
3540 memset(&p, 0, p.sizeof);
3541 p.simpleColor = false;
3543 p.xform.identity.rotate(angle);
3544 p.xform.mat.ptr[4] = cx;
3545 p.xform.mat.ptr[5] = cy;
3547 p.extent.ptr[0] = w;
3548 p.extent.ptr[1] = h;
3550 p.image = image;
3552 p.innerColor = p.middleColor = p.outerColor = nvgRGBAf(1, 1, 1, alpha);
3553 p.midp = -1;
3555 return p;
3558 /// Linear gradient with multiple stops.
3559 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3560 /// Group: paints
3561 public struct NVGLGS {
3562 private:
3563 NVGColor ic, mc, oc; // inner, middle, out
3564 float midp;
3565 NVGImage imgid;
3566 // [imagePattern] arguments
3567 float cx, cy, dimx, dimy; // dimx and dimy are ex and ey for simple gradients
3568 public float angle; ///
3570 public:
3571 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (imgid.valid || midp >= -1); } ///
3572 void clear () nothrow @safe @nogc { pragma(inline, true); imgid.clear(); midp = float.nan; } ///
3575 /** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops].
3576 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3578 * $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3579 * Group: paints
3581 public NVGPaint asPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc {
3582 if (!lgs.valid) {
3583 NVGPaint p = void;
3584 memset(&p, 0, p.sizeof);
3585 nvg__setPaintColor(p, NVGColor.red);
3586 return p;
3587 } else if (lgs.midp >= -1) {
3588 return ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
3589 } else {
3590 return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
3594 /// Gradient Stop Point.
3595 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3596 /// Group: paints
3597 public struct NVGGradientStop {
3598 float offset = 0; /// [0..1]
3599 NVGColor color; ///
3601 this() (in float aofs, in auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } ///
3602 static if (NanoVegaHasArsdColor) {
3603 this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } ///
3607 /// Create linear gradient data suitable to use with `linearGradient(res)`.
3608 /// Don't forget to destroy the result when you don't need it anymore with `ctx.kill(res);`.
3609 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3610 /// Group: paints
3611 public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, const(NVGGradientStop)[] stops...) nothrow @trusted @nogc {
3612 // based on the code by Jorge Acereda <jacereda@gmail.com>
3613 enum NVG_GRADIENT_SAMPLES = 1024;
3614 static void gradientSpan (uint* dst, const(NVGGradientStop)* s0, const(NVGGradientStop)* s1) nothrow @trusted @nogc {
3615 immutable float s0o = nvg__clamp(s0.offset, 0.0f, 1.0f);
3616 immutable float s1o = nvg__clamp(s1.offset, 0.0f, 1.0f);
3617 uint s = cast(uint)(s0o*NVG_GRADIENT_SAMPLES);
3618 uint e = cast(uint)(s1o*NVG_GRADIENT_SAMPLES);
3619 uint sc = 0xffffffffU;
3620 uint sh = 24;
3621 uint r = cast(uint)(s0.color.rgba[0]*sc);
3622 uint g = cast(uint)(s0.color.rgba[1]*sc);
3623 uint b = cast(uint)(s0.color.rgba[2]*sc);
3624 uint a = cast(uint)(s0.color.rgba[3]*sc);
3625 uint dr = cast(uint)((s1.color.rgba[0]*sc-r)/(e-s));
3626 uint dg = cast(uint)((s1.color.rgba[1]*sc-g)/(e-s));
3627 uint db = cast(uint)((s1.color.rgba[2]*sc-b)/(e-s));
3628 uint da = cast(uint)((s1.color.rgba[3]*sc-a)/(e-s));
3629 dst += s;
3630 foreach (immutable _; s..e) {
3631 version(BigEndian) {
3632 *dst++ = ((r>>sh)<<24)+((g>>sh)<<16)+((b>>sh)<<8)+((a>>sh)<<0);
3633 } else {
3634 *dst++ = ((a>>sh)<<24)+((b>>sh)<<16)+((g>>sh)<<8)+((r>>sh)<<0);
3636 r += dr;
3637 g += dg;
3638 b += db;
3639 a += da;
3643 NVGLGS res;
3644 res.cx = sx;
3645 res.cy = sy;
3647 if (stops.length == 2 && stops.ptr[0].offset <= 0 && stops.ptr[1].offset >= 1) {
3648 // create simple linear gradient
3649 res.ic = res.mc = stops.ptr[0].color;
3650 res.oc = stops.ptr[1].color;
3651 res.midp = -1;
3652 res.dimx = ex;
3653 res.dimy = ey;
3654 } else if (stops.length == 3 && stops.ptr[0].offset <= 0 && stops.ptr[2].offset >= 1) {
3655 // create simple linear gradient with middle stop
3656 res.ic = stops.ptr[0].color;
3657 res.mc = stops.ptr[1].color;
3658 res.oc = stops.ptr[2].color;
3659 res.midp = stops.ptr[1].offset;
3660 res.dimx = ex;
3661 res.dimy = ey;
3662 } else {
3663 // create image gradient
3664 uint[NVG_GRADIENT_SAMPLES] data = void;
3665 immutable float w = ex-sx;
3666 immutable float h = ey-sy;
3667 res.dimx = nvg__sqrtf(w*w+h*h);
3668 res.dimy = 1; //???
3670 res.angle =
3671 (/*nvg__absf(h) < 0.0001 ? 0 :
3672 nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/
3673 nvg__atan2f(h/*ey-sy*/, w/*ex-sx*/));
3675 if (stops.length > 0) {
3676 auto s0 = NVGGradientStop(0, nvgRGBAf(0, 0, 0, 1));
3677 auto s1 = NVGGradientStop(1, nvgRGBAf(1, 1, 1, 1));
3678 if (stops.length > 64) stops = stops[0..64];
3679 if (stops.length) {
3680 s0.color = stops[0].color;
3681 s1.color = stops[$-1].color;
3683 gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1));
3684 foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1);
3685 gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1);
3686 res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[]/*, NVGImageFlag.RepeatX, NVGImageFlag.RepeatY*/);
3689 return res;
3693 // ////////////////////////////////////////////////////////////////////////// //
3694 // Scissoring
3696 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform.
3697 /// Group: scissoring
3698 public void scissor (NVGContext ctx, in float x, in float y, float w, float h) nothrow @trusted @nogc {
3699 NVGstate* state = nvg__getState(ctx);
3701 w = nvg__max(0.0f, w);
3702 h = nvg__max(0.0f, h);
3704 state.scissor.xform.identity;
3705 state.scissor.xform.mat.ptr[4] = x+w*0.5f;
3706 state.scissor.xform.mat.ptr[5] = y+h*0.5f;
3707 //nvgTransformMultiply(state.scissor.xform[], state.xform[]);
3708 state.scissor.xform.mul(state.xform);
3710 state.scissor.extent.ptr[0] = w*0.5f;
3711 state.scissor.extent.ptr[1] = h*0.5f;
3714 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform.
3715 /// Arguments: [x, y, w, h]*
3716 /// Group: scissoring
3717 public void scissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
3718 enum ArgC = 4;
3719 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [scissor] call");
3720 if (args.length < ArgC) return;
3721 NVGstate* state = nvg__getState(ctx);
3722 const(float)* aptr = args.ptr;
3723 foreach (immutable idx; 0..args.length/ArgC) {
3724 immutable x = *aptr++;
3725 immutable y = *aptr++;
3726 immutable w = nvg__max(0.0f, *aptr++);
3727 immutable h = nvg__max(0.0f, *aptr++);
3729 state.scissor.xform.identity;
3730 state.scissor.xform.mat.ptr[4] = x+w*0.5f;
3731 state.scissor.xform.mat.ptr[5] = y+h*0.5f;
3732 //nvgTransformMultiply(state.scissor.xform[], state.xform[]);
3733 state.scissor.xform.mul(state.xform);
3735 state.scissor.extent.ptr[0] = w*0.5f;
3736 state.scissor.extent.ptr[1] = h*0.5f;
3740 void nvg__isectRects (float* dst, float ax, float ay, float aw, float ah, float bx, float by, float bw, float bh) nothrow @trusted @nogc {
3741 immutable float minx = nvg__max(ax, bx);
3742 immutable float miny = nvg__max(ay, by);
3743 immutable float maxx = nvg__min(ax+aw, bx+bw);
3744 immutable float maxy = nvg__min(ay+ah, by+bh);
3745 dst[0] = minx;
3746 dst[1] = miny;
3747 dst[2] = nvg__max(0.0f, maxx-minx);
3748 dst[3] = nvg__max(0.0f, maxy-miny);
3751 /** Intersects current scissor rectangle with the specified rectangle.
3752 * The scissor rectangle is transformed by the current transform.
3753 * Note: in case the rotation of previous scissor rect differs from
3754 * the current one, the intersection will be done between the specified
3755 * rectangle and the previous scissor rectangle transformed in the current
3756 * transform space. The resulting shape is always rectangle.
3758 * Group: scissoring
3760 public void intersectScissor (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc {
3761 NVGstate* state = nvg__getState(ctx);
3763 // If no previous scissor has been set, set the scissor as current scissor.
3764 if (state.scissor.extent.ptr[0] < 0) {
3765 ctx.scissor(x, y, w, h);
3766 return;
3769 NVGMatrix pxform = void;
3770 NVGMatrix invxorm = void;
3771 float[4] rect = void;
3773 // Transform the current scissor rect into current transform space.
3774 // If there is difference in rotation, this will be approximation.
3775 //memcpy(pxform.mat.ptr, state.scissor.xform.ptr, float.sizeof*6);
3776 pxform = state.scissor.xform;
3777 immutable float ex = state.scissor.extent.ptr[0];
3778 immutable float ey = state.scissor.extent.ptr[1];
3779 //nvgTransformInverse(invxorm[], state.xform[]);
3780 invxorm = state.xform.inverted;
3781 //nvgTransformMultiply(pxform[], invxorm[]);
3782 pxform.mul(invxorm);
3783 immutable float tex = ex*nvg__absf(pxform.mat.ptr[0])+ey*nvg__absf(pxform.mat.ptr[2]);
3784 immutable float tey = ex*nvg__absf(pxform.mat.ptr[1])+ey*nvg__absf(pxform.mat.ptr[3]);
3786 // Intersect rects.
3787 nvg__isectRects(rect.ptr, pxform.mat.ptr[4]-tex, pxform.mat.ptr[5]-tey, tex*2, tey*2, x, y, w, h);
3789 //ctx.scissor(rect.ptr[0], rect.ptr[1], rect.ptr[2], rect.ptr[3]);
3790 ctx.scissor(rect.ptr[0..4]);
3793 /** Intersects current scissor rectangle with the specified rectangle.
3794 * The scissor rectangle is transformed by the current transform.
3795 * Note: in case the rotation of previous scissor rect differs from
3796 * the current one, the intersection will be done between the specified
3797 * rectangle and the previous scissor rectangle transformed in the current
3798 * transform space. The resulting shape is always rectangle.
3800 * Arguments: [x, y, w, h]*
3802 * Group: scissoring
3804 public void intersectScissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
3805 enum ArgC = 4;
3806 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [intersectScissor] call");
3807 if (args.length < ArgC) return;
3808 const(float)* aptr = args.ptr;
3809 foreach (immutable idx; 0..args.length/ArgC) {
3810 immutable x = *aptr++;
3811 immutable y = *aptr++;
3812 immutable w = *aptr++;
3813 immutable h = *aptr++;
3814 ctx.intersectScissor(x, y, w, h);
3818 /// Reset and disables scissoring.
3819 /// Group: scissoring
3820 public void resetScissor (NVGContext ctx) nothrow @trusted @nogc {
3821 NVGstate* state = nvg__getState(ctx);
3822 state.scissor.xform.mat[] = 0.0f;
3823 state.scissor.extent[] = -1.0f;
3827 // ////////////////////////////////////////////////////////////////////////// //
3828 // Render-Time Affine Transformations
3830 /// Sets GPU affine transformatin matrix. Don't do scaling or skewing here.
3831 /// This matrix won't be saved/restored with context state save/restore operations, as it is not a part of that state.
3832 /// Group: gpu_affine
3833 public void affineGPU() (NVGContext ctx, in auto ref NVGMatrix mat) nothrow @trusted @nogc {
3834 ctx.gpuAffine = mat;
3835 ctx.params.renderSetAffine(ctx.params.userPtr, ctx.gpuAffine);
3838 /// Get current GPU affine transformatin matrix.
3839 /// Group: gpu_affine
3840 public NVGMatrix affineGPU (NVGContext ctx) nothrow @safe @nogc {
3841 pragma(inline, true);
3842 return ctx.gpuAffine;
3845 /// "Untransform" point using current GPU affine matrix.
3846 /// Group: gpu_affine
3847 public void gpuUntransformPoint (NVGContext ctx, float *dx, float *dy, in float x, in float y) nothrow @safe @nogc {
3848 if (ctx.gpuAffine.isIdentity) {
3849 if (dx !is null) *dx = x;
3850 if (dy !is null) *dy = y;
3851 } else {
3852 // inverse GPU transformation
3853 NVGMatrix igpu = ctx.gpuAffine.inverted;
3854 igpu.point(dx, dy, x, y);
3859 // ////////////////////////////////////////////////////////////////////////// //
3860 // rasterization (tesselation) code
3862 int nvg__ptEquals (float x1, float y1, float x2, float y2, float tol) pure nothrow @safe @nogc {
3863 //pragma(inline, true);
3864 immutable float dx = x2-x1;
3865 immutable float dy = y2-y1;
3866 return dx*dx+dy*dy < tol*tol;
3869 float nvg__distPtSeg (float x, float y, float px, float py, float qx, float qy) pure nothrow @safe @nogc {
3870 immutable float pqx = qx-px;
3871 immutable float pqy = qy-py;
3872 float dx = x-px;
3873 float dy = y-py;
3874 immutable float d = pqx*pqx+pqy*pqy;
3875 float t = pqx*dx+pqy*dy;
3876 if (d > 0) t /= d;
3877 if (t < 0) t = 0; else if (t > 1) t = 1;
3878 dx = px+t*pqx-x;
3879 dy = py+t*pqy-y;
3880 return dx*dx+dy*dy;
3883 void nvg__appendCommands(bool useCommand=true) (NVGContext ctx, Command acmd, const(float)[] vals...) nothrow @trusted @nogc {
3884 int nvals = cast(int)vals.length;
3885 static if (useCommand) {
3886 enum addon = 1;
3887 } else {
3888 enum addon = 0;
3889 if (nvals == 0) return; // nothing to do
3892 NVGstate* state = nvg__getState(ctx);
3894 if (ctx.ncommands+nvals+addon > ctx.ccommands) {
3895 //int ccommands = ctx.ncommands+nvals+ctx.ccommands/2;
3896 int ccommands = ((ctx.ncommands+(nvals+addon))|0xfff)+1;
3897 float* commands = cast(float*)realloc(ctx.commands, float.sizeof*ccommands);
3898 if (commands is null) assert(0, "NanoVega: out of memory");
3899 ctx.commands = commands;
3900 ctx.ccommands = ccommands;
3901 assert(ctx.ncommands+(nvals+addon) <= ctx.ccommands);
3904 static if (!useCommand) acmd = cast(Command)vals.ptr[0];
3906 if (acmd != Command.Close && acmd != Command.Winding) {
3907 //assert(nvals+addon >= 3);
3908 ctx.commandx = vals.ptr[nvals-2];
3909 ctx.commandy = vals.ptr[nvals-1];
3912 // copy commands
3913 float* vp = ctx.commands+ctx.ncommands;
3914 static if (useCommand) {
3915 vp[0] = cast(float)acmd;
3916 if (nvals > 0) memcpy(vp+1, vals.ptr, nvals*float.sizeof);
3917 } else {
3918 memcpy(vp, vals.ptr, nvals*float.sizeof);
3920 ctx.ncommands += nvals+addon;
3922 // transform commands
3923 int i = nvals+addon;
3924 while (i > 0) {
3925 int nlen = 1;
3926 final switch (cast(Command)(*vp)) {
3927 case Command.MoveTo:
3928 case Command.LineTo:
3929 assert(i >= 3);
3930 state.xform.point(vp+1, vp+2, vp[1], vp[2]);
3931 nlen = 3;
3932 break;
3933 case Command.BezierTo:
3934 assert(i >= 7);
3935 state.xform.point(vp+1, vp+2, vp[1], vp[2]);
3936 state.xform.point(vp+3, vp+4, vp[3], vp[4]);
3937 state.xform.point(vp+5, vp+6, vp[5], vp[6]);
3938 nlen = 7;
3939 break;
3940 case Command.Close:
3941 nlen = 1;
3942 break;
3943 case Command.Winding:
3944 nlen = 2;
3945 break;
3947 assert(nlen > 0 && nlen <= i);
3948 i -= nlen;
3949 vp += nlen;
3953 void nvg__clearPathCache (NVGContext ctx) nothrow @trusted @nogc {
3954 // no need to clear paths, as data is not copied there
3955 //foreach (ref p; ctx.cache.paths[0..ctx.cache.npaths]) p.clear();
3956 ctx.cache.npoints = 0;
3957 ctx.cache.npaths = 0;
3958 ctx.cache.fillReady = ctx.cache.strokeReady = false;
3959 ctx.cache.clipmode = NVGClipMode.None;
3962 NVGpath* nvg__lastPath (NVGContext ctx) nothrow @trusted @nogc {
3963 return (ctx.cache.npaths > 0 ? &ctx.cache.paths[ctx.cache.npaths-1] : null);
3966 void nvg__addPath (NVGContext ctx) nothrow @trusted @nogc {
3967 import core.stdc.stdlib : realloc;
3968 import core.stdc.string : memset;
3970 if (ctx.cache.npaths+1 > ctx.cache.cpaths) {
3971 int cpaths = ctx.cache.npaths+1+ctx.cache.cpaths/2;
3972 NVGpath* paths = cast(NVGpath*)realloc(ctx.cache.paths, NVGpath.sizeof*cpaths);
3973 if (paths is null) assert(0, "NanoVega: out of memory");
3974 ctx.cache.paths = paths;
3975 ctx.cache.cpaths = cpaths;
3978 NVGpath* path = &ctx.cache.paths[ctx.cache.npaths++];
3979 memset(path, 0, NVGpath.sizeof);
3980 path.first = ctx.cache.npoints;
3981 path.mWinding = NVGWinding.CCW;
3984 NVGpoint* nvg__lastPoint (NVGContext ctx) nothrow @trusted @nogc {
3985 return (ctx.cache.npoints > 0 ? &ctx.cache.points[ctx.cache.npoints-1] : null);
3988 void nvg__addPoint (NVGContext ctx, float x, float y, int flags) nothrow @trusted @nogc {
3989 NVGpath* path = nvg__lastPath(ctx);
3990 if (path is null) return;
3992 if (path.count > 0 && ctx.cache.npoints > 0) {
3993 NVGpoint* pt = nvg__lastPoint(ctx);
3994 if (nvg__ptEquals(pt.x, pt.y, x, y, ctx.distTol)) {
3995 pt.flags |= flags;
3996 return;
4000 if (ctx.cache.npoints+1 > ctx.cache.cpoints) {
4001 int cpoints = ctx.cache.npoints+1+ctx.cache.cpoints/2;
4002 NVGpoint* points = cast(NVGpoint*)realloc(ctx.cache.points, NVGpoint.sizeof*cpoints);
4003 if (points is null) return;
4004 ctx.cache.points = points;
4005 ctx.cache.cpoints = cpoints;
4008 NVGpoint* pt = &ctx.cache.points[ctx.cache.npoints];
4009 memset(pt, 0, (*pt).sizeof);
4010 pt.x = x;
4011 pt.y = y;
4012 pt.flags = cast(ubyte)flags;
4014 ++ctx.cache.npoints;
4015 ++path.count;
4018 void nvg__closePath (NVGContext ctx) nothrow @trusted @nogc {
4019 NVGpath* path = nvg__lastPath(ctx);
4020 if (path is null) return;
4021 path.closed = true;
4024 void nvg__pathWinding (NVGContext ctx, NVGWinding winding) nothrow @trusted @nogc {
4025 NVGpath* path = nvg__lastPath(ctx);
4026 if (path is null) return;
4027 path.mWinding = winding;
4030 float nvg__getAverageScale() (in auto ref NVGMatrix t) /*pure*/ nothrow @trusted @nogc {
4031 immutable float sx = nvg__sqrtf(t.mat.ptr[0]*t.mat.ptr[0]+t.mat.ptr[2]*t.mat.ptr[2]);
4032 immutable float sy = nvg__sqrtf(t.mat.ptr[1]*t.mat.ptr[1]+t.mat.ptr[3]*t.mat.ptr[3]);
4033 return (sx+sy)*0.5f;
4036 NVGVertex* nvg__allocTempVerts (NVGContext ctx, int nverts) nothrow @trusted @nogc {
4037 if (nverts > ctx.cache.cverts) {
4038 int cverts = (nverts+0xff)&~0xff; // Round up to prevent allocations when things change just slightly.
4039 NVGVertex* verts = cast(NVGVertex*)realloc(ctx.cache.verts, NVGVertex.sizeof*cverts);
4040 if (verts is null) return null;
4041 ctx.cache.verts = verts;
4042 ctx.cache.cverts = cverts;
4045 return ctx.cache.verts;
4048 float nvg__triarea2 (float ax, float ay, float bx, float by, float cx, float cy) pure nothrow @safe @nogc {
4049 immutable float abx = bx-ax;
4050 immutable float aby = by-ay;
4051 immutable float acx = cx-ax;
4052 immutable float acy = cy-ay;
4053 return acx*aby-abx*acy;
4056 float nvg__polyArea (NVGpoint* pts, int npts) nothrow @trusted @nogc {
4057 float area = 0;
4058 foreach (int i; 2..npts) {
4059 NVGpoint* a = &pts[0];
4060 NVGpoint* b = &pts[i-1];
4061 NVGpoint* c = &pts[i];
4062 area += nvg__triarea2(a.x, a.y, b.x, b.y, c.x, c.y);
4064 return area*0.5f;
4067 void nvg__polyReverse (NVGpoint* pts, int npts) nothrow @trusted @nogc {
4068 NVGpoint tmp = void;
4069 int i = 0, j = npts-1;
4070 while (i < j) {
4071 tmp = pts[i];
4072 pts[i] = pts[j];
4073 pts[j] = tmp;
4074 ++i;
4075 --j;
4079 void nvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc {
4080 vtx.x = x;
4081 vtx.y = y;
4082 vtx.u = u;
4083 vtx.v = v;
4086 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 {
4087 if (level > 10) return;
4089 // check for collinear points, and use AFD tesselator on such curves (it is WAY faster for this case)
4091 if (level == 0 && ctx.tesselatortype == NVGTesselation.Combined) {
4092 static bool collinear (in float v0x, in float v0y, in float v1x, in float v1y, in float v2x, in float v2y) nothrow @trusted @nogc {
4093 immutable float cz = (v1x-v0x)*(v2y-v0y)-(v2x-v0x)*(v1y-v0y);
4094 return (nvg__absf(cz*cz) <= 0.01f); // arbitrary number, seems to work ok with NanoSVG output
4096 if (collinear(x1, y1, x2, y2, x3, y3) && collinear(x2, y2, x3, y3, x3, y4)) {
4097 //{ import core.stdc.stdio; printf("AFD fallback!\n"); }
4098 ctx.nvg__tesselateBezierAFD(x1, y1, x2, y2, x3, y3, x4, y4, type);
4099 return;
4104 immutable float x12 = (x1+x2)*0.5f;
4105 immutable float y12 = (y1+y2)*0.5f;
4106 immutable float x23 = (x2+x3)*0.5f;
4107 immutable float y23 = (y2+y3)*0.5f;
4108 immutable float x34 = (x3+x4)*0.5f;
4109 immutable float y34 = (y3+y4)*0.5f;
4110 immutable float x123 = (x12+x23)*0.5f;
4111 immutable float y123 = (y12+y23)*0.5f;
4113 immutable float dx = x4-x1;
4114 immutable float dy = y4-y1;
4115 immutable float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx));
4116 immutable float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx));
4118 if ((d2+d3)*(d2+d3) < ctx.tessTol*(dx*dx+dy*dy)) {
4119 nvg__addPoint(ctx, x4, y4, type);
4120 return;
4123 immutable float x234 = (x23+x34)*0.5f;
4124 immutable float y234 = (y23+y34)*0.5f;
4125 immutable float x1234 = (x123+x234)*0.5f;
4126 immutable float y1234 = (y123+y234)*0.5f;
4128 // "taxicab" / "manhattan" check for flat curves
4129 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) {
4130 nvg__addPoint(ctx, x1234, y1234, type);
4131 return;
4134 nvg__tesselateBezier(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
4135 nvg__tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
4138 // based on the ideas and code of Maxim Shemanarev. Rest in Peace, bro!
4139 // see http://www.antigrain.com/research/adaptive_bezier/index.html
4140 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 {
4141 enum CollinearEPS = 0.00000001f; // 0.00001f;
4142 enum AngleTolEPS = 0.01f;
4144 static float distSquared (in float x1, in float y1, in float x2, in float y2) pure nothrow @safe @nogc {
4145 pragma(inline, true);
4146 immutable float dx = x2-x1;
4147 immutable float dy = y2-y1;
4148 return dx*dx+dy*dy;
4151 if (level == 0) {
4152 nvg__addPoint(ctx, x1, y1, 0);
4153 nvg__tesselateBezierMcSeem(ctx, x1, y1, x2, y2, x3, y3, x4, y4, 1, type);
4154 nvg__addPoint(ctx, x4, y4, type);
4155 return;
4158 if (level >= 32) return; // recurse limit; practically, it should be never reached, but...
4160 // calculate all the mid-points of the line segments
4161 immutable float x12 = (x1+x2)*0.5f;
4162 immutable float y12 = (y1+y2)*0.5f;
4163 immutable float x23 = (x2+x3)*0.5f;
4164 immutable float y23 = (y2+y3)*0.5f;
4165 immutable float x34 = (x3+x4)*0.5f;
4166 immutable float y34 = (y3+y4)*0.5f;
4167 immutable float x123 = (x12+x23)*0.5f;
4168 immutable float y123 = (y12+y23)*0.5f;
4169 immutable float x234 = (x23+x34)*0.5f;
4170 immutable float y234 = (y23+y34)*0.5f;
4171 immutable float x1234 = (x123+x234)*0.5f;
4172 immutable float y1234 = (y123+y234)*0.5f;
4174 // try to approximate the full cubic curve by a single straight line
4175 immutable float dx = x4-x1;
4176 immutable float dy = y4-y1;
4178 float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx));
4179 float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx));
4180 //immutable float da1, da2, k;
4182 final switch ((cast(int)(d2 > CollinearEPS)<<1)+cast(int)(d3 > CollinearEPS)) {
4183 case 0:
4184 // all collinear or p1 == p4
4185 float k = dx*dx+dy*dy;
4186 if (k == 0) {
4187 d2 = distSquared(x1, y1, x2, y2);
4188 d3 = distSquared(x4, y4, x3, y3);
4189 } else {
4190 k = 1.0f/k;
4191 float da1 = x2-x1;
4192 float da2 = y2-y1;
4193 d2 = k*(da1*dx+da2*dy);
4194 da1 = x3-x1;
4195 da2 = y3-y1;
4196 d3 = k*(da1*dx+da2*dy);
4197 if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) {
4198 // Simple collinear case, 1---2---3---4
4199 // We can leave just two endpoints
4200 return;
4202 if (d2 <= 0) d2 = distSquared(x2, y2, x1, y1);
4203 else if (d2 >= 1) d2 = distSquared(x2, y2, x4, y4);
4204 else d2 = distSquared(x2, y2, x1+d2*dx, y1+d2*dy);
4206 if (d3 <= 0) d3 = distSquared(x3, y3, x1, y1);
4207 else if (d3 >= 1) d3 = distSquared(x3, y3, x4, y4);
4208 else d3 = distSquared(x3, y3, x1+d3*dx, y1+d3*dy);
4210 if (d2 > d3) {
4211 if (d2 < ctx.tessTol) {
4212 nvg__addPoint(ctx, x2, y2, type);
4213 return;
4215 } if (d3 < ctx.tessTol) {
4216 nvg__addPoint(ctx, x3, y3, type);
4217 return;
4219 break;
4220 case 1:
4221 // p1,p2,p4 are collinear, p3 is significant
4222 if (d3*d3 <= ctx.tessTol*(dx*dx+dy*dy)) {
4223 if (ctx.angleTol < AngleTolEPS) {
4224 nvg__addPoint(ctx, x23, y23, type);
4225 return;
4226 } else {
4227 // angle condition
4228 float da1 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-nvg__atan2f(y3-y2, x3-x2));
4229 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4230 if (da1 < ctx.angleTol) {
4231 nvg__addPoint(ctx, x2, y2, type);
4232 nvg__addPoint(ctx, x3, y3, type);
4233 return;
4235 if (ctx.cuspLimit != 0.0) {
4236 if (da1 > ctx.cuspLimit) {
4237 nvg__addPoint(ctx, x3, y3, type);
4238 return;
4243 break;
4244 case 2:
4245 // p1,p3,p4 are collinear, p2 is significant
4246 if (d2*d2 <= ctx.tessTol*(dx*dx+dy*dy)) {
4247 if (ctx.angleTol < AngleTolEPS) {
4248 nvg__addPoint(ctx, x23, y23, type);
4249 return;
4250 } else {
4251 // angle condition
4252 float da1 = nvg__absf(nvg__atan2f(y3-y2, x3-x2)-nvg__atan2f(y2-y1, x2-x1));
4253 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4254 if (da1 < ctx.angleTol) {
4255 nvg__addPoint(ctx, x2, y2, type);
4256 nvg__addPoint(ctx, x3, y3, type);
4257 return;
4259 if (ctx.cuspLimit != 0.0) {
4260 if (da1 > ctx.cuspLimit) {
4261 nvg__addPoint(ctx, x2, y2, type);
4262 return;
4267 break;
4268 case 3:
4269 // regular case
4270 if ((d2+d3)*(d2+d3) <= ctx.tessTol*(dx*dx+dy*dy)) {
4271 // if the curvature doesn't exceed the distance tolerance value, we tend to finish subdivisions
4272 if (ctx.angleTol < AngleTolEPS) {
4273 nvg__addPoint(ctx, x23, y23, type);
4274 return;
4275 } else {
4276 // angle and cusp condition
4277 immutable float k = nvg__atan2f(y3-y2, x3-x2);
4278 float da1 = nvg__absf(k-nvg__atan2f(y2-y1, x2-x1));
4279 float da2 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-k);
4280 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4281 if (da2 >= NVG_PI) da2 = 2*NVG_PI-da2;
4282 if (da1+da2 < ctx.angleTol) {
4283 // finally we can stop the recursion
4284 nvg__addPoint(ctx, x23, y23, type);
4285 return;
4287 if (ctx.cuspLimit != 0.0) {
4288 if (da1 > ctx.cuspLimit) {
4289 nvg__addPoint(ctx, x2, y2, type);
4290 return;
4292 if (da2 > ctx.cuspLimit) {
4293 nvg__addPoint(ctx, x3, y3, type);
4294 return;
4299 break;
4302 // continue subdivision
4303 nvg__tesselateBezierMcSeem(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
4304 nvg__tesselateBezierMcSeem(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
4308 // Adaptive forward differencing for bezier tesselation.
4309 // See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt.
4310 // "Adaptive forward differencing for rendering curves and surfaces."
4311 // ACM SIGGRAPH Computer Graphics. Vol. 21. No. 4. ACM, 1987.
4312 // original code by Taylor Holliday <taylor@audulus.com>
4313 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 {
4314 enum AFD_ONE = (1<<10);
4316 // power basis
4317 immutable float ax = -x1+3*x2-3*x3+x4;
4318 immutable float ay = -y1+3*y2-3*y3+y4;
4319 immutable float bx = 3*x1-6*x2+3*x3;
4320 immutable float by = 3*y1-6*y2+3*y3;
4321 immutable float cx = -3*x1+3*x2;
4322 immutable float cy = -3*y1+3*y2;
4324 // Transform to forward difference basis (stepsize 1)
4325 float px = x1;
4326 float py = y1;
4327 float dx = ax+bx+cx;
4328 float dy = ay+by+cy;
4329 float ddx = 6*ax+2*bx;
4330 float ddy = 6*ay+2*by;
4331 float dddx = 6*ax;
4332 float dddy = 6*ay;
4334 //printf("dx: %f, dy: %f\n", dx, dy);
4335 //printf("ddx: %f, ddy: %f\n", ddx, ddy);
4336 //printf("dddx: %f, dddy: %f\n", dddx, dddy);
4338 int t = 0;
4339 int dt = AFD_ONE;
4341 immutable float tol = ctx.tessTol*4;
4343 while (t < AFD_ONE) {
4344 // Flatness measure.
4345 float d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4347 // printf("d: %f, th: %f\n", d, th);
4349 // Go to higher resolution if we're moving a lot or overshooting the end.
4350 while ((d > tol && dt > 1) || (t+dt > AFD_ONE)) {
4351 // printf("up\n");
4353 // Apply L to the curve. Increase curve resolution.
4354 dx = 0.5f*dx-(1.0f/8.0f)*ddx+(1.0f/16.0f)*dddx;
4355 dy = 0.5f*dy-(1.0f/8.0f)*ddy+(1.0f/16.0f)*dddy;
4356 ddx = (1.0f/4.0f)*ddx-(1.0f/8.0f)*dddx;
4357 ddy = (1.0f/4.0f)*ddy-(1.0f/8.0f)*dddy;
4358 dddx = (1.0f/8.0f)*dddx;
4359 dddy = (1.0f/8.0f)*dddy;
4361 // Half the stepsize.
4362 dt >>= 1;
4364 // Recompute d
4365 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4368 // Go to lower resolution if we're really flat
4369 // and we aren't going to overshoot the end.
4370 // XXX: tol/32 is just a guess for when we are too flat.
4371 while ((d > 0 && d < tol/32.0f && dt < AFD_ONE) && (t+2*dt <= AFD_ONE)) {
4372 // printf("down\n");
4374 // Apply L^(-1) to the curve. Decrease curve resolution.
4375 dx = 2*dx+ddx;
4376 dy = 2*dy+ddy;
4377 ddx = 4*ddx+4*dddx;
4378 ddy = 4*ddy+4*dddy;
4379 dddx = 8*dddx;
4380 dddy = 8*dddy;
4382 // Double the stepsize.
4383 dt <<= 1;
4385 // Recompute d
4386 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4389 // Forward differencing.
4390 px += dx;
4391 py += dy;
4392 dx += ddx;
4393 dy += ddy;
4394 ddx += dddx;
4395 ddy += dddy;
4397 // Output a point.
4398 nvg__addPoint(ctx, px, py, (t > 0 ? type : 0));
4400 // Advance along the curve.
4401 t += dt;
4403 // Ensure we don't overshoot.
4404 assert(t <= AFD_ONE);
4409 void nvg__dashLastPath (NVGContext ctx) nothrow @trusted @nogc {
4410 import core.stdc.stdlib : realloc;
4411 import core.stdc.string : memcpy;
4413 NVGpathCache* cache = ctx.cache;
4414 if (cache.npaths == 0) return;
4416 NVGpath* path = nvg__lastPath(ctx);
4417 if (path is null) return;
4419 NVGstate* state = nvg__getState(ctx);
4420 if (!state.dasherActive) return;
4422 static NVGpoint* pts = null;
4423 static uint ptsCount = 0;
4424 static uint ptsSize = 0;
4426 if (path.count < 2) return; // just in case
4428 // copy path points (reserve one point for closed pathes)
4429 if (ptsSize < path.count+1) {
4430 ptsSize = cast(uint)(path.count+1);
4431 pts = cast(NVGpoint*)realloc(pts, ptsSize*NVGpoint.sizeof);
4432 if (pts is null) assert(0, "NanoVega: out of memory");
4434 ptsCount = cast(uint)path.count;
4435 memcpy(pts, &cache.points[path.first], ptsCount*NVGpoint.sizeof);
4436 // add closing point for closed pathes
4437 if (path.closed && !nvg__ptEquals(pts[0].x, pts[0].y, pts[ptsCount-1].x, pts[ptsCount-1].y, ctx.distTol)) {
4438 pts[ptsCount++] = pts[0];
4441 // remove last path (with its points)
4442 --cache.npaths;
4443 cache.npoints -= path.count;
4445 // add stroked pathes
4446 const(float)* dashes = state.dashes.ptr;
4447 immutable uint dashCount = state.dashCount;
4448 float currDashStart = 0;
4449 uint currDashIdx = 0;
4450 immutable bool firstIsGap = state.firstDashIsGap;
4452 // calculate lengthes
4454 NVGpoint* v1 = &pts[0];
4455 NVGpoint* v2 = &pts[1];
4456 foreach (immutable _; 0..ptsCount) {
4457 float dx = v2.x-v1.x;
4458 float dy = v2.y-v1.y;
4459 v1.len = nvg__normalize(&dx, &dy);
4460 v1 = v2++;
4464 void calcDashStart (float ds) {
4465 if (ds < 0) {
4466 ds = ds%state.totalDashLen;
4467 while (ds < 0) ds += state.totalDashLen;
4469 currDashIdx = 0;
4470 currDashStart = 0;
4471 while (ds > 0) {
4472 if (ds > dashes[currDashIdx]) {
4473 ds -= dashes[currDashIdx];
4474 ++currDashIdx;
4475 currDashStart = 0;
4476 if (currDashIdx >= dashCount) currDashIdx = 0;
4477 } else {
4478 currDashStart = ds;
4479 ds = 0;
4484 calcDashStart(state.dashStart);
4486 uint srcPointIdx = 1;
4487 const(NVGpoint)* v1 = &pts[0];
4488 const(NVGpoint)* v2 = &pts[1];
4489 float currRest = v1.len;
4490 nvg__addPath(ctx);
4491 nvg__addPoint(ctx, v1.x, v1.y, PointFlag.Corner);
4493 void fixLastPoint () {
4494 auto lpt = nvg__lastPath(ctx);
4495 if (lpt !is null && lpt.count > 0) {
4496 // fix last point
4497 if (auto lps = nvg__lastPoint(ctx)) lps.flags = PointFlag.Corner;
4498 // fix first point
4499 NVGpathCache* cache = ctx.cache;
4500 cache.points[lpt.first].flags = PointFlag.Corner;
4504 for (;;) {
4505 immutable float dlen = dashes[currDashIdx];
4506 if (dlen == 0) {
4507 ++currDashIdx;
4508 if (currDashIdx >= dashCount) currDashIdx = 0;
4509 continue;
4511 immutable float dashRest = dlen-currDashStart;
4512 if ((currDashIdx&1) != firstIsGap) {
4513 // this is "moveto" command, so create new path
4514 fixLastPoint();
4515 nvg__addPath(ctx);
4517 if (currRest > dashRest) {
4518 currRest -= dashRest;
4519 ++currDashIdx;
4520 if (currDashIdx >= dashCount) currDashIdx = 0;
4521 currDashStart = 0;
4522 nvg__addPoint(ctx,
4523 v2.x-(v2.x-v1.x)*currRest/v1.len,
4524 v2.y-(v2.y-v1.y)*currRest/v1.len,
4525 PointFlag.Corner
4527 } else {
4528 currDashStart += currRest;
4529 nvg__addPoint(ctx, v2.x, v2.y, v1.flags); //k8:fix flags here?
4530 ++srcPointIdx;
4531 v1 = v2;
4532 currRest = v1.len;
4533 if (srcPointIdx >= ptsCount) break;
4534 v2 = &pts[srcPointIdx];
4537 fixLastPoint();
4541 version(nanovg_bench_flatten) import iv.timer : Timer;
4543 void nvg__flattenPaths(bool asStroke) (NVGContext ctx) nothrow @trusted @nogc {
4544 version(nanovg_bench_flatten) {
4545 Timer timer;
4546 char[128] tmbuf;
4547 int bzcount;
4549 NVGpathCache* cache = ctx.cache;
4550 NVGstate* state = nvg__getState(ctx);
4552 // check if we already did flattening
4553 static if (asStroke) {
4554 if (state.dashCount >= 2) {
4555 if (cache.npaths > 0 && state.lastFlattenDashCount == state.dashCount) return; // already flattened
4556 state.dasherActive = true;
4557 state.lastFlattenDashCount = state.dashCount;
4558 } else {
4559 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened
4560 state.dasherActive = false;
4561 state.lastFlattenDashCount = 0;
4563 } else {
4564 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened
4565 state.lastFlattenDashCount = 0; // so next stroke flattening will redo it
4566 state.dasherActive = false;
4569 // clear path cache
4570 cache.npaths = 0;
4571 cache.npoints = 0;
4573 // flatten
4574 version(nanovg_bench_flatten) timer.restart();
4575 int i = 0;
4576 while (i < ctx.ncommands) {
4577 final switch (cast(Command)ctx.commands[i]) {
4578 case Command.MoveTo:
4579 //assert(i+3 <= ctx.ncommands);
4580 static if (asStroke) {
4581 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx);
4583 nvg__addPath(ctx);
4584 const p = &ctx.commands[i+1];
4585 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner);
4586 i += 3;
4587 break;
4588 case Command.LineTo:
4589 //assert(i+3 <= ctx.ncommands);
4590 const p = &ctx.commands[i+1];
4591 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner);
4592 i += 3;
4593 break;
4594 case Command.BezierTo:
4595 //assert(i+7 <= ctx.ncommands);
4596 const last = nvg__lastPoint(ctx);
4597 if (last !is null) {
4598 const cp1 = &ctx.commands[i+1];
4599 const cp2 = &ctx.commands[i+3];
4600 const p = &ctx.commands[i+5];
4601 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) {
4602 nvg__tesselateBezier(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
4603 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) {
4604 nvg__tesselateBezierMcSeem(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
4605 } else {
4606 nvg__tesselateBezierAFD(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], PointFlag.Corner);
4608 version(nanovg_bench_flatten) ++bzcount;
4610 i += 7;
4611 break;
4612 case Command.Close:
4613 //assert(i+1 <= ctx.ncommands);
4614 nvg__closePath(ctx);
4615 i += 1;
4616 break;
4617 case Command.Winding:
4618 //assert(i+2 <= ctx.ncommands);
4619 nvg__pathWinding(ctx, cast(NVGWinding)ctx.commands[i+1]);
4620 i += 2;
4621 break;
4624 static if (asStroke) {
4625 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx);
4627 version(nanovg_bench_flatten) {{
4628 timer.stop();
4629 auto xb = timer.toBuffer(tmbuf[]);
4630 import core.stdc.stdio : printf;
4631 printf("flattening time: [%.*s] (%d beziers)\n", cast(uint)xb.length, xb.ptr, bzcount);
4634 cache.bounds.ptr[0] = cache.bounds.ptr[1] = float.max;
4635 cache.bounds.ptr[2] = cache.bounds.ptr[3] = -float.max;
4637 // calculate the direction and length of line segments
4638 version(nanovg_bench_flatten) timer.restart();
4639 foreach (int j; 0..cache.npaths) {
4640 NVGpath* path = &cache.paths[j];
4641 NVGpoint* pts = &cache.points[path.first];
4643 // if the first and last points are the same, remove the last, mark as closed path
4644 NVGpoint* p0 = &pts[path.count-1];
4645 NVGpoint* p1 = &pts[0];
4646 if (nvg__ptEquals(p0.x, p0.y, p1.x, p1.y, ctx.distTol)) {
4647 --path.count;
4648 p0 = &pts[path.count-1];
4649 path.closed = true;
4652 // enforce winding
4653 if (path.count > 2) {
4654 immutable float area = nvg__polyArea(pts, path.count);
4655 if (path.mWinding == NVGWinding.CCW && area < 0.0f) nvg__polyReverse(pts, path.count);
4656 if (path.mWinding == NVGWinding.CW && area > 0.0f) nvg__polyReverse(pts, path.count);
4659 foreach (immutable _; 0..path.count) {
4660 // calculate segment direction and length
4661 p0.dx = p1.x-p0.x;
4662 p0.dy = p1.y-p0.y;
4663 p0.len = nvg__normalize(&p0.dx, &p0.dy);
4664 // update bounds
4665 cache.bounds.ptr[0] = nvg__min(cache.bounds.ptr[0], p0.x);
4666 cache.bounds.ptr[1] = nvg__min(cache.bounds.ptr[1], p0.y);
4667 cache.bounds.ptr[2] = nvg__max(cache.bounds.ptr[2], p0.x);
4668 cache.bounds.ptr[3] = nvg__max(cache.bounds.ptr[3], p0.y);
4669 // advance
4670 p0 = p1++;
4673 version(nanovg_bench_flatten) {{
4674 timer.stop();
4675 auto xb = timer.toBuffer(tmbuf[]);
4676 import core.stdc.stdio : printf;
4677 printf("segment calculation time: [%.*s]\n", cast(uint)xb.length, xb.ptr);
4681 int nvg__curveDivs (float r, float arc, float tol) nothrow @trusted @nogc {
4682 immutable float da = nvg__acosf(r/(r+tol))*2.0f;
4683 return nvg__max(2, cast(int)nvg__ceilf(arc/da));
4686 void nvg__chooseBevel (int bevel, NVGpoint* p0, NVGpoint* p1, float w, float* x0, float* y0, float* x1, float* y1) nothrow @trusted @nogc {
4687 if (bevel) {
4688 *x0 = p1.x+p0.dy*w;
4689 *y0 = p1.y-p0.dx*w;
4690 *x1 = p1.x+p1.dy*w;
4691 *y1 = p1.y-p1.dx*w;
4692 } else {
4693 *x0 = p1.x+p1.dmx*w;
4694 *y0 = p1.y+p1.dmy*w;
4695 *x1 = p1.x+p1.dmx*w;
4696 *y1 = p1.y+p1.dmy*w;
4700 NVGVertex* nvg__roundJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, int ncap, float fringe) nothrow @trusted @nogc {
4701 float dlx0 = p0.dy;
4702 float dly0 = -p0.dx;
4703 float dlx1 = p1.dy;
4704 float dly1 = -p1.dx;
4705 //NVG_NOTUSED(fringe);
4707 if (p1.flags&PointFlag.Left) {
4708 float lx0 = void, ly0 = void, lx1 = void, ly1 = void;
4709 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1);
4710 immutable float a0 = nvg__atan2f(-dly0, -dlx0);
4711 float a1 = nvg__atan2f(-dly1, -dlx1);
4712 if (a1 > a0) a1 -= NVG_PI*2;
4714 nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4715 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4717 int n = nvg__clamp(cast(int)nvg__ceilf(((a0-a1)/NVG_PI)*ncap), 2, ncap);
4718 for (int i = 0; i < n; ++i) {
4719 float u = i/cast(float)(n-1);
4720 float a = a0+u*(a1-a0);
4721 float rx = p1.x+nvg__cosf(a)*rw;
4722 float ry = p1.y+nvg__sinf(a)*rw;
4723 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4724 nvg__vset(dst, rx, ry, ru, 1); ++dst;
4727 nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4728 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4730 } else {
4731 float rx0 = void, ry0 = void, rx1 = void, ry1 = void;
4732 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1);
4733 immutable float a0 = nvg__atan2f(dly0, dlx0);
4734 float a1 = nvg__atan2f(dly1, dlx1);
4735 if (a1 < a0) a1 += NVG_PI*2;
4737 nvg__vset(dst, p1.x+dlx0*rw, p1.y+dly0*rw, lu, 1); ++dst;
4738 nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4740 int n = nvg__clamp(cast(int)nvg__ceilf(((a1-a0)/NVG_PI)*ncap), 2, ncap);
4741 for (int i = 0; i < n; i++) {
4742 float u = i/cast(float)(n-1);
4743 float a = a0+u*(a1-a0);
4744 float lx = p1.x+nvg__cosf(a)*lw;
4745 float ly = p1.y+nvg__sinf(a)*lw;
4746 nvg__vset(dst, lx, ly, lu, 1); ++dst;
4747 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4750 nvg__vset(dst, p1.x+dlx1*rw, p1.y+dly1*rw, lu, 1); ++dst;
4751 nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
4754 return dst;
4757 NVGVertex* nvg__bevelJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, float fringe) nothrow @trusted @nogc {
4758 float rx0, ry0, rx1, ry1;
4759 float lx0, ly0, lx1, ly1;
4760 float dlx0 = p0.dy;
4761 float dly0 = -p0.dx;
4762 float dlx1 = p1.dy;
4763 float dly1 = -p1.dx;
4764 //NVG_NOTUSED(fringe);
4766 if (p1.flags&PointFlag.Left) {
4767 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1);
4769 nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4770 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4772 if (p1.flags&PointFlag.Bevel) {
4773 nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4774 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4776 nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4777 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4778 } else {
4779 rx0 = p1.x-p1.dmx*rw;
4780 ry0 = p1.y-p1.dmy*rw;
4782 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4783 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4785 nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4786 nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4788 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4789 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4792 nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4793 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4795 } else {
4796 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1);
4798 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
4799 nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4801 if (p1.flags&PointFlag.Bevel) {
4802 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
4803 nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4805 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
4806 nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
4807 } else {
4808 lx0 = p1.x+p1.dmx*lw;
4809 ly0 = p1.y+p1.dmy*lw;
4811 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
4812 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4814 nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4815 nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4817 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
4818 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4821 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
4822 nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
4825 return dst;
4828 NVGVertex* nvg__buttCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc {
4829 immutable float px = p.x-dx*d;
4830 immutable float py = p.y-dy*d;
4831 immutable float dlx = dy;
4832 immutable float dly = -dx;
4833 nvg__vset(dst, px+dlx*w-dx*aa, py+dly*w-dy*aa, 0, 0); ++dst;
4834 nvg__vset(dst, px-dlx*w-dx*aa, py-dly*w-dy*aa, 1, 0); ++dst;
4835 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
4836 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
4837 return dst;
4840 NVGVertex* nvg__buttCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc {
4841 immutable float px = p.x+dx*d;
4842 immutable float py = p.y+dy*d;
4843 immutable float dlx = dy;
4844 immutable float dly = -dx;
4845 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
4846 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
4847 nvg__vset(dst, px+dlx*w+dx*aa, py+dly*w+dy*aa, 0, 0); ++dst;
4848 nvg__vset(dst, px-dlx*w+dx*aa, py-dly*w+dy*aa, 1, 0); ++dst;
4849 return dst;
4852 NVGVertex* nvg__roundCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc {
4853 immutable float px = p.x;
4854 immutable float py = p.y;
4855 immutable float dlx = dy;
4856 immutable float dly = -dx;
4857 //NVG_NOTUSED(aa);
4858 immutable float ncpf = cast(float)(ncap-1);
4859 foreach (int i; 0..ncap) {
4860 float a = i/*/cast(float)(ncap-1)*//ncpf*NVG_PI;
4861 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w;
4862 nvg__vset(dst, px-dlx*ax-dx*ay, py-dly*ax-dy*ay, 0, 1); ++dst;
4863 nvg__vset(dst, px, py, 0.5f, 1); ++dst;
4865 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
4866 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
4867 return dst;
4870 NVGVertex* nvg__roundCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc {
4871 immutable float px = p.x;
4872 immutable float py = p.y;
4873 immutable float dlx = dy;
4874 immutable float dly = -dx;
4875 //NVG_NOTUSED(aa);
4876 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
4877 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
4878 immutable float ncpf = cast(float)(ncap-1);
4879 foreach (int i; 0..ncap) {
4880 float a = i/*cast(float)(ncap-1)*//ncpf*NVG_PI;
4881 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w;
4882 nvg__vset(dst, px, py, 0.5f, 1); ++dst;
4883 nvg__vset(dst, px-dlx*ax+dx*ay, py-dly*ax+dy*ay, 0, 1); ++dst;
4885 return dst;
4888 void nvg__calculateJoins (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc {
4889 NVGpathCache* cache = ctx.cache;
4890 float iw = 0.0f;
4892 if (w > 0.0f) iw = 1.0f/w;
4894 // Calculate which joins needs extra vertices to append, and gather vertex count.
4895 foreach (int i; 0..cache.npaths) {
4896 NVGpath* path = &cache.paths[i];
4897 NVGpoint* pts = &cache.points[path.first];
4898 NVGpoint* p0 = &pts[path.count-1];
4899 NVGpoint* p1 = &pts[0];
4900 int nleft = 0;
4902 path.nbevel = 0;
4904 foreach (int j; 0..path.count) {
4905 //float dlx0, dly0, dlx1, dly1, dmr2, cross, limit;
4906 immutable float dlx0 = p0.dy;
4907 immutable float dly0 = -p0.dx;
4908 immutable float dlx1 = p1.dy;
4909 immutable float dly1 = -p1.dx;
4910 // Calculate extrusions
4911 p1.dmx = (dlx0+dlx1)*0.5f;
4912 p1.dmy = (dly0+dly1)*0.5f;
4913 immutable float dmr2 = p1.dmx*p1.dmx+p1.dmy*p1.dmy;
4914 if (dmr2 > 0.000001f) {
4915 float scale = 1.0f/dmr2;
4916 if (scale > 600.0f) scale = 600.0f;
4917 p1.dmx *= scale;
4918 p1.dmy *= scale;
4921 // Clear flags, but keep the corner.
4922 p1.flags = (p1.flags&PointFlag.Corner) ? PointFlag.Corner : 0;
4924 // Keep track of left turns.
4925 immutable float cross = p1.dx*p0.dy-p0.dx*p1.dy;
4926 if (cross > 0.0f) {
4927 nleft++;
4928 p1.flags |= PointFlag.Left;
4931 // Calculate if we should use bevel or miter for inner join.
4932 immutable float limit = nvg__max(1.01f, nvg__min(p0.len, p1.len)*iw);
4933 if ((dmr2*limit*limit) < 1.0f) p1.flags |= PointFlag.InnerBevelPR;
4935 // Check to see if the corner needs to be beveled.
4936 if (p1.flags&PointFlag.Corner) {
4937 if ((dmr2*miterLimit*miterLimit) < 1.0f || lineJoin == NVGLineCap.Bevel || lineJoin == NVGLineCap.Round) {
4938 p1.flags |= PointFlag.Bevel;
4942 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) path.nbevel++;
4944 p0 = p1++;
4947 path.convex = (nleft == path.count);
4951 void nvg__expandStroke (NVGContext ctx, float w, int lineCap, int lineJoin, float miterLimit) nothrow @trusted @nogc {
4952 NVGpathCache* cache = ctx.cache;
4953 immutable float aa = ctx.fringeWidth;
4954 int ncap = nvg__curveDivs(w, NVG_PI, ctx.tessTol); // Calculate divisions per half circle.
4956 nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
4958 // Calculate max vertex usage.
4959 int cverts = 0;
4960 foreach (int i; 0..cache.npaths) {
4961 NVGpath* path = &cache.paths[i];
4962 immutable bool loop = path.closed;
4963 if (lineJoin == NVGLineCap.Round) {
4964 cverts += (path.count+path.nbevel*(ncap+2)+1)*2; // plus one for loop
4965 } else {
4966 cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop
4968 if (!loop) {
4969 // space for caps
4970 if (lineCap == NVGLineCap.Round) {
4971 cverts += (ncap*2+2)*2;
4972 } else {
4973 cverts += (3+3)*2;
4978 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts);
4979 if (verts is null) return;
4981 foreach (int i; 0..cache.npaths) {
4982 NVGpath* path = &cache.paths[i];
4983 NVGpoint* pts = &cache.points[path.first];
4984 NVGpoint* p0;
4985 NVGpoint* p1;
4986 int s, e;
4988 path.fill = null;
4989 path.nfill = 0;
4991 // Calculate fringe or stroke
4992 immutable bool loop = path.closed;
4993 NVGVertex* dst = verts;
4994 path.stroke = dst;
4996 if (loop) {
4997 // Looping
4998 p0 = &pts[path.count-1];
4999 p1 = &pts[0];
5000 s = 0;
5001 e = path.count;
5002 } else {
5003 // Add cap
5004 p0 = &pts[0];
5005 p1 = &pts[1];
5006 s = 1;
5007 e = path.count-1;
5010 if (!loop) {
5011 // Add cap
5012 float dx = p1.x-p0.x;
5013 float dy = p1.y-p0.y;
5014 nvg__normalize(&dx, &dy);
5015 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa);
5016 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa);
5017 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa);
5020 foreach (int j; s..e) {
5021 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) {
5022 if (lineJoin == NVGLineCap.Round) {
5023 dst = nvg__roundJoin(dst, p0, p1, w, w, 0, 1, ncap, aa);
5024 } else {
5025 dst = nvg__bevelJoin(dst, p0, p1, w, w, 0, 1, aa);
5027 } else {
5028 nvg__vset(dst, p1.x+(p1.dmx*w), p1.y+(p1.dmy*w), 0, 1); ++dst;
5029 nvg__vset(dst, p1.x-(p1.dmx*w), p1.y-(p1.dmy*w), 1, 1); ++dst;
5031 p0 = p1++;
5034 if (loop) {
5035 // Loop it
5036 nvg__vset(dst, verts[0].x, verts[0].y, 0, 1); ++dst;
5037 nvg__vset(dst, verts[1].x, verts[1].y, 1, 1); ++dst;
5038 } else {
5039 // Add cap
5040 float dx = p1.x-p0.x;
5041 float dy = p1.y-p0.y;
5042 nvg__normalize(&dx, &dy);
5043 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa);
5044 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa);
5045 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa);
5048 path.nstroke = cast(int)(dst-verts);
5050 verts = dst;
5054 void nvg__expandFill (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5055 NVGpathCache* cache = ctx.cache;
5056 immutable float aa = ctx.fringeWidth;
5057 bool fringe = (w > 0.0f);
5059 nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
5061 // Calculate max vertex usage.
5062 int cverts = 0;
5063 foreach (int i; 0..cache.npaths) {
5064 NVGpath* path = &cache.paths[i];
5065 cverts += path.count+path.nbevel+1;
5066 if (fringe) cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop
5069 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts);
5070 if (verts is null) return;
5072 bool convex = (cache.npaths == 1 && cache.paths[0].convex);
5074 foreach (int i; 0..cache.npaths) {
5075 NVGpath* path = &cache.paths[i];
5076 NVGpoint* pts = &cache.points[path.first];
5078 // Calculate shape vertices.
5079 immutable float woff = 0.5f*aa;
5080 NVGVertex* dst = verts;
5081 path.fill = dst;
5083 if (fringe) {
5084 // Looping
5085 NVGpoint* p0 = &pts[path.count-1];
5086 NVGpoint* p1 = &pts[0];
5087 foreach (int j; 0..path.count) {
5088 if (p1.flags&PointFlag.Bevel) {
5089 immutable float dlx0 = p0.dy;
5090 immutable float dly0 = -p0.dx;
5091 immutable float dlx1 = p1.dy;
5092 immutable float dly1 = -p1.dx;
5093 if (p1.flags&PointFlag.Left) {
5094 immutable float lx = p1.x+p1.dmx*woff;
5095 immutable float ly = p1.y+p1.dmy*woff;
5096 nvg__vset(dst, lx, ly, 0.5f, 1); ++dst;
5097 } else {
5098 immutable float lx0 = p1.x+dlx0*woff;
5099 immutable float ly0 = p1.y+dly0*woff;
5100 immutable float lx1 = p1.x+dlx1*woff;
5101 immutable float ly1 = p1.y+dly1*woff;
5102 nvg__vset(dst, lx0, ly0, 0.5f, 1); ++dst;
5103 nvg__vset(dst, lx1, ly1, 0.5f, 1); ++dst;
5105 } else {
5106 nvg__vset(dst, p1.x+(p1.dmx*woff), p1.y+(p1.dmy*woff), 0.5f, 1); ++dst;
5108 p0 = p1++;
5110 } else {
5111 foreach (int j; 0..path.count) {
5112 nvg__vset(dst, pts[j].x, pts[j].y, 0.5f, 1);
5113 ++dst;
5117 path.nfill = cast(int)(dst-verts);
5118 verts = dst;
5120 // Calculate fringe
5121 if (fringe) {
5122 float lw = w+woff;
5123 immutable float rw = w-woff;
5124 float lu = 0;
5125 immutable float ru = 1;
5126 dst = verts;
5127 path.stroke = dst;
5129 // Create only half a fringe for convex shapes so that
5130 // the shape can be rendered without stenciling.
5131 if (convex) {
5132 lw = woff; // This should generate the same vertex as fill inset above.
5133 lu = 0.5f; // Set outline fade at middle.
5136 // Looping
5137 NVGpoint* p0 = &pts[path.count-1];
5138 NVGpoint* p1 = &pts[0];
5140 foreach (int j; 0..path.count) {
5141 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) {
5142 dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx.fringeWidth);
5143 } else {
5144 nvg__vset(dst, p1.x+(p1.dmx*lw), p1.y+(p1.dmy*lw), lu, 1); ++dst;
5145 nvg__vset(dst, p1.x-(p1.dmx*rw), p1.y-(p1.dmy*rw), ru, 1); ++dst;
5147 p0 = p1++;
5150 // Loop it
5151 nvg__vset(dst, verts[0].x, verts[0].y, lu, 1); ++dst;
5152 nvg__vset(dst, verts[1].x, verts[1].y, ru, 1); ++dst;
5154 path.nstroke = cast(int)(dst-verts);
5155 verts = dst;
5156 } else {
5157 path.stroke = null;
5158 path.nstroke = 0;
5164 // ////////////////////////////////////////////////////////////////////////// //
5165 // Paths
5167 /// Clears the current path and sub-paths.
5168 /// Group: paths
5169 @scriptable
5170 public void beginPath (NVGContext ctx) nothrow @trusted @nogc {
5171 ctx.ncommands = 0;
5172 ctx.pathPickRegistered &= NVGPickKind.All; // reset "registered" flags
5173 nvg__clearPathCache(ctx);
5176 public alias newPath = beginPath; /// Ditto.
5178 /// Starts new sub-path with specified point as first point.
5179 /// Group: paths
5180 @scriptable
5181 public void moveTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
5182 nvg__appendCommands(ctx, Command.MoveTo, x, y);
5185 /// Starts new sub-path with specified point as first point.
5186 /// Arguments: [x, y]*
5187 /// Group: paths
5188 public void moveTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5189 enum ArgC = 2;
5190 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [moveTo] call");
5191 if (args.length < ArgC) return;
5192 nvg__appendCommands(ctx, Command.MoveTo, args[$-2..$]);
5195 /// Adds line segment from the last point in the path to the specified point.
5196 /// Group: paths
5197 @scriptable
5198 public void lineTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
5199 nvg__appendCommands(ctx, Command.LineTo, x, y);
5202 /// Adds line segment from the last point in the path to the specified point.
5203 /// Arguments: [x, y]*
5204 /// Group: paths
5205 public void lineTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5206 enum ArgC = 2;
5207 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [lineTo] call");
5208 if (args.length < ArgC) return;
5209 foreach (immutable idx; 0..args.length/ArgC) {
5210 nvg__appendCommands(ctx, Command.LineTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]);
5214 /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
5215 /// Group: paths
5216 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 {
5217 nvg__appendCommands(ctx, Command.BezierTo, c1x, c1y, c2x, c2y, x, y);
5220 /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
5221 /// Arguments: [c1x, c1y, c2x, c2y, x, y]*
5222 /// Group: paths
5223 public void bezierTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5224 enum ArgC = 6;
5225 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [bezierTo] call");
5226 if (args.length < ArgC) return;
5227 foreach (immutable idx; 0..args.length/ArgC) {
5228 nvg__appendCommands(ctx, Command.BezierTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]);
5232 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
5233 /// Group: paths
5234 public void quadTo (NVGContext ctx, in float cx, in float cy, in float x, in float y) nothrow @trusted @nogc {
5235 immutable float x0 = ctx.commandx;
5236 immutable float y0 = ctx.commandy;
5237 nvg__appendCommands(ctx,
5238 Command.BezierTo,
5239 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
5240 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
5241 x, y,
5245 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
5246 /// Arguments: [cx, cy, x, y]*
5247 /// Group: paths
5248 public void quadTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5249 enum ArgC = 4;
5250 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [quadTo] call");
5251 if (args.length < ArgC) return;
5252 const(float)* aptr = args.ptr;
5253 foreach (immutable idx; 0..args.length/ArgC) {
5254 immutable float x0 = ctx.commandx;
5255 immutable float y0 = ctx.commandy;
5256 immutable float cx = *aptr++;
5257 immutable float cy = *aptr++;
5258 immutable float x = *aptr++;
5259 immutable float y = *aptr++;
5260 nvg__appendCommands(ctx,
5261 Command.BezierTo,
5262 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
5263 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
5264 x, y,
5269 /// Adds an arc segment at the corner defined by the last path point, and two specified points.
5270 /// Group: paths
5271 public void arcTo (NVGContext ctx, in float x1, in float y1, in float x2, in float y2, in float radius) nothrow @trusted @nogc {
5272 if (ctx.ncommands == 0) return;
5274 immutable float x0 = ctx.commandx;
5275 immutable float y0 = ctx.commandy;
5277 // handle degenerate cases
5278 if (nvg__ptEquals(x0, y0, x1, y1, ctx.distTol) ||
5279 nvg__ptEquals(x1, y1, x2, y2, ctx.distTol) ||
5280 nvg__distPtSeg(x1, y1, x0, y0, x2, y2) < ctx.distTol*ctx.distTol ||
5281 radius < ctx.distTol)
5283 ctx.lineTo(x1, y1);
5284 return;
5287 // calculate tangential circle to lines (x0, y0)-(x1, y1) and (x1, y1)-(x2, y2)
5288 float dx0 = x0-x1;
5289 float dy0 = y0-y1;
5290 float dx1 = x2-x1;
5291 float dy1 = y2-y1;
5292 nvg__normalize(&dx0, &dy0);
5293 nvg__normalize(&dx1, &dy1);
5294 immutable float a = nvg__acosf(dx0*dx1+dy0*dy1);
5295 immutable float d = radius/nvg__tanf(a/2.0f);
5297 //printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d);
5299 if (d > 10000.0f) {
5300 ctx.lineTo(x1, y1);
5301 return;
5304 float cx = void, cy = void, a0 = void, a1 = void;
5305 NVGWinding dir;
5306 if (nvg__cross(dx0, dy0, dx1, dy1) > 0.0f) {
5307 cx = x1+dx0*d+dy0*radius;
5308 cy = y1+dy0*d+-dx0*radius;
5309 a0 = nvg__atan2f(dx0, -dy0);
5310 a1 = nvg__atan2f(-dx1, dy1);
5311 dir = NVGWinding.CW;
5312 //printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
5313 } else {
5314 cx = x1+dx0*d+-dy0*radius;
5315 cy = y1+dy0*d+dx0*radius;
5316 a0 = nvg__atan2f(-dx0, dy0);
5317 a1 = nvg__atan2f(dx1, -dy1);
5318 dir = NVGWinding.CCW;
5319 //printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
5322 ctx.arc(dir, cx, cy, radius, a0, a1); // first is line
5326 /// Adds an arc segment at the corner defined by the last path point, and two specified points.
5327 /// Arguments: [x1, y1, x2, y2, radius]*
5328 /// Group: paths
5329 public void arcTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5330 enum ArgC = 5;
5331 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arcTo] call");
5332 if (args.length < ArgC) return;
5333 if (ctx.ncommands == 0) return;
5334 const(float)* aptr = args.ptr;
5335 foreach (immutable idx; 0..args.length/ArgC) {
5336 immutable float x0 = ctx.commandx;
5337 immutable float y0 = ctx.commandy;
5338 immutable float x1 = *aptr++;
5339 immutable float y1 = *aptr++;
5340 immutable float x2 = *aptr++;
5341 immutable float y2 = *aptr++;
5342 immutable float radius = *aptr++;
5343 ctx.arcTo(x1, y1, x2, y2, radius);
5347 /// Closes current sub-path with a line segment.
5348 /// Group: paths
5349 @scriptable
5350 public void closePath (NVGContext ctx) nothrow @trusted @nogc {
5351 nvg__appendCommands(ctx, Command.Close);
5354 /// Sets the current sub-path winding, see NVGWinding and NVGSolidity.
5355 /// Group: paths
5356 public void pathWinding (NVGContext ctx, NVGWinding dir) nothrow @trusted @nogc {
5357 nvg__appendCommands(ctx, Command.Winding, cast(float)dir);
5360 /// Ditto.
5361 public void pathWinding (NVGContext ctx, NVGSolidity dir) nothrow @trusted @nogc {
5362 nvg__appendCommands(ctx, Command.Winding, cast(float)dir);
5365 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r,
5366 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW).
5367 * Angles are specified in radians.
5369 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo
5371 * Group: paths
5373 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 {
5374 static assert(mode == "original" || mode == "move" || mode == "line");
5376 float[3+5*7+100] vals = void;
5377 //int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo);
5378 static if (mode == "original") {
5379 immutable int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo);
5380 } else static if (mode == "move") {
5381 enum move = Command.MoveTo;
5382 } else static if (mode == "line") {
5383 enum move = Command.LineTo;
5384 } else {
5385 static assert(0, "wtf?!");
5388 // Clamp angles
5389 float da = a1-a0;
5390 if (dir == NVGWinding.CW) {
5391 if (nvg__absf(da) >= NVG_PI*2) {
5392 da = NVG_PI*2;
5393 } else {
5394 while (da < 0.0f) da += NVG_PI*2;
5396 } else {
5397 if (nvg__absf(da) >= NVG_PI*2) {
5398 da = -NVG_PI*2;
5399 } else {
5400 while (da > 0.0f) da -= NVG_PI*2;
5404 // Split arc into max 90 degree segments.
5405 immutable int ndivs = nvg__max(1, nvg__min(cast(int)(nvg__absf(da)/(NVG_PI*0.5f)+0.5f), 5));
5406 immutable float hda = (da/cast(float)ndivs)/2.0f;
5407 float kappa = nvg__absf(4.0f/3.0f*(1.0f-nvg__cosf(hda))/nvg__sinf(hda));
5409 if (dir == NVGWinding.CCW) kappa = -kappa;
5411 int nvals = 0;
5412 float px = 0, py = 0, ptanx = 0, ptany = 0;
5413 foreach (int i; 0..ndivs+1) {
5414 immutable float a = a0+da*(i/cast(float)ndivs);
5415 immutable float dx = nvg__cosf(a);
5416 immutable float dy = nvg__sinf(a);
5417 immutable float x = cx+dx*r;
5418 immutable float y = cy+dy*r;
5419 immutable float tanx = -dy*r*kappa;
5420 immutable float tany = dx*r*kappa;
5422 if (i == 0) {
5423 if (vals.length-nvals < 3) {
5424 // flush
5425 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5426 nvals = 0;
5428 vals.ptr[nvals++] = cast(float)move;
5429 vals.ptr[nvals++] = x;
5430 vals.ptr[nvals++] = y;
5431 } else {
5432 if (vals.length-nvals < 7) {
5433 // flush
5434 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5435 nvals = 0;
5437 vals.ptr[nvals++] = Command.BezierTo;
5438 vals.ptr[nvals++] = px+ptanx;
5439 vals.ptr[nvals++] = py+ptany;
5440 vals.ptr[nvals++] = x-tanx;
5441 vals.ptr[nvals++] = y-tany;
5442 vals.ptr[nvals++] = x;
5443 vals.ptr[nvals++] = y;
5445 px = x;
5446 py = y;
5447 ptanx = tanx;
5448 ptany = tany;
5451 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5455 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r,
5456 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW).
5457 * Angles are specified in radians.
5459 * Arguments: [cx, cy, r, a0, a1]*
5461 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo
5463 * Group: paths
5465 public void arc(string mode="original") (NVGContext ctx, NVGWinding dir, in float[] args) nothrow @trusted @nogc {
5466 static assert(mode == "original" || mode == "move" || mode == "line");
5467 enum ArgC = 5;
5468 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arc] call");
5469 if (args.length < ArgC) return;
5470 const(float)* aptr = args.ptr;
5471 foreach (immutable idx; 0..args.length/ArgC) {
5472 immutable cx = *aptr++;
5473 immutable cy = *aptr++;
5474 immutable r = *aptr++;
5475 immutable a0 = *aptr++;
5476 immutable a1 = *aptr++;
5477 ctx.arc!mode(dir, cx, cy, r, a0, a1);
5481 /// Creates new rectangle shaped sub-path.
5482 /// Group: paths
5483 @scriptable
5484 public void rect (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc {
5485 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5486 Command.MoveTo, x, y,
5487 Command.LineTo, x, y+h,
5488 Command.LineTo, x+w, y+h,
5489 Command.LineTo, x+w, y,
5490 Command.Close,
5494 /// Creates new rectangle shaped sub-path.
5495 /// Arguments: [x, y, w, h]*
5496 /// Group: paths
5497 public void rect (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5498 enum ArgC = 4;
5499 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [rect] call");
5500 if (args.length < ArgC) return;
5501 const(float)* aptr = args.ptr;
5502 foreach (immutable idx; 0..args.length/ArgC) {
5503 immutable x = *aptr++;
5504 immutable y = *aptr++;
5505 immutable w = *aptr++;
5506 immutable h = *aptr++;
5507 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5508 Command.MoveTo, x, y,
5509 Command.LineTo, x, y+h,
5510 Command.LineTo, x+w, y+h,
5511 Command.LineTo, x+w, y,
5512 Command.Close,
5517 /// Creates new rounded rectangle shaped sub-path.
5518 /// Group: paths
5519 @scriptable
5520 public void roundedRect (NVGContext ctx, in float x, in float y, in float w, in float h, in float radius) nothrow @trusted @nogc {
5521 ctx.roundedRectVarying(x, y, w, h, radius, radius, radius, radius);
5524 /// Creates new rounded rectangle shaped sub-path.
5525 /// Arguments: [x, y, w, h, radius]*
5526 /// Group: paths
5527 public void roundedRect (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5528 enum ArgC = 5;
5529 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRect] call");
5530 if (args.length < ArgC) return;
5531 const(float)* aptr = args.ptr;
5532 foreach (immutable idx; 0..args.length/ArgC) {
5533 immutable x = *aptr++;
5534 immutable y = *aptr++;
5535 immutable w = *aptr++;
5536 immutable h = *aptr++;
5537 immutable r = *aptr++;
5538 ctx.roundedRectVarying(x, y, w, h, r, r, r, r);
5542 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it.
5543 /// Group: paths
5544 @scriptable
5545 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 {
5546 if (rw < 0.1f || rh < 0.1f) {
5547 rect(ctx, x, y, w, h);
5548 } else {
5549 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5550 Command.MoveTo, x+rw, y,
5551 Command.LineTo, x+w-rw, y,
5552 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh,
5553 Command.LineTo, x+w, y+h-rh,
5554 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h,
5555 Command.LineTo, x+rw, y+h,
5556 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh,
5557 Command.LineTo, x, y+rh,
5558 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y,
5559 Command.Close,
5564 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it.
5565 /// Arguments: [x, y, w, h, rw, rh]*
5566 /// Group: paths
5567 public void roundedRectEllipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5568 enum ArgC = 6;
5569 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectEllipse] call");
5570 if (args.length < ArgC) return;
5571 const(float)* aptr = args.ptr;
5572 foreach (immutable idx; 0..args.length/ArgC) {
5573 immutable x = *aptr++;
5574 immutable y = *aptr++;
5575 immutable w = *aptr++;
5576 immutable h = *aptr++;
5577 immutable rw = *aptr++;
5578 immutable rh = *aptr++;
5579 if (rw < 0.1f || rh < 0.1f) {
5580 rect(ctx, x, y, w, h);
5581 } else {
5582 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5583 Command.MoveTo, x+rw, y,
5584 Command.LineTo, x+w-rw, y,
5585 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh,
5586 Command.LineTo, x+w, y+h-rh,
5587 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h,
5588 Command.LineTo, x+rw, y+h,
5589 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh,
5590 Command.LineTo, x, y+rh,
5591 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y,
5592 Command.Close,
5598 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner.
5599 /// Group: paths
5600 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 {
5601 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) {
5602 ctx.rect(x, y, w, h);
5603 } else {
5604 immutable float halfw = nvg__absf(w)*0.5f;
5605 immutable float halfh = nvg__absf(h)*0.5f;
5606 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h);
5607 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h);
5608 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h);
5609 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h);
5610 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5611 Command.MoveTo, x, y+ryTL,
5612 Command.LineTo, x, y+h-ryBL,
5613 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h,
5614 Command.LineTo, x+w-rxBR, y+h,
5615 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR,
5616 Command.LineTo, x+w, y+ryTR,
5617 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y,
5618 Command.LineTo, x+rxTL, y,
5619 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL,
5620 Command.Close,
5625 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner.
5626 /// Arguments: [x, y, w, h, radTopLeft, radTopRight, radBottomRight, radBottomLeft]*
5627 /// Group: paths
5628 public void roundedRectVarying (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5629 enum ArgC = 8;
5630 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectVarying] call");
5631 if (args.length < ArgC) return;
5632 const(float)* aptr = args.ptr;
5633 foreach (immutable idx; 0..args.length/ArgC) {
5634 immutable x = *aptr++;
5635 immutable y = *aptr++;
5636 immutable w = *aptr++;
5637 immutable h = *aptr++;
5638 immutable radTopLeft = *aptr++;
5639 immutable radTopRight = *aptr++;
5640 immutable radBottomRight = *aptr++;
5641 immutable radBottomLeft = *aptr++;
5642 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) {
5643 ctx.rect(x, y, w, h);
5644 } else {
5645 immutable float halfw = nvg__absf(w)*0.5f;
5646 immutable float halfh = nvg__absf(h)*0.5f;
5647 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h);
5648 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h);
5649 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h);
5650 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h);
5651 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5652 Command.MoveTo, x, y+ryTL,
5653 Command.LineTo, x, y+h-ryBL,
5654 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h,
5655 Command.LineTo, x+w-rxBR, y+h,
5656 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR,
5657 Command.LineTo, x+w, y+ryTR,
5658 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y,
5659 Command.LineTo, x+rxTL, y,
5660 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL,
5661 Command.Close,
5667 /// Creates new ellipse shaped sub-path.
5668 /// Group: paths
5669 public void ellipse (NVGContext ctx, in float cx, in float cy, in float rx, in float ry) nothrow @trusted @nogc {
5670 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5671 Command.MoveTo, cx-rx, cy,
5672 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,
5673 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,
5674 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,
5675 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,
5676 Command.Close,
5680 /// Creates new ellipse shaped sub-path.
5681 /// Arguments: [cx, cy, rx, ry]*
5682 /// Group: paths
5683 public void ellipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5684 enum ArgC = 4;
5685 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [ellipse] call");
5686 if (args.length < ArgC) return;
5687 const(float)* aptr = args.ptr;
5688 foreach (immutable idx; 0..args.length/ArgC) {
5689 immutable cx = *aptr++;
5690 immutable cy = *aptr++;
5691 immutable rx = *aptr++;
5692 immutable ry = *aptr++;
5693 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5694 Command.MoveTo, cx-rx, cy,
5695 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,
5696 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,
5697 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,
5698 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,
5699 Command.Close,
5704 /// Creates new circle shaped sub-path.
5705 /// Group: paths
5706 public void circle (NVGContext ctx, in float cx, in float cy, in float r) nothrow @trusted @nogc {
5707 ctx.ellipse(cx, cy, r, r);
5710 /// Creates new circle shaped sub-path.
5711 /// Arguments: [cx, cy, r]*
5712 /// Group: paths
5713 public void circle (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5714 enum ArgC = 3;
5715 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [circle] call");
5716 if (args.length < ArgC) return;
5717 const(float)* aptr = args.ptr;
5718 foreach (immutable idx; 0..args.length/ArgC) {
5719 immutable cx = *aptr++;
5720 immutable cy = *aptr++;
5721 immutable r = *aptr++;
5722 ctx.ellipse(cx, cy, r, r);
5726 // Debug function to dump cached path data.
5727 debug public void debugDumpPathCache (NVGContext ctx) nothrow @trusted @nogc {
5728 import core.stdc.stdio : printf;
5729 const(NVGpath)* path;
5730 printf("Dumping %d cached paths\n", ctx.cache.npaths);
5731 for (int i = 0; i < ctx.cache.npaths; ++i) {
5732 path = &ctx.cache.paths[i];
5733 printf("-Path %d\n", i);
5734 if (path.nfill) {
5735 printf("-fill: %d\n", path.nfill);
5736 for (int j = 0; j < path.nfill; ++j) printf("%f\t%f\n", path.fill[j].x, path.fill[j].y);
5738 if (path.nstroke) {
5739 printf("-stroke: %d\n", path.nstroke);
5740 for (int j = 0; j < path.nstroke; ++j) printf("%f\t%f\n", path.stroke[j].x, path.stroke[j].y);
5745 // Flatten path, prepare it for fill operation.
5746 void nvg__prepareFill (NVGContext ctx) nothrow @trusted @nogc {
5747 NVGpathCache* cache = ctx.cache;
5748 NVGstate* state = nvg__getState(ctx);
5750 nvg__flattenPaths!false(ctx);
5752 if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) {
5753 nvg__expandFill(ctx, ctx.fringeWidth, NVGLineCap.Miter, 2.4f);
5754 } else {
5755 nvg__expandFill(ctx, 0.0f, NVGLineCap.Miter, 2.4f);
5758 cache.evenOddMode = state.evenOddMode;
5759 cache.fringeWidth = ctx.fringeWidth;
5760 cache.fillReady = true;
5761 cache.strokeReady = false;
5762 cache.clipmode = NVGClipMode.None;
5765 // Flatten path, prepare it for stroke operation.
5766 void nvg__prepareStroke (NVGContext ctx) nothrow @trusted @nogc {
5767 NVGstate* state = nvg__getState(ctx);
5768 NVGpathCache* cache = ctx.cache;
5770 nvg__flattenPaths!true(ctx);
5772 immutable float scale = nvg__getAverageScale(state.xform);
5773 float strokeWidth = nvg__clamp(state.strokeWidth*scale, 0.0f, 200.0f);
5775 if (strokeWidth < ctx.fringeWidth) {
5776 // If the stroke width is less than pixel size, use alpha to emulate coverage.
5777 // Since coverage is area, scale by alpha*alpha.
5778 immutable float alpha = nvg__clamp(strokeWidth/ctx.fringeWidth, 0.0f, 1.0f);
5779 cache.strokeAlphaMul = alpha*alpha;
5780 strokeWidth = ctx.fringeWidth;
5781 } else {
5782 cache.strokeAlphaMul = 1.0f;
5784 cache.strokeWidth = strokeWidth;
5786 if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) {
5787 nvg__expandStroke(ctx, strokeWidth*0.5f+ctx.fringeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit);
5788 } else {
5789 nvg__expandStroke(ctx, strokeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit);
5792 cache.fringeWidth = ctx.fringeWidth;
5793 cache.fillReady = false;
5794 cache.strokeReady = true;
5795 cache.clipmode = NVGClipMode.None;
5798 /// Fills the current path with current fill style.
5799 /// Group: paths
5800 @scriptable
5801 public void fill (NVGContext ctx) nothrow @trusted @nogc {
5802 NVGstate* state = nvg__getState(ctx);
5804 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) {
5805 ctx.pathPickRegistered |= NVGPickKind.Fill<<16;
5806 ctx.currFillHitId = ctx.pathPickId;
5809 nvg__prepareFill(ctx);
5811 // apply global alpha
5812 NVGPaint fillPaint = state.fill;
5813 fillPaint.innerColor.a *= state.alpha;
5814 fillPaint.middleColor.a *= state.alpha;
5815 fillPaint.outerColor.a *= state.alpha;
5817 ctx.appendCurrentPathToCache(ctx.recset, state.fill);
5819 if (ctx.recblockdraw) return;
5821 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);
5823 // count triangles
5824 foreach (int i; 0..ctx.cache.npaths) {
5825 NVGpath* path = &ctx.cache.paths[i];
5826 ctx.fillTriCount += path.nfill-2;
5827 ctx.fillTriCount += path.nstroke-2;
5828 ctx.drawCallCount += 2;
5832 /// Fills the current path with current stroke style.
5833 /// Group: paths
5834 @scriptable
5835 public void stroke (NVGContext ctx) nothrow @trusted @nogc {
5836 NVGstate* state = nvg__getState(ctx);
5838 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) {
5839 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16;
5840 ctx.currStrokeHitId = ctx.pathPickId;
5843 nvg__prepareStroke(ctx);
5845 NVGpathCache* cache = ctx.cache;
5847 NVGPaint strokePaint = state.stroke;
5848 strokePaint.innerColor.a *= cache.strokeAlphaMul;
5849 strokePaint.middleColor.a *= cache.strokeAlphaMul;
5850 strokePaint.outerColor.a *= cache.strokeAlphaMul;
5852 // apply global alpha
5853 strokePaint.innerColor.a *= state.alpha;
5854 strokePaint.middleColor.a *= state.alpha;
5855 strokePaint.outerColor.a *= state.alpha;
5857 ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
5859 if (ctx.recblockdraw) return;
5861 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths);
5863 // count triangles
5864 foreach (int i; 0..ctx.cache.npaths) {
5865 NVGpath* path = &ctx.cache.paths[i];
5866 ctx.strokeTriCount += path.nstroke-2;
5867 ++ctx.drawCallCount;
5871 /// Sets current path as clipping region.
5872 /// Group: clipping
5873 public void clip (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc {
5874 NVGstate* state = nvg__getState(ctx);
5876 if (aclipmode == NVGClipMode.None) return;
5877 if (ctx.recblockdraw) return; //???
5879 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr);
5882 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) {
5883 ctx.pathPickRegistered |= NVGPickKind.Fill<<16;
5884 ctx.currFillHitId = ctx.pathPickId;
5888 nvg__prepareFill(ctx);
5890 // apply global alpha
5891 NVGPaint fillPaint = state.fill;
5892 fillPaint.innerColor.a *= state.alpha;
5893 fillPaint.middleColor.a *= state.alpha;
5894 fillPaint.outerColor.a *= state.alpha;
5896 //ctx.appendCurrentPathToCache(ctx.recset, state.fill);
5898 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);
5900 // count triangles
5901 foreach (int i; 0..ctx.cache.npaths) {
5902 NVGpath* path = &ctx.cache.paths[i];
5903 ctx.fillTriCount += path.nfill-2;
5904 ctx.fillTriCount += path.nstroke-2;
5905 ctx.drawCallCount += 2;
5909 /// Sets current path as clipping region.
5910 /// Group: clipping
5911 public alias clipFill = clip;
5913 /// Sets current path' stroke as clipping region.
5914 /// Group: clipping
5915 public void clipStroke (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc {
5916 NVGstate* state = nvg__getState(ctx);
5918 if (aclipmode == NVGClipMode.None) return;
5919 if (ctx.recblockdraw) return; //???
5921 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr);
5924 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) {
5925 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16;
5926 ctx.currStrokeHitId = ctx.pathPickId;
5930 nvg__prepareStroke(ctx);
5932 NVGpathCache* cache = ctx.cache;
5934 NVGPaint strokePaint = state.stroke;
5935 strokePaint.innerColor.a *= cache.strokeAlphaMul;
5936 strokePaint.middleColor.a *= cache.strokeAlphaMul;
5937 strokePaint.outerColor.a *= cache.strokeAlphaMul;
5939 // apply global alpha
5940 strokePaint.innerColor.a *= state.alpha;
5941 strokePaint.middleColor.a *= state.alpha;
5942 strokePaint.outerColor.a *= state.alpha;
5944 //ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
5946 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, aclipmode, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths);
5948 // count triangles
5949 foreach (int i; 0..ctx.cache.npaths) {
5950 NVGpath* path = &ctx.cache.paths[i];
5951 ctx.strokeTriCount += path.nstroke-2;
5952 ++ctx.drawCallCount;
5957 // ////////////////////////////////////////////////////////////////////////// //
5958 // Picking API
5960 // most of the code is by Michael Wynne <mike@mikesspace.net>
5961 // https://github.com/memononen/nanovg/pull/230
5962 // https://github.com/MikeWW/nanovg
5964 /// Pick type query. Used in [hitTest] and [hitTestAll].
5965 /// Group: picking_api
5966 public enum NVGPickKind : ubyte {
5967 Fill = 0x01, ///
5968 Stroke = 0x02, ///
5969 All = 0x03, ///
5972 /// Marks the fill of the current path as pickable with the specified id.
5973 /// Note that you can create and mark path without rasterizing it.
5974 /// Group: picking_api
5975 public void currFillHitId (NVGContext ctx, int id) nothrow @trusted @nogc {
5976 NVGpickScene* ps = nvg__pickSceneGet(ctx);
5977 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/false);
5978 nvg__pickSceneInsert(ps, pp);
5981 public alias currFillPickId = currFillHitId; /// Ditto.
5983 /// Marks the stroke of the current path as pickable with the specified id.
5984 /// Note that you can create and mark path without rasterizing it.
5985 /// Group: picking_api
5986 public void currStrokeHitId (NVGContext ctx, int id) nothrow @trusted @nogc {
5987 NVGpickScene* ps = nvg__pickSceneGet(ctx);
5988 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/true);
5989 nvg__pickSceneInsert(ps, pp);
5992 public alias currStrokePickId = currStrokeHitId; /// Ditto.
5994 // Marks the saved path set (fill) as pickable with the specified id.
5995 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!)
5996 // Group: picking_api
5998 public void pathSetFillHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc {
5999 if (svp is null) return;
6000 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context");
6001 foreach (ref cp; svp.caches[0..svp.ncaches]) {
6002 NVGpickScene* ps = nvg__pickSceneGet(ctx);
6003 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/false);
6004 nvg__pickSceneInsert(ps, pp);
6009 // Marks the saved path set (stroke) as pickable with the specified id.
6010 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!)
6011 // Group: picking_api
6013 public void pathSetStrokeHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc {
6014 if (svp is null) return;
6015 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context");
6016 foreach (ref cp; svp.caches[0..svp.ncaches]) {
6017 NVGpickScene* ps = nvg__pickSceneGet(ctx);
6018 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/true);
6019 nvg__pickSceneInsert(ps, pp);
6024 private template IsGoodHitTestDG(DG) {
6025 enum IsGoodHitTestDG =
6026 __traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); }) ||
6027 __traits(compiles, (){ DG dg; dg(cast(int)42, cast(int)666); });
6030 private template IsGoodHitTestInternalDG(DG) {
6031 enum IsGoodHitTestInternalDG =
6032 __traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); }) ||
6033 __traits(compiles, (){ DG dg; NVGpickPath* pp; dg(pp); });
6036 /// Call delegate [dg] for each path under the specified position (in no particular order).
6037 /// Returns the id of the path for which delegate [dg] returned true or [NVGNoPick].
6038 /// dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending).
6039 /// Group: picking_api
6040 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) {
6041 if (ctx.pickScene is null || ctx.pickScene.npaths == 0 || (kind&NVGPickKind.All) == 0) return -1;
6043 NVGpickScene* ps = ctx.pickScene;
6044 int levelwidth = 1<<(ps.nlevels-1);
6045 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth);
6046 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth);
6047 int npicked = 0;
6049 // if we are interested only in most-toplevel path, there is no reason to check paths with worser order.
6050 // but we cannot just get out on the first path found, 'cause we are using quad tree to speed up bounds
6051 // checking, so path walking order is not guaranteed.
6052 static if (bestOrder) {
6053 int lastBestOrder = int.min;
6056 //{ import core.stdc.stdio; printf("npaths=%d\n", ps.npaths); }
6057 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) {
6058 for (NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; pp !is null; pp = pp.next) {
6059 //{ 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); }
6060 static if (bestOrder) {
6061 // reject earlier paths
6062 if (pp.order <= lastBestOrder) continue; // not interesting
6064 immutable uint kpx = kind&pp.flags&3;
6065 if (kpx == 0) continue; // not interesting
6066 if (!nvg__pickPathTestBounds(ctx, ps, pp, x, y)) continue; // not interesting
6067 //{ import core.stdc.stdio; printf("in bounds!\n"); }
6068 int hit = 0;
6069 if (kpx&NVGPickKind.Stroke) hit = nvg__pickPathStroke(ps, pp, x, y);
6070 if (!hit && (kpx&NVGPickKind.Fill)) hit = nvg__pickPath(ps, pp, x, y);
6071 if (!hit) continue;
6072 //{ import core.stdc.stdio; printf(" HIT!\n"); }
6073 static if (bestOrder) lastBestOrder = pp.order;
6074 static if (IsGoodHitTestDG!DG) {
6075 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) {
6076 if (dg(pp.id, cast(int)pp.order)) return pp.id;
6077 } else {
6078 dg(pp.id, cast(int)pp.order);
6080 } else {
6081 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) {
6082 if (dg(pp)) return pp.id;
6083 } else {
6084 dg(pp);
6088 cellx >>= 1;
6089 celly >>= 1;
6090 levelwidth >>= 1;
6093 return -1;
6096 /// Fills ids with a list of the top most hit ids (from bottom to top) under the specified position.
6097 /// Returns the slice of [ids].
6098 /// Group: picking_api
6099 public int[] hitTestAll (NVGContext ctx, in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc {
6100 if (ctx.pickScene is null || ids.length == 0) return ids[0..0];
6102 int npicked = 0;
6103 NVGpickScene* ps = ctx.pickScene;
6105 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
6106 if (npicked == ps.cpicked) {
6107 int cpicked = ps.cpicked+ps.cpicked;
6108 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked);
6109 if (picked is null) return true; // abort
6110 ps.cpicked = cpicked;
6111 ps.picked = picked;
6113 ps.picked[npicked] = pp;
6114 ++npicked;
6115 return false; // go on
6118 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths);
6120 assert(npicked >= 0);
6121 if (npicked > ids.length) npicked = cast(int)ids.length;
6122 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id;
6124 return ids[0..npicked];
6127 /// Returns the id of the pickable shape containing x,y or [NVGNoPick] if no shape was found.
6128 /// Group: picking_api
6129 public int hitTest (NVGContext ctx, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc {
6130 if (ctx.pickScene is null) return -1;
6132 int bestOrder = int.min;
6133 int bestID = -1;
6135 ctx.hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) {
6136 if (pp.order > bestOrder) {
6137 bestOrder = pp.order;
6138 bestID = pp.id;
6142 return bestID;
6145 /// Returns `true` if the path with the given id contains x,y.
6146 /// Group: picking_api
6147 public bool hitTestForId (NVGContext ctx, in int id, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc {
6148 if (ctx.pickScene is null || id == NVGNoPick) return false;
6150 bool res = false;
6152 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) {
6153 if (pp.id == id) {
6154 res = true;
6155 return true; // stop
6157 return false; // continue
6160 return res;
6163 /// Returns `true` if the given point is within the fill of the currently defined path.
6164 /// This operation can be done before rasterizing the current path.
6165 /// Group: picking_api
6166 public bool hitTestCurrFill (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
6167 NVGpickScene* ps = nvg__pickSceneGet(ctx);
6168 int oldnpoints = ps.npoints;
6169 int oldnsegments = ps.nsegments;
6170 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/false);
6171 if (pp is null) return false; // oops
6172 scope(exit) {
6173 nvg__freePickPath(ps, pp);
6174 ps.npoints = oldnpoints;
6175 ps.nsegments = oldnsegments;
6177 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPath(ps, pp, x, y) : false);
6180 public alias isPointInPath = hitTestCurrFill; /// Ditto.
6182 /// Returns `true` if the given point is within the stroke of the currently defined path.
6183 /// This operation can be done before rasterizing the current path.
6184 /// Group: picking_api
6185 public bool hitTestCurrStroke (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
6186 NVGpickScene* ps = nvg__pickSceneGet(ctx);
6187 int oldnpoints = ps.npoints;
6188 int oldnsegments = ps.nsegments;
6189 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/true);
6190 if (pp is null) return false; // oops
6191 scope(exit) {
6192 nvg__freePickPath(ps, pp);
6193 ps.npoints = oldnpoints;
6194 ps.nsegments = oldnsegments;
6196 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPathStroke(ps, pp, x, y) : false);
6200 nothrow @trusted @nogc {
6201 extern(C) {
6202 private alias _compare_fp_t = int function (const void*, const void*) nothrow @nogc;
6203 private extern(C) void qsort (scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc;
6205 extern(C) int nvg__comparePaths (const void* a, const void* b) {
6206 return (*cast(const(NVGpickPath)**)b).order-(*cast(const(NVGpickPath)**)a).order;
6210 enum NVGPickEPS = 0.0001f;
6212 // Segment flags
6213 enum NVGSegmentFlags {
6214 Corner = 1,
6215 Bevel = 2,
6216 InnerBevel = 4,
6217 Cap = 8,
6218 Endcap = 16,
6221 // Path flags
6222 enum NVGPathFlags : ushort {
6223 Fill = NVGPickKind.Fill,
6224 Stroke = NVGPickKind.Stroke,
6225 Scissor = 0x80,
6228 struct NVGsegment {
6229 int firstPoint; // Index into NVGpickScene.points
6230 short type; // NVG_LINETO or NVG_BEZIERTO
6231 short flags; // Flags relate to the corner between the prev segment and this one.
6232 float[4] bounds;
6233 float[2] startDir; // Direction at t == 0
6234 float[2] endDir; // Direction at t == 1
6235 float[2] miterDir; // Direction of miter of corner between the prev segment and this one.
6238 struct NVGpickSubPath {
6239 short winding; // TODO: Merge to flag field
6240 bool closed; // TODO: Merge to flag field
6242 int firstSegment; // Index into NVGpickScene.segments
6243 int nsegments;
6245 float[4] bounds;
6247 NVGpickSubPath* next;
6250 struct NVGpickPath {
6251 int id;
6252 short flags;
6253 short order;
6254 float strokeWidth;
6255 float miterLimit;
6256 short lineCap;
6257 short lineJoin;
6258 bool evenOddMode;
6260 float[4] bounds;
6261 int scissor; // Indexes into ps->points and defines scissor rect as XVec, YVec and Center
6263 NVGpickSubPath* subPaths;
6264 NVGpickPath* next;
6265 NVGpickPath* cellnext;
6268 struct NVGpickScene {
6269 int npaths;
6271 NVGpickPath* paths; // Linked list of paths
6272 NVGpickPath* lastPath; // The last path in the paths linked list (the first path added)
6273 NVGpickPath* freePaths; // Linked list of free paths
6275 NVGpickSubPath* freeSubPaths; // Linked list of free sub paths
6277 int width;
6278 int height;
6280 // Points for all path sub paths.
6281 float* points;
6282 int npoints;
6283 int cpoints;
6285 // Segments for all path sub paths
6286 NVGsegment* segments;
6287 int nsegments;
6288 int csegments;
6290 // Implicit quadtree
6291 float xdim; // Width / (1 << nlevels)
6292 float ydim; // Height / (1 << nlevels)
6293 int ncells; // Total number of cells in all levels
6294 int nlevels;
6295 NVGpickPath*** levels; // Index: [Level][LevelY * LevelW + LevelX] Value: Linked list of paths
6297 // Temp storage for picking
6298 int cpicked;
6299 NVGpickPath** picked;
6303 // bounds utilities
6304 void nvg__initBounds (ref float[4] bounds) {
6305 bounds.ptr[0] = bounds.ptr[1] = float.max;
6306 bounds.ptr[2] = bounds.ptr[3] = -float.max;
6309 void nvg__expandBounds (ref float[4] bounds, const(float)* points, int npoints) {
6310 npoints *= 2;
6311 for (int i = 0; i < npoints; i += 2) {
6312 bounds.ptr[0] = nvg__min(bounds.ptr[0], points[i]);
6313 bounds.ptr[1] = nvg__min(bounds.ptr[1], points[i+1]);
6314 bounds.ptr[2] = nvg__max(bounds.ptr[2], points[i]);
6315 bounds.ptr[3] = nvg__max(bounds.ptr[3], points[i+1]);
6319 void nvg__unionBounds (ref float[4] bounds, in ref float[4] boundsB) {
6320 bounds.ptr[0] = nvg__min(bounds.ptr[0], boundsB.ptr[0]);
6321 bounds.ptr[1] = nvg__min(bounds.ptr[1], boundsB.ptr[1]);
6322 bounds.ptr[2] = nvg__max(bounds.ptr[2], boundsB.ptr[2]);
6323 bounds.ptr[3] = nvg__max(bounds.ptr[3], boundsB.ptr[3]);
6326 void nvg__intersectBounds (ref float[4] bounds, in ref float[4] boundsB) {
6327 bounds.ptr[0] = nvg__max(boundsB.ptr[0], bounds.ptr[0]);
6328 bounds.ptr[1] = nvg__max(boundsB.ptr[1], bounds.ptr[1]);
6329 bounds.ptr[2] = nvg__min(boundsB.ptr[2], bounds.ptr[2]);
6330 bounds.ptr[3] = nvg__min(boundsB.ptr[3], bounds.ptr[3]);
6332 bounds.ptr[2] = nvg__max(bounds.ptr[0], bounds.ptr[2]);
6333 bounds.ptr[3] = nvg__max(bounds.ptr[1], bounds.ptr[3]);
6336 bool nvg__pointInBounds (in float x, in float y, in ref float[4] bounds) {
6337 pragma(inline, true);
6338 return (x >= bounds.ptr[0] && x <= bounds.ptr[2] && y >= bounds.ptr[1] && y <= bounds.ptr[3]);
6341 // building paths & sub paths
6342 int nvg__pickSceneAddPoints (NVGpickScene* ps, const(float)* xy, int n) {
6343 import core.stdc.string : memcpy;
6344 if (ps.npoints+n > ps.cpoints) {
6345 import core.stdc.stdlib : realloc;
6346 int cpoints = ps.npoints+n+(ps.cpoints<<1);
6347 float* points = cast(float*)realloc(ps.points, float.sizeof*2*cpoints);
6348 if (points is null) assert(0, "NanoVega: out of memory");
6349 ps.points = points;
6350 ps.cpoints = cpoints;
6352 int i = ps.npoints;
6353 if (xy !is null) memcpy(&ps.points[i*2], xy, float.sizeof*2*n);
6354 ps.npoints += n;
6355 return i;
6358 void nvg__pickSubPathAddSegment (NVGpickScene* ps, NVGpickSubPath* psp, int firstPoint, int type, short flags) {
6359 NVGsegment* seg = null;
6360 if (ps.nsegments == ps.csegments) {
6361 int csegments = 1+ps.csegments+(ps.csegments<<1);
6362 NVGsegment* segments = cast(NVGsegment*)realloc(ps.segments, NVGsegment.sizeof*csegments);
6363 if (segments is null) assert(0, "NanoVega: out of memory");
6364 ps.segments = segments;
6365 ps.csegments = csegments;
6368 if (psp.firstSegment == -1) psp.firstSegment = ps.nsegments;
6370 seg = &ps.segments[ps.nsegments];
6371 ++ps.nsegments;
6372 seg.firstPoint = firstPoint;
6373 seg.type = cast(short)type;
6374 seg.flags = flags;
6375 ++psp.nsegments;
6377 nvg__segmentDir(ps, psp, seg, 0, seg.startDir);
6378 nvg__segmentDir(ps, psp, seg, 1, seg.endDir);
6381 void nvg__segmentDir (NVGpickScene* ps, NVGpickSubPath* psp, NVGsegment* seg, float t, ref float[2] d) {
6382 const(float)* points = &ps.points[seg.firstPoint*2];
6383 immutable float x0 = points[0*2+0], x1 = points[1*2+0];
6384 immutable float y0 = points[0*2+1], y1 = points[1*2+1];
6385 switch (seg.type) {
6386 case Command.LineTo:
6387 d.ptr[0] = x1-x0;
6388 d.ptr[1] = y1-y0;
6389 nvg__normalize(&d.ptr[0], &d.ptr[1]);
6390 break;
6391 case Command.BezierTo:
6392 immutable float x2 = points[2*2+0];
6393 immutable float y2 = points[2*2+1];
6394 immutable float x3 = points[3*2+0];
6395 immutable float y3 = points[3*2+1];
6397 immutable float omt = 1.0f-t;
6398 immutable float omt2 = omt*omt;
6399 immutable float t2 = t*t;
6401 d.ptr[0] =
6402 3.0f*omt2*(x1-x0)+
6403 6.0f*omt*t*(x2-x1)+
6404 3.0f*t2*(x3-x2);
6406 d.ptr[1] =
6407 3.0f*omt2*(y1-y0)+
6408 6.0f*omt*t*(y2-y1)+
6409 3.0f*t2*(y3-y2);
6411 nvg__normalize(&d.ptr[0], &d.ptr[1]);
6412 break;
6413 default:
6414 break;
6418 void nvg__pickSubPathAddFillSupports (NVGpickScene* ps, NVGpickSubPath* psp) {
6419 if (psp.firstSegment == -1) return;
6420 NVGsegment* segments = &ps.segments[psp.firstSegment];
6421 for (int s = 0; s < psp.nsegments; ++s) {
6422 NVGsegment* seg = &segments[s];
6423 const(float)* points = &ps.points[seg.firstPoint*2];
6424 if (seg.type == Command.LineTo) {
6425 nvg__initBounds(seg.bounds);
6426 nvg__expandBounds(seg.bounds, points, 2);
6427 } else {
6428 nvg__bezierBounds(points, seg.bounds);
6433 void nvg__pickSubPathAddStrokeSupports (NVGpickScene* ps, NVGpickSubPath* psp, float strokeWidth, int lineCap, int lineJoin, float miterLimit) {
6434 if (psp.firstSegment == -1) return;
6435 immutable bool closed = psp.closed;
6436 const(float)* points = ps.points;
6437 NVGsegment* seg = null;
6438 NVGsegment* segments = &ps.segments[psp.firstSegment];
6439 int nsegments = psp.nsegments;
6440 NVGsegment* prevseg = (closed ? &segments[psp.nsegments-1] : null);
6442 int ns = 0; // nsupports
6443 float[32] supportingPoints = void;
6444 int firstPoint, lastPoint;
6446 if (!closed) {
6447 segments[0].flags |= NVGSegmentFlags.Cap;
6448 segments[nsegments-1].flags |= NVGSegmentFlags.Endcap;
6451 for (int s = 0; s < nsegments; ++s) {
6452 seg = &segments[s];
6453 nvg__initBounds(seg.bounds);
6455 firstPoint = seg.firstPoint*2;
6456 lastPoint = firstPoint+(seg.type == Command.LineTo ? 2 : 6);
6458 ns = 0;
6460 // First two supporting points are either side of the start point
6461 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[1]*strokeWidth;
6462 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.startDir.ptr[0]*strokeWidth;
6464 supportingPoints.ptr[ns++] = points[firstPoint]+seg.startDir.ptr[1]*strokeWidth;
6465 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[0]*strokeWidth;
6467 // Second two supporting points are either side of the end point
6468 supportingPoints.ptr[ns++] = points[lastPoint]-seg.endDir.ptr[1]*strokeWidth;
6469 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[0]*strokeWidth;
6471 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[1]*strokeWidth;
6472 supportingPoints.ptr[ns++] = points[lastPoint+1]-seg.endDir.ptr[0]*strokeWidth;
6474 if ((seg.flags&NVGSegmentFlags.Corner) && prevseg !is null) {
6475 seg.miterDir.ptr[0] = 0.5f*(-prevseg.endDir.ptr[1]-seg.startDir.ptr[1]);
6476 seg.miterDir.ptr[1] = 0.5f*(prevseg.endDir.ptr[0]+seg.startDir.ptr[0]);
6478 immutable float M2 = seg.miterDir.ptr[0]*seg.miterDir.ptr[0]+seg.miterDir.ptr[1]*seg.miterDir.ptr[1];
6480 if (M2 > 0.000001f) {
6481 float scale = 1.0f/M2;
6482 if (scale > 600.0f) scale = 600.0f;
6483 seg.miterDir.ptr[0] *= scale;
6484 seg.miterDir.ptr[1] *= scale;
6487 //NVG_PICK_DEBUG_VECTOR_SCALE(&points[firstPoint], seg.miterDir, 10);
6489 // Add an additional support at the corner on the other line
6490 supportingPoints.ptr[ns++] = points[firstPoint]-prevseg.endDir.ptr[1]*strokeWidth;
6491 supportingPoints.ptr[ns++] = points[firstPoint+1]+prevseg.endDir.ptr[0]*strokeWidth;
6493 if (lineJoin == NVGLineCap.Miter || lineJoin == NVGLineCap.Bevel) {
6494 // Set a corner as beveled if the join type is bevel or mitered and
6495 // miterLimit is hit.
6496 if (lineJoin == NVGLineCap.Bevel || (M2*miterLimit*miterLimit) < 1.0f) {
6497 seg.flags |= NVGSegmentFlags.Bevel;
6498 } else {
6499 // Corner is mitered - add miter point as a support
6500 supportingPoints.ptr[ns++] = points[firstPoint]+seg.miterDir.ptr[0]*strokeWidth;
6501 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.miterDir.ptr[1]*strokeWidth;
6503 } else if (lineJoin == NVGLineCap.Round) {
6504 // ... and at the midpoint of the corner arc
6505 float[2] vertexN = [ -seg.startDir.ptr[0]+prevseg.endDir.ptr[0], -seg.startDir.ptr[1]+prevseg.endDir.ptr[1] ];
6506 nvg__normalize(&vertexN[0], &vertexN[1]);
6508 supportingPoints.ptr[ns++] = points[firstPoint]+vertexN[0]*strokeWidth;
6509 supportingPoints.ptr[ns++] = points[firstPoint+1]+vertexN[1]*strokeWidth;
6513 if (seg.flags&NVGSegmentFlags.Cap) {
6514 switch (lineCap) {
6515 case NVGLineCap.Butt:
6516 // supports for butt already added
6517 break;
6518 case NVGLineCap.Square:
6519 // square cap supports are just the original two supports moved out along the direction
6520 supportingPoints.ptr[ns++] = supportingPoints.ptr[0]-seg.startDir.ptr[0]*strokeWidth;
6521 supportingPoints.ptr[ns++] = supportingPoints.ptr[1]-seg.startDir.ptr[1]*strokeWidth;
6522 supportingPoints.ptr[ns++] = supportingPoints.ptr[2]-seg.startDir.ptr[0]*strokeWidth;
6523 supportingPoints.ptr[ns++] = supportingPoints.ptr[3]-seg.startDir.ptr[1]*strokeWidth;
6524 break;
6525 case NVGLineCap.Round:
6526 // add one additional support for the round cap along the dir
6527 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[0]*strokeWidth;
6528 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[1]*strokeWidth;
6529 break;
6530 default:
6531 break;
6535 if (seg.flags&NVGSegmentFlags.Endcap) {
6536 // end supporting points, either side of line
6537 int end = 4;
6538 switch(lineCap) {
6539 case NVGLineCap.Butt:
6540 // supports for butt already added
6541 break;
6542 case NVGLineCap.Square:
6543 // square cap supports are just the original two supports moved out along the direction
6544 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+0]+seg.endDir.ptr[0]*strokeWidth;
6545 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+1]+seg.endDir.ptr[1]*strokeWidth;
6546 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+2]+seg.endDir.ptr[0]*strokeWidth;
6547 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+3]+seg.endDir.ptr[1]*strokeWidth;
6548 break;
6549 case NVGLineCap.Round:
6550 // add one additional support for the round cap along the dir
6551 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[0]*strokeWidth;
6552 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[1]*strokeWidth;
6553 break;
6554 default:
6555 break;
6559 nvg__expandBounds(seg.bounds, supportingPoints.ptr, ns/2);
6561 prevseg = seg;
6565 NVGpickPath* nvg__pickPathCreate (NVGContext context, const(float)[] acommands, int id, bool forStroke) {
6566 NVGpickScene* ps = nvg__pickSceneGet(context);
6567 if (ps is null) return null;
6569 int i = 0;
6571 int ncommands = cast(int)acommands.length;
6572 const(float)* commands = acommands.ptr;
6574 NVGpickPath* pp = null;
6575 NVGpickSubPath* psp = null;
6576 float[2] start = void;
6577 int firstPoint;
6579 //bool hasHoles = false;
6580 NVGpickSubPath* prev = null;
6582 float[8] points = void;
6583 float[2] inflections = void;
6584 int ninflections = 0;
6586 NVGstate* state = nvg__getState(context);
6587 float[4] totalBounds = void;
6588 NVGsegment* segments = null;
6589 const(NVGsegment)* seg = null;
6590 NVGpickSubPath *curpsp;
6592 pp = nvg__allocPickPath(ps);
6593 if (pp is null) return null;
6595 pp.id = id;
6597 bool hasPoints = false;
6599 void closeIt () {
6600 if (psp is null || !hasPoints) return;
6601 if (ps.points[(ps.npoints-1)*2] != start.ptr[0] || ps.points[(ps.npoints-1)*2+1] != start.ptr[1]) {
6602 firstPoint = nvg__pickSceneAddPoints(ps, start.ptr, 1);
6603 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, Command.LineTo, NVGSegmentFlags.Corner);
6605 psp.closed = true;
6608 while (i < ncommands) {
6609 int cmd = cast(int)commands[i++];
6610 switch (cmd) {
6611 case Command.MoveTo: // one coordinate pair
6612 const(float)* tfxy = commands+i;
6613 i += 2;
6615 // new starting point
6616 start.ptr[0..2] = tfxy[0..2];
6618 // start a new path for each sub path to handle sub paths that intersect other sub paths
6619 prev = psp;
6620 psp = nvg__allocPickSubPath(ps);
6621 if (psp is null) { psp = prev; break; }
6622 psp.firstSegment = -1;
6623 psp.winding = NVGSolidity.Solid;
6624 psp.next = prev;
6626 nvg__pickSceneAddPoints(ps, tfxy, 1);
6627 hasPoints = true;
6628 break;
6629 case Command.LineTo: // one coordinate pair
6630 const(float)* tfxy = commands+i;
6631 i += 2;
6632 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 1);
6633 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner);
6634 hasPoints = true;
6635 break;
6636 case Command.BezierTo: // three coordinate pairs
6637 const(float)* tfxy = commands+i;
6638 i += 3*2;
6640 // Split the curve at it's dx==0 or dy==0 inflection points.
6641 // Thus:
6642 // A horizontal line only ever interects the curves once.
6643 // and
6644 // Finding the closest point on any curve converges more reliably.
6646 // NOTE: We could just split on dy==0 here.
6648 memcpy(&points.ptr[0], &ps.points[(ps.npoints-1)*2], float.sizeof*2);
6649 memcpy(&points.ptr[2], tfxy, float.sizeof*2*3);
6651 ninflections = 0;
6652 nvg__bezierInflections(points.ptr, 1, &ninflections, inflections.ptr);
6653 nvg__bezierInflections(points.ptr, 0, &ninflections, inflections.ptr);
6655 if (ninflections) {
6656 float previnfl = 0;
6657 float[8] pointsA = void, pointsB = void;
6659 nvg__smallsort(inflections.ptr, ninflections);
6661 for (int infl = 0; infl < ninflections; ++infl) {
6662 if (nvg__absf(inflections.ptr[infl]-previnfl) < NVGPickEPS) continue;
6664 immutable float t = (inflections.ptr[infl]-previnfl)*(1.0f/(1.0f-previnfl));
6666 previnfl = inflections.ptr[infl];
6668 nvg__splitBezier(points.ptr, t, pointsA.ptr, pointsB.ptr);
6670 firstPoint = nvg__pickSceneAddPoints(ps, &pointsA.ptr[2], 3);
6671 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, (infl == 0) ? NVGSegmentFlags.Corner : 0);
6673 memcpy(points.ptr, pointsB.ptr, float.sizeof*8);
6676 firstPoint = nvg__pickSceneAddPoints(ps, &pointsB.ptr[2], 3);
6677 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, 0);
6678 } else {
6679 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 3);
6680 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner);
6682 hasPoints = true;
6683 break;
6684 case Command.Close:
6685 closeIt();
6686 break;
6687 case Command.Winding:
6688 psp.winding = cast(short)cast(int)commands[i];
6689 //if (psp.winding == NVGSolidity.Hole) hasHoles = true;
6690 i += 1;
6691 break;
6692 default:
6693 break;
6697 // force-close filled paths
6698 if (psp !is null && !forStroke && hasPoints && !psp.closed) closeIt();
6700 pp.flags = (forStroke ? NVGPathFlags.Stroke : NVGPathFlags.Fill);
6701 pp.subPaths = psp;
6702 pp.strokeWidth = state.strokeWidth*0.5f;
6703 pp.miterLimit = state.miterLimit;
6704 pp.lineCap = cast(short)state.lineCap;
6705 pp.lineJoin = cast(short)state.lineJoin;
6706 pp.evenOddMode = nvg__getState(context).evenOddMode;
6708 nvg__initBounds(totalBounds);
6710 for (curpsp = psp; curpsp; curpsp = curpsp.next) {
6711 if (forStroke) {
6712 nvg__pickSubPathAddStrokeSupports(ps, curpsp, pp.strokeWidth, pp.lineCap, pp.lineJoin, pp.miterLimit);
6713 } else {
6714 nvg__pickSubPathAddFillSupports(ps, curpsp);
6717 if (curpsp.firstSegment == -1) continue;
6718 segments = &ps.segments[curpsp.firstSegment];
6719 nvg__initBounds(curpsp.bounds);
6720 for (int s = 0; s < curpsp.nsegments; ++s) {
6721 seg = &segments[s];
6722 //NVG_PICK_DEBUG_BOUNDS(seg.bounds);
6723 nvg__unionBounds(curpsp.bounds, seg.bounds);
6726 nvg__unionBounds(totalBounds, curpsp.bounds);
6729 // Store the scissor rect if present.
6730 if (state.scissor.extent.ptr[0] != -1.0f) {
6731 // Use points storage to store the scissor data
6732 pp.scissor = nvg__pickSceneAddPoints(ps, null, 4);
6733 float* scissor = &ps.points[pp.scissor*2];
6735 //memcpy(scissor, state.scissor.xform.ptr, 6*float.sizeof);
6736 scissor[0..6] = state.scissor.xform.mat[];
6737 memcpy(scissor+6, state.scissor.extent.ptr, 2*float.sizeof);
6739 pp.flags |= NVGPathFlags.Scissor;
6742 memcpy(pp.bounds.ptr, totalBounds.ptr, float.sizeof*4);
6744 return pp;
6748 // Struct management
6749 NVGpickPath* nvg__allocPickPath (NVGpickScene* ps) {
6750 NVGpickPath* pp = ps.freePaths;
6751 if (pp !is null) {
6752 ps.freePaths = pp.next;
6753 } else {
6754 pp = cast(NVGpickPath*)malloc(NVGpickPath.sizeof);
6756 memset(pp, 0, NVGpickPath.sizeof);
6757 return pp;
6760 // Put a pick path and any sub paths (back) to the free lists.
6761 void nvg__freePickPath (NVGpickScene* ps, NVGpickPath* pp) {
6762 // Add all sub paths to the sub path free list.
6763 // Finds the end of the path sub paths, links that to the current
6764 // sub path free list head and replaces the head ptr with the
6765 // head path sub path entry.
6766 NVGpickSubPath* psp = null;
6767 for (psp = pp.subPaths; psp !is null && psp.next !is null; psp = psp.next) {}
6769 if (psp) {
6770 psp.next = ps.freeSubPaths;
6771 ps.freeSubPaths = pp.subPaths;
6773 pp.subPaths = null;
6775 // Add the path to the path freelist
6776 pp.next = ps.freePaths;
6777 ps.freePaths = pp;
6778 if (pp.next is null) ps.lastPath = pp;
6781 NVGpickSubPath* nvg__allocPickSubPath (NVGpickScene* ps) {
6782 NVGpickSubPath* psp = ps.freeSubPaths;
6783 if (psp !is null) {
6784 ps.freeSubPaths = psp.next;
6785 } else {
6786 psp = cast(NVGpickSubPath*)malloc(NVGpickSubPath.sizeof);
6787 if (psp is null) return null;
6789 memset(psp, 0, NVGpickSubPath.sizeof);
6790 return psp;
6793 void nvg__returnPickSubPath (NVGpickScene* ps, NVGpickSubPath* psp) {
6794 psp.next = ps.freeSubPaths;
6795 ps.freeSubPaths = psp;
6798 NVGpickScene* nvg__allocPickScene () {
6799 NVGpickScene* ps = cast(NVGpickScene*)malloc(NVGpickScene.sizeof);
6800 if (ps is null) return null;
6801 memset(ps, 0, NVGpickScene.sizeof);
6802 ps.nlevels = 5;
6803 return ps;
6806 void nvg__deletePickScene (NVGpickScene* ps) {
6807 NVGpickPath* pp;
6808 NVGpickSubPath* psp;
6810 // Add all paths (and thus sub paths) to the free list(s).
6811 while (ps.paths !is null) {
6812 pp = ps.paths.next;
6813 nvg__freePickPath(ps, ps.paths);
6814 ps.paths = pp;
6817 // Delete all paths
6818 while (ps.freePaths !is null) {
6819 pp = ps.freePaths;
6820 ps.freePaths = pp.next;
6821 while (pp.subPaths !is null) {
6822 psp = pp.subPaths;
6823 pp.subPaths = psp.next;
6824 free(psp);
6826 free(pp);
6829 // Delete all sub paths
6830 while (ps.freeSubPaths !is null) {
6831 psp = ps.freeSubPaths.next;
6832 free(ps.freeSubPaths);
6833 ps.freeSubPaths = psp;
6836 ps.npoints = 0;
6837 ps.nsegments = 0;
6839 if (ps.levels !is null) {
6840 free(ps.levels[0]);
6841 free(ps.levels);
6844 if (ps.picked !is null) free(ps.picked);
6845 if (ps.points !is null) free(ps.points);
6846 if (ps.segments !is null) free(ps.segments);
6848 free(ps);
6851 NVGpickScene* nvg__pickSceneGet (NVGContext ctx) {
6852 if (ctx.pickScene is null) ctx.pickScene = nvg__allocPickScene();
6853 return ctx.pickScene;
6857 // Applies Casteljau's algorithm to a cubic bezier for a given parameter t
6858 // points is 4 points (8 floats)
6859 // lvl1 is 3 points (6 floats)
6860 // lvl2 is 2 points (4 floats)
6861 // lvl3 is 1 point (2 floats)
6862 void nvg__casteljau (const(float)* points, float t, float* lvl1, float* lvl2, float* lvl3) {
6863 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0;
6864 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1;
6866 // Level 1
6867 lvl1[x0] = (points[x1]-points[x0])*t+points[x0];
6868 lvl1[y0] = (points[y1]-points[y0])*t+points[y0];
6870 lvl1[x1] = (points[x2]-points[x1])*t+points[x1];
6871 lvl1[y1] = (points[y2]-points[y1])*t+points[y1];
6873 lvl1[x2] = (points[x3]-points[x2])*t+points[x2];
6874 lvl1[y2] = (points[y3]-points[y2])*t+points[y2];
6876 // Level 2
6877 lvl2[x0] = (lvl1[x1]-lvl1[x0])*t+lvl1[x0];
6878 lvl2[y0] = (lvl1[y1]-lvl1[y0])*t+lvl1[y0];
6880 lvl2[x1] = (lvl1[x2]-lvl1[x1])*t+lvl1[x1];
6881 lvl2[y1] = (lvl1[y2]-lvl1[y1])*t+lvl1[y1];
6883 // Level 3
6884 lvl3[x0] = (lvl2[x1]-lvl2[x0])*t+lvl2[x0];
6885 lvl3[y0] = (lvl2[y1]-lvl2[y0])*t+lvl2[y0];
6888 // Calculates a point on a bezier at point t.
6889 void nvg__bezierEval (const(float)* points, float t, ref float[2] tpoint) {
6890 immutable float omt = 1-t;
6891 immutable float omt3 = omt*omt*omt;
6892 immutable float omt2 = omt*omt;
6893 immutable float t3 = t*t*t;
6894 immutable float t2 = t*t;
6896 tpoint.ptr[0] =
6897 points[0]*omt3+
6898 points[2]*3.0f*omt2*t+
6899 points[4]*3.0f*omt*t2+
6900 points[6]*t3;
6902 tpoint.ptr[1] =
6903 points[1]*omt3+
6904 points[3]*3.0f*omt2*t+
6905 points[5]*3.0f*omt*t2+
6906 points[7]*t3;
6909 // Splits a cubic bezier curve into two parts at point t.
6910 void nvg__splitBezier (const(float)* points, float t, float* pointsA, float* pointsB) {
6911 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0;
6912 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1;
6914 float[6] lvl1 = void;
6915 float[4] lvl2 = void;
6916 float[2] lvl3 = void;
6918 nvg__casteljau(points, t, lvl1.ptr, lvl2.ptr, lvl3.ptr);
6920 // First half
6921 pointsA[x0] = points[x0];
6922 pointsA[y0] = points[y0];
6924 pointsA[x1] = lvl1.ptr[x0];
6925 pointsA[y1] = lvl1.ptr[y0];
6927 pointsA[x2] = lvl2.ptr[x0];
6928 pointsA[y2] = lvl2.ptr[y0];
6930 pointsA[x3] = lvl3.ptr[x0];
6931 pointsA[y3] = lvl3.ptr[y0];
6933 // Second half
6934 pointsB[x0] = lvl3.ptr[x0];
6935 pointsB[y0] = lvl3.ptr[y0];
6937 pointsB[x1] = lvl2.ptr[x1];
6938 pointsB[y1] = lvl2.ptr[y1];
6940 pointsB[x2] = lvl1.ptr[x2];
6941 pointsB[y2] = lvl1.ptr[y2];
6943 pointsB[x3] = points[x3];
6944 pointsB[y3] = points[y3];
6947 // Calculates the inflection points in coordinate coord (X = 0, Y = 1) of a cubic bezier.
6948 // Appends any found inflection points to the array inflections and increments *ninflections.
6949 // So finds the parameters where dx/dt or dy/dt is 0
6950 void nvg__bezierInflections (const(float)* points, int coord, int* ninflections, float* inflections) {
6951 immutable float v0 = points[0*2+coord], v1 = points[1*2+coord], v2 = points[2*2+coord], v3 = points[3*2+coord];
6952 float[2] t = void;
6953 int nvalid = *ninflections;
6955 immutable float a = 3.0f*( -v0+3.0f*v1-3.0f*v2+v3 );
6956 immutable float b = 6.0f*( v0-2.0f*v1+v2 );
6957 immutable float c = 3.0f*( v1-v0 );
6959 float d = b*b-4.0f*a*c;
6960 if (nvg__absf(d-0.0f) < NVGPickEPS) {
6961 // Zero or one root
6962 t.ptr[0] = -b/2.0f*a;
6963 if (t.ptr[0] > NVGPickEPS && t.ptr[0] < (1.0f-NVGPickEPS)) {
6964 inflections[nvalid] = t.ptr[0];
6965 ++nvalid;
6967 } else if (d > NVGPickEPS) {
6968 // zero, one or two roots
6969 d = nvg__sqrtf(d);
6971 t.ptr[0] = (-b+d)/(2.0f*a);
6972 t.ptr[1] = (-b-d)/(2.0f*a);
6974 for (int i = 0; i < 2; ++i) {
6975 if (t.ptr[i] > NVGPickEPS && t.ptr[i] < (1.0f-NVGPickEPS)) {
6976 inflections[nvalid] = t.ptr[i];
6977 ++nvalid;
6980 } else {
6981 // zero roots
6984 *ninflections = nvalid;
6987 // Sort a small number of floats in ascending order (0 < n < 6)
6988 void nvg__smallsort (float* values, int n) {
6989 bool bSwapped = true;
6990 for (int j = 0; j < n-1 && bSwapped; ++j) {
6991 bSwapped = false;
6992 for (int i = 0; i < n-1; ++i) {
6993 if (values[i] > values[i+1]) {
6994 auto tmp = values[i];
6995 values[i] = values[i+1];
6996 values[i+1] = tmp;
7002 // Calculates the bounding rect of a given cubic bezier curve.
7003 void nvg__bezierBounds (const(float)* points, ref float[4] bounds) {
7004 float[4] inflections = void;
7005 int ninflections = 0;
7006 float[2] tpoint = void;
7008 nvg__initBounds(bounds);
7010 // Include start and end points in bounds
7011 nvg__expandBounds(bounds, &points[0], 1);
7012 nvg__expandBounds(bounds, &points[6], 1);
7014 // Calculate dx==0 and dy==0 inflection points and add them to the bounds
7016 nvg__bezierInflections(points, 0, &ninflections, inflections.ptr);
7017 nvg__bezierInflections(points, 1, &ninflections, inflections.ptr);
7019 foreach (immutable int i; 0..ninflections) {
7020 nvg__bezierEval(points, inflections[i], tpoint);
7021 nvg__expandBounds(bounds, tpoint.ptr, 1);
7025 // Checks to see if a line originating from x,y along the +ve x axis
7026 // intersects the given line (points[0],points[1]) -> (points[2], points[3]).
7027 // Returns `true` on intersection.
7028 // Horizontal lines are never hit.
7029 bool nvg__intersectLine (const(float)* points, float x, float y) {
7030 immutable float x1 = points[0];
7031 immutable float y1 = points[1];
7032 immutable float x2 = points[2];
7033 immutable float y2 = points[3];
7034 immutable float d = y2-y1;
7035 if (d > NVGPickEPS || d < -NVGPickEPS) {
7036 immutable float s = (x2-x1)/d;
7037 immutable float lineX = x1+(y-y1)*s;
7038 return (lineX > x);
7039 } else {
7040 return false;
7044 // Checks to see if a line originating from x,y along the +ve x axis intersects the given bezier.
7045 // It is assumed that the line originates from within the bounding box of
7046 // the bezier and that the curve has no dy=0 inflection points.
7047 // Returns the number of intersections found (which is either 1 or 0).
7048 int nvg__intersectBezier (const(float)* points, float x, float y) {
7049 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0];
7050 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1];
7052 if (y0 == y1 && y1 == y2 && y2 == y3) return 0;
7054 // Initial t guess
7055 float t = void;
7056 if (y3 != y0) t = (y-y0)/(y3-y0);
7057 else if (x3 != x0) t = (x-x0)/(x3-x0);
7058 else t = 0.5f;
7060 // A few Newton iterations
7061 for (int iter = 0; iter < 6; ++iter) {
7062 immutable float omt = 1-t;
7063 immutable float omt2 = omt*omt;
7064 immutable float t2 = t*t;
7065 immutable float omt3 = omt2*omt;
7066 immutable float t3 = t2*t;
7068 immutable float ty = y0*omt3 +
7069 y1*3.0f*omt2*t +
7070 y2*3.0f*omt*t2 +
7071 y3*t3;
7073 // Newton iteration
7074 immutable float dty = 3.0f*omt2*(y1-y0) +
7075 6.0f*omt*t*(y2-y1) +
7076 3.0f*t2*(y3-y2);
7078 // dty will never == 0 since:
7079 // Either omt, omt2 are zero OR t2 is zero
7080 // y0 != y1 != y2 != y3 (checked above)
7081 t = t-(ty-y)/dty;
7085 immutable float omt = 1-t;
7086 immutable float omt2 = omt*omt;
7087 immutable float t2 = t*t;
7088 immutable float omt3 = omt2*omt;
7089 immutable float t3 = t2*t;
7091 immutable float tx =
7092 x0*omt3+
7093 x1*3.0f*omt2*t+
7094 x2*3.0f*omt*t2+
7095 x3*t3;
7097 return (tx > x ? 1 : 0);
7101 // Finds the closest point on a line to a given point
7102 void nvg__closestLine (const(float)* points, float x, float y, float* closest, float* ot) {
7103 immutable float x1 = points[0];
7104 immutable float y1 = points[1];
7105 immutable float x2 = points[2];
7106 immutable float y2 = points[3];
7107 immutable float pqx = x2-x1;
7108 immutable float pqz = y2-y1;
7109 immutable float dx = x-x1;
7110 immutable float dz = y-y1;
7111 immutable float d = pqx*pqx+pqz*pqz;
7112 float t = pqx*dx+pqz*dz;
7113 if (d > 0) t /= d;
7114 if (t < 0) t = 0; else if (t > 1) t = 1;
7115 closest[0] = x1+t*pqx;
7116 closest[1] = y1+t*pqz;
7117 *ot = t;
7120 // Finds the closest point on a curve for a given point (x,y).
7121 // Assumes that the curve has no dx==0 or dy==0 inflection points.
7122 void nvg__closestBezier (const(float)* points, float x, float y, float* closest, float *ot) {
7123 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0];
7124 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1];
7126 // This assumes that the curve has no dy=0 inflection points.
7128 // Initial t guess
7129 float t = 0.5f;
7131 // A few Newton iterations
7132 for (int iter = 0; iter < 6; ++iter) {
7133 immutable float omt = 1-t;
7134 immutable float omt2 = omt*omt;
7135 immutable float t2 = t*t;
7136 immutable float omt3 = omt2*omt;
7137 immutable float t3 = t2*t;
7139 immutable float ty =
7140 y0*omt3+
7141 y1*3.0f*omt2*t+
7142 y2*3.0f*omt*t2+
7143 y3*t3;
7145 immutable float tx =
7146 x0*omt3+
7147 x1*3.0f*omt2*t+
7148 x2*3.0f*omt*t2+
7149 x3*t3;
7151 // Newton iteration
7152 immutable float dty =
7153 3.0f*omt2*(y1-y0)+
7154 6.0f*omt*t*(y2-y1)+
7155 3.0f*t2*(y3-y2);
7157 immutable float ddty =
7158 6.0f*omt*(y2-2.0f*y1+y0)+
7159 6.0f*t*(y3-2.0f*y2+y1);
7161 immutable float dtx =
7162 3.0f*omt2*(x1-x0)+
7163 6.0f*omt*t*(x2-x1)+
7164 3.0f*t2*(x3-x2);
7166 immutable float ddtx =
7167 6.0f*omt*(x2-2.0f*x1+x0)+
7168 6.0f*t*(x3-2.0f*x2+x1);
7170 immutable float errorx = tx-x;
7171 immutable float errory = ty-y;
7173 immutable float n = errorx*dtx+errory*dty;
7174 if (n == 0) break;
7176 immutable float d = dtx*dtx+dty*dty+errorx*ddtx+errory*ddty;
7177 if (d != 0) t = t-n/d; else break;
7180 t = nvg__max(0, nvg__min(1.0, t));
7181 *ot = t;
7183 immutable float omt = 1-t;
7184 immutable float omt2 = omt*omt;
7185 immutable float t2 = t*t;
7186 immutable float omt3 = omt2*omt;
7187 immutable float t3 = t2*t;
7189 immutable float ty =
7190 y0*omt3+
7191 y1*3.0f*omt2*t+
7192 y2*3.0f*omt*t2+
7193 y3*t3;
7195 immutable float tx =
7196 x0*omt3+
7197 x1*3.0f*omt2*t+
7198 x2*3.0f*omt*t2+
7199 x3*t3;
7201 closest[0] = tx;
7202 closest[1] = ty;
7206 // Returns:
7207 // 1 If (x,y) is contained by the stroke of the path
7208 // 0 If (x,y) is not contained by the path.
7209 int nvg__pickSubPathStroke (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, float strokeWidth, int lineCap, int lineJoin) {
7210 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0;
7211 if (psp.firstSegment == -1) return 0;
7213 float[2] closest = void;
7214 float[2] d = void;
7215 float t = void;
7217 // trace a line from x,y out along the positive x axis and count the number of intersections
7218 int nsegments = psp.nsegments;
7219 const(NVGsegment)* seg = ps.segments+psp.firstSegment;
7220 const(NVGsegment)* prevseg = (psp.closed ? &ps.segments[psp.firstSegment+nsegments-1] : null);
7221 immutable float strokeWidthSqd = strokeWidth*strokeWidth;
7223 for (int s = 0; s < nsegments; ++s, prevseg = seg, ++seg) {
7224 if (nvg__pointInBounds(x, y, seg.bounds)) {
7225 // Line potentially hits stroke.
7226 switch (seg.type) {
7227 case Command.LineTo:
7228 nvg__closestLine(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t);
7229 break;
7230 case Command.BezierTo:
7231 nvg__closestBezier(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t);
7232 break;
7233 default:
7234 continue;
7237 d.ptr[0] = x-closest.ptr[0];
7238 d.ptr[1] = y-closest.ptr[1];
7240 if ((t >= NVGPickEPS && t <= 1.0f-NVGPickEPS) ||
7241 (seg.flags&(NVGSegmentFlags.Corner|NVGSegmentFlags.Cap|NVGSegmentFlags.Endcap)) == 0 ||
7242 (lineJoin == NVGLineCap.Round))
7244 // Closest point is in the middle of the line/curve, at a rounded join/cap
7245 // or at a smooth join
7246 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7247 if (distSqd < strokeWidthSqd) return 1;
7248 } else if ((t > 1.0f-NVGPickEPS && (seg.flags&NVGSegmentFlags.Endcap)) ||
7249 (t < NVGPickEPS && (seg.flags&NVGSegmentFlags.Cap))) {
7250 switch (lineCap) {
7251 case NVGLineCap.Butt:
7252 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7253 immutable float dirD = (t < NVGPickEPS ?
7254 -(d.ptr[0]*seg.startDir.ptr[0]+d.ptr[1]*seg.startDir.ptr[1]) :
7255 d.ptr[0]*seg.endDir.ptr[0]+d.ptr[1]*seg.endDir.ptr[1]);
7256 if (dirD < -NVGPickEPS && distSqd < strokeWidthSqd) return 1;
7257 break;
7258 case NVGLineCap.Square:
7259 if (nvg__absf(d.ptr[0]) < strokeWidth && nvg__absf(d.ptr[1]) < strokeWidth) return 1;
7260 break;
7261 case NVGLineCap.Round:
7262 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7263 if (distSqd < strokeWidthSqd) return 1;
7264 break;
7265 default:
7266 break;
7268 } else if (seg.flags&NVGSegmentFlags.Corner) {
7269 // Closest point is at a corner
7270 const(NVGsegment)* seg0, seg1;
7272 if (t < NVGPickEPS) {
7273 seg0 = prevseg;
7274 seg1 = seg;
7275 } else {
7276 seg0 = seg;
7277 seg1 = (s == nsegments-1 ? &ps.segments[psp.firstSegment] : seg+1);
7280 if (!(seg1.flags&NVGSegmentFlags.Bevel)) {
7281 immutable float prevNDist = -seg0.endDir.ptr[1]*d.ptr[0]+seg0.endDir.ptr[0]*d.ptr[1];
7282 immutable float curNDist = seg1.startDir.ptr[1]*d.ptr[0]-seg1.startDir.ptr[0]*d.ptr[1];
7283 if (nvg__absf(prevNDist) < strokeWidth && nvg__absf(curNDist) < strokeWidth) return 1;
7284 } else {
7285 d.ptr[0] -= -seg1.startDir.ptr[1]*strokeWidth;
7286 d.ptr[1] -= +seg1.startDir.ptr[0]*strokeWidth;
7287 if (seg1.miterDir.ptr[0]*d.ptr[0]+seg1.miterDir.ptr[1]*d.ptr[1] < 0) return 1;
7293 return 0;
7296 // Returns:
7297 // 1 If (x,y) is contained by the path and the path is solid.
7298 // -1 If (x,y) is contained by the path and the path is a hole.
7299 // 0 If (x,y) is not contained by the path.
7300 int nvg__pickSubPath (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, bool evenOddMode) {
7301 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0;
7302 if (psp.firstSegment == -1) return 0;
7304 const(NVGsegment)* seg = &ps.segments[psp.firstSegment];
7305 int nsegments = psp.nsegments;
7306 int nintersections = 0;
7308 // trace a line from x,y out along the positive x axis and count the number of intersections
7309 for (int s = 0; s < nsegments; ++s, ++seg) {
7310 if ((seg.bounds.ptr[1]-NVGPickEPS) < y &&
7311 (seg.bounds.ptr[3]-NVGPickEPS) > y &&
7312 seg.bounds.ptr[2] > x)
7314 // Line hits the box.
7315 switch (seg.type) {
7316 case Command.LineTo:
7317 if (seg.bounds.ptr[0] > x) {
7318 // line originates outside the box
7319 ++nintersections;
7320 } else {
7321 // line originates inside the box
7322 nintersections += nvg__intersectLine(&ps.points[seg.firstPoint*2], x, y);
7324 break;
7325 case Command.BezierTo:
7326 if (seg.bounds.ptr[0] > x) {
7327 // line originates outside the box
7328 ++nintersections;
7329 } else {
7330 // line originates inside the box
7331 nintersections += nvg__intersectBezier(&ps.points[seg.firstPoint*2], x, y);
7333 break;
7334 default:
7335 break;
7340 if (evenOddMode) {
7341 return nintersections;
7342 } else {
7343 return (nintersections&1 ? (psp.winding == NVGSolidity.Solid ? 1 : -1) : 0);
7347 bool nvg__pickPath (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) {
7348 int pickCount = 0;
7349 const(NVGpickSubPath)* psp = pp.subPaths;
7350 while (psp !is null) {
7351 pickCount += nvg__pickSubPath(ps, psp, x, y, pp.evenOddMode);
7352 psp = psp.next;
7354 return ((pp.evenOddMode ? pickCount&1 : pickCount) != 0);
7357 bool nvg__pickPathStroke (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) {
7358 const(NVGpickSubPath)* psp = pp.subPaths;
7359 while (psp !is null) {
7360 if (nvg__pickSubPathStroke(ps, psp, x, y, pp.strokeWidth, pp.lineCap, pp.lineJoin)) return true;
7361 psp = psp.next;
7363 return false;
7366 bool nvg__pickPathTestBounds (NVGContext ctx, const NVGpickScene* ps, const NVGpickPath* pp, float x, float y) {
7367 if (nvg__pointInBounds(x, y, pp.bounds)) {
7368 //{ import core.stdc.stdio; printf(" (0): in bounds!\n"); }
7369 if (pp.flags&NVGPathFlags.Scissor) {
7370 const(float)* scissor = &ps.points[pp.scissor*2];
7371 // untransform scissor translation
7372 float stx = void, sty = void;
7373 ctx.gpuUntransformPoint(&stx, &sty, scissor[4], scissor[5]);
7374 immutable float rx = x-stx;
7375 immutable float ry = y-sty;
7376 //{ 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]); }
7377 if (nvg__absf((scissor[0]*rx)+(scissor[1]*ry)) > scissor[6] ||
7378 nvg__absf((scissor[2]*rx)+(scissor[3]*ry)) > scissor[7])
7380 //{ import core.stdc.stdio; printf(" (1): scissor reject!\n"); }
7381 return false;
7384 return true;
7386 return false;
7389 int nvg__countBitsUsed (uint v) pure {
7390 pragma(inline, true);
7391 import core.bitop : bsr;
7392 return (v != 0 ? bsr(v)+1 : 0);
7395 void nvg__pickSceneInsert (NVGpickScene* ps, NVGpickPath* pp) {
7396 if (ps is null || pp is null) return;
7398 int[4] cellbounds;
7399 int base = ps.nlevels-1;
7400 int level;
7401 int levelwidth;
7402 int levelshift;
7403 int levelx;
7404 int levely;
7405 NVGpickPath** cell = null;
7407 // Bit tricks for inserting into an implicit quadtree.
7409 // Calc bounds of path in cells at the lowest level
7410 cellbounds.ptr[0] = cast(int)(pp.bounds.ptr[0]/ps.xdim);
7411 cellbounds.ptr[1] = cast(int)(pp.bounds.ptr[1]/ps.ydim);
7412 cellbounds.ptr[2] = cast(int)(pp.bounds.ptr[2]/ps.xdim);
7413 cellbounds.ptr[3] = cast(int)(pp.bounds.ptr[3]/ps.ydim);
7415 // Find which bits differ between the min/max x/y coords
7416 cellbounds.ptr[0] ^= cellbounds.ptr[2];
7417 cellbounds.ptr[1] ^= cellbounds.ptr[3];
7419 // Use the number of bits used (countBitsUsed(x) == sizeof(int) * 8 - clz(x);
7420 // to calculate the level to insert at (the level at which the bounds fit in a single cell)
7421 level = nvg__min(base-nvg__countBitsUsed(cellbounds.ptr[0]), base-nvg__countBitsUsed(cellbounds.ptr[1]));
7422 if (level < 0) level = 0;
7423 //{ 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]); }
7424 //level = 0;
7426 // Find the correct cell in the chosen level, clamping to the edges.
7427 levelwidth = 1<<level;
7428 levelshift = (ps.nlevels-level)-1;
7429 levelx = nvg__clamp(cellbounds.ptr[2]>>levelshift, 0, levelwidth-1);
7430 levely = nvg__clamp(cellbounds.ptr[3]>>levelshift, 0, levelwidth-1);
7432 // Insert the path into the linked list at that cell.
7433 cell = &ps.levels[level][levely*levelwidth+levelx];
7435 pp.cellnext = *cell;
7436 *cell = pp;
7438 if (ps.paths is null) ps.lastPath = pp;
7439 pp.next = ps.paths;
7440 ps.paths = pp;
7442 // Store the order (depth) of the path for picking ops.
7443 pp.order = cast(short)ps.npaths;
7444 ++ps.npaths;
7447 void nvg__pickBeginFrame (NVGContext ctx, int width, int height) {
7448 NVGpickScene* ps = nvg__pickSceneGet(ctx);
7450 //NVG_PICK_DEBUG_NEWFRAME();
7452 // Return all paths & sub paths from last frame to the free list
7453 while (ps.paths !is null) {
7454 NVGpickPath* pp = ps.paths.next;
7455 nvg__freePickPath(ps, ps.paths);
7456 ps.paths = pp;
7459 ps.paths = null;
7460 ps.npaths = 0;
7462 // Store the screen metrics for the quadtree
7463 ps.width = width;
7464 ps.height = height;
7466 immutable float lowestSubDiv = cast(float)(1<<(ps.nlevels-1));
7467 ps.xdim = cast(float)width/lowestSubDiv;
7468 ps.ydim = cast(float)height/lowestSubDiv;
7470 // Allocate the quadtree if required.
7471 if (ps.levels is null) {
7472 int ncells = 1;
7474 ps.levels = cast(NVGpickPath***)malloc((NVGpickPath**).sizeof*ps.nlevels);
7475 for (int l = 0; l < ps.nlevels; ++l) {
7476 int leveldim = 1<<l;
7477 ncells += leveldim*leveldim;
7480 ps.levels[0] = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ncells);
7482 int cell = 1;
7483 for (int l = 1; l < ps.nlevels; ++l) {
7484 ps.levels[l] = &ps.levels[0][cell];
7485 int leveldim = 1<<l;
7486 cell += leveldim*leveldim;
7489 ps.ncells = ncells;
7491 memset(ps.levels[0], 0, ps.ncells*(NVGpickPath*).sizeof);
7493 // Allocate temporary storage for nvgHitTestAll results if required.
7494 if (ps.picked is null) {
7495 ps.cpicked = 16;
7496 ps.picked = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ps.cpicked);
7499 ps.npoints = 0;
7500 ps.nsegments = 0;
7502 } // nothrow @trusted @nogc
7505 /// Return outline of the current path. Returned outline is not flattened.
7506 /// Group: paths
7507 public NVGPathOutline getCurrPathOutline (NVGContext ctx) nothrow @trusted @nogc {
7508 if (ctx is null || !ctx.contextAlive || ctx.ncommands == 0) return NVGPathOutline.init;
7510 auto res = NVGPathOutline.createNew();
7512 const(float)[] acommands = ctx.commands[0..ctx.ncommands];
7513 int ncommands = cast(int)acommands.length;
7514 const(float)* commands = acommands.ptr;
7516 float cx = 0, cy = 0;
7517 float[2] start = void;
7518 float[4] totalBounds = [float.max, float.max, -float.max, -float.max];
7519 float[8] bcp = void; // bezier curve points; used to calculate bounds
7521 void addToBounds (in float x, in float y) nothrow @trusted @nogc {
7522 totalBounds.ptr[0] = nvg__min(totalBounds.ptr[0], x);
7523 totalBounds.ptr[1] = nvg__min(totalBounds.ptr[1], y);
7524 totalBounds.ptr[2] = nvg__max(totalBounds.ptr[2], x);
7525 totalBounds.ptr[3] = nvg__max(totalBounds.ptr[3], y);
7528 bool hasPoints = false;
7530 void closeIt () nothrow @trusted @nogc {
7531 if (!hasPoints) return;
7532 if (cx != start.ptr[0] || cy != start.ptr[1]) {
7533 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo);
7534 res.ds.putArgs(start[]);
7535 cx = start.ptr[0];
7536 cy = start.ptr[1];
7537 addToBounds(cx, cy);
7541 int i = 0;
7542 while (i < ncommands) {
7543 int cmd = cast(int)commands[i++];
7544 switch (cmd) {
7545 case Command.MoveTo: // one coordinate pair
7546 const(float)* tfxy = commands+i;
7547 i += 2;
7548 // add command
7549 res.ds.putCommand(NVGPathOutline.Command.Kind.MoveTo);
7550 res.ds.putArgs(tfxy[0..2]);
7551 // new starting point
7552 start.ptr[0..2] = tfxy[0..2];
7553 cx = tfxy[0];
7554 cy = tfxy[0];
7555 addToBounds(cx, cy);
7556 hasPoints = true;
7557 break;
7558 case Command.LineTo: // one coordinate pair
7559 const(float)* tfxy = commands+i;
7560 i += 2;
7561 // add command
7562 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo);
7563 res.ds.putArgs(tfxy[0..2]);
7564 cx = tfxy[0];
7565 cy = tfxy[0];
7566 addToBounds(cx, cy);
7567 hasPoints = true;
7568 break;
7569 case Command.BezierTo: // three coordinate pairs
7570 const(float)* tfxy = commands+i;
7571 i += 3*2;
7572 // add command
7573 res.ds.putCommand(NVGPathOutline.Command.Kind.BezierTo);
7574 res.ds.putArgs(tfxy[0..6]);
7575 // bounds
7576 bcp.ptr[0] = cx;
7577 bcp.ptr[1] = cy;
7578 bcp.ptr[2..8] = tfxy[0..6];
7579 nvg__bezierBounds(bcp.ptr, totalBounds);
7580 cx = tfxy[4];
7581 cy = tfxy[5];
7582 hasPoints = true;
7583 break;
7584 case Command.Close:
7585 closeIt();
7586 hasPoints = false;
7587 break;
7588 case Command.Winding:
7589 //psp.winding = cast(short)cast(int)commands[i];
7590 i += 1;
7591 break;
7592 default:
7593 break;
7597 res.ds.bounds[] = totalBounds[];
7598 return res;
7602 // ////////////////////////////////////////////////////////////////////////// //
7603 // Text
7605 /** Creates font by loading it from the disk from specified file name.
7606 * Returns handle to the font or FONS_INVALID (aka -1) on error.
7607 * Use "fontname:noaa" as [name] to turn off antialiasing (if font driver supports that).
7609 * On POSIX systems it is possible to use fontconfig font names too.
7610 * `:noaa` in font path is still allowed, but it must be the last option.
7612 * Group: text_api
7614 public int createFont (NVGContext ctx, const(char)[] name, const(char)[] path) nothrow @trusted {
7615 return ctx.fs.addFont(name, path, ctx.params.fontAA);
7618 /** Creates font by loading it from the specified memory chunk.
7619 * Returns handle to the font or FONS_INVALID (aka -1) on error.
7620 * Won't free data on error.
7622 * Group: text_api
7624 public int createFontMem (NVGContext ctx, const(char)[] name, ubyte* data, int ndata, bool freeData) nothrow @trusted @nogc {
7625 return ctx.fs.addFontMem(name, data, ndata, freeData, ctx.params.fontAA);
7628 /// Add fonts from another context.
7629 /// This is more effective than reloading fonts, 'cause font data will be shared.
7630 /// Group: text_api
7631 public void addFontsFrom (NVGContext ctx, NVGContext source) nothrow @trusted @nogc {
7632 if (ctx is null || source is null) return;
7633 ctx.fs.addFontsFrom(source.fs);
7636 /// Finds a loaded font of specified name, and returns handle to it, or FONS_INVALID (aka -1) if the font is not found.
7637 /// Group: text_api
7638 public int findFont (NVGContext ctx, const(char)[] name) nothrow @trusted @nogc {
7639 pragma(inline, true);
7640 return (name.length == 0 ? FONS_INVALID : ctx.fs.getFontByName(name));
7643 /// Sets the font size of current text style.
7644 /// Group: text_api
7645 public void fontSize (NVGContext ctx, float size) nothrow @trusted @nogc {
7646 pragma(inline, true);
7647 nvg__getState(ctx).fontSize = size;
7650 /// Gets the font size of current text style.
7651 /// Group: text_api
7652 public float fontSize (NVGContext ctx) nothrow @trusted @nogc {
7653 pragma(inline, true);
7654 return nvg__getState(ctx).fontSize;
7657 /// Sets the blur of current text style.
7658 /// Group: text_api
7659 public void fontBlur (NVGContext ctx, float blur) nothrow @trusted @nogc {
7660 pragma(inline, true);
7661 nvg__getState(ctx).fontBlur = blur;
7664 /// Gets the blur of current text style.
7665 /// Group: text_api
7666 public float fontBlur (NVGContext ctx) nothrow @trusted @nogc {
7667 pragma(inline, true);
7668 return nvg__getState(ctx).fontBlur;
7671 /// Sets the letter spacing of current text style.
7672 /// Group: text_api
7673 public void textLetterSpacing (NVGContext ctx, float spacing) nothrow @trusted @nogc {
7674 pragma(inline, true);
7675 nvg__getState(ctx).letterSpacing = spacing;
7678 /// Gets the letter spacing of current text style.
7679 /// Group: text_api
7680 public float textLetterSpacing (NVGContext ctx) nothrow @trusted @nogc {
7681 pragma(inline, true);
7682 return nvg__getState(ctx).letterSpacing;
7685 /// Sets the proportional line height of current text style. The line height is specified as multiple of font size.
7686 /// Group: text_api
7687 public void textLineHeight (NVGContext ctx, float lineHeight) nothrow @trusted @nogc {
7688 pragma(inline, true);
7689 nvg__getState(ctx).lineHeight = lineHeight;
7692 /// Gets the proportional line height of current text style. The line height is specified as multiple of font size.
7693 /// Group: text_api
7694 public float textLineHeight (NVGContext ctx) nothrow @trusted @nogc {
7695 pragma(inline, true);
7696 return nvg__getState(ctx).lineHeight;
7699 /// Sets the text align of current text style, see [NVGTextAlign] for options.
7700 /// Group: text_api
7701 public void textAlign (NVGContext ctx, NVGTextAlign talign) nothrow @trusted @nogc {
7702 pragma(inline, true);
7703 nvg__getState(ctx).textAlign = talign;
7706 /// Ditto.
7707 public void textAlign (NVGContext ctx, NVGTextAlign.H h) nothrow @trusted @nogc {
7708 pragma(inline, true);
7709 nvg__getState(ctx).textAlign.horizontal = h;
7712 /// Ditto.
7713 public void textAlign (NVGContext ctx, NVGTextAlign.V v) nothrow @trusted @nogc {
7714 pragma(inline, true);
7715 nvg__getState(ctx).textAlign.vertical = v;
7718 /// Ditto.
7719 public void textAlign (NVGContext ctx, NVGTextAlign.H h, NVGTextAlign.V v) nothrow @trusted @nogc {
7720 pragma(inline, true);
7721 nvg__getState(ctx).textAlign.reset(h, v);
7724 /// Ditto.
7725 public void textAlign (NVGContext ctx, NVGTextAlign.V v, NVGTextAlign.H h) nothrow @trusted @nogc {
7726 pragma(inline, true);
7727 nvg__getState(ctx).textAlign.reset(h, v);
7730 /// Gets the text align of current text style, see [NVGTextAlign] for options.
7731 /// Group: text_api
7732 public NVGTextAlign textAlign (NVGContext ctx) nothrow @trusted @nogc {
7733 pragma(inline, true);
7734 return nvg__getState(ctx).textAlign;
7737 /// Sets the font face based on specified id of current text style.
7738 /// Group: text_api
7739 public void fontFaceId (NVGContext ctx, int font) nothrow @trusted @nogc {
7740 pragma(inline, true);
7741 nvg__getState(ctx).fontId = font;
7744 /// Gets the font face based on specified id of current text style.
7745 /// Group: text_api
7746 public int fontFaceId (NVGContext ctx) nothrow @trusted @nogc {
7747 pragma(inline, true);
7748 return nvg__getState(ctx).fontId;
7751 /** Sets the font face based on specified name of current text style.
7753 * The underlying implementation is using O(1) data structure to lookup
7754 * font names, so you probably should use this function instead of [fontFaceId]
7755 * to make your code more robust and less error-prone.
7757 * Group: text_api
7759 public void fontFace (NVGContext ctx, const(char)[] font) nothrow @trusted @nogc {
7760 pragma(inline, true);
7761 nvg__getState(ctx).fontId = ctx.fs.getFontByName(font);
7764 static if (is(typeof(&fons__nvg__toPath))) {
7765 public enum NanoVegaHasCharToPath = true; ///
7766 } else {
7767 public enum NanoVegaHasCharToPath = false; ///
7770 /// Adds glyph outlines to the current path. Vertical 0 is baseline.
7771 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead.
7772 /// Returns `false` if there is no such glyph, or current font is not scalable.
7773 /// Group: text_api
7774 public bool charToPath (NVGContext ctx, dchar dch, float[] bounds=null) nothrow @trusted @nogc {
7775 NVGstate* state = nvg__getState(ctx);
7776 ctx.fs.fontId = state.fontId;
7777 return ctx.fs.toPath(ctx, dch, bounds);
7780 static if (is(typeof(&fons__nvg__bounds))) {
7781 public enum NanoVegaHasCharPathBounds = true; ///
7782 } else {
7783 public enum NanoVegaHasCharPathBounds = false; ///
7786 /// Returns bounds of the glyph outlines. Vertical 0 is baseline.
7787 /// The glyph is not scaled in any way.
7788 /// Returns `false` if there is no such glyph, or current font is not scalable.
7789 /// Group: text_api
7790 public bool charPathBounds (NVGContext ctx, dchar dch, float[] bounds) nothrow @trusted @nogc {
7791 NVGstate* state = nvg__getState(ctx);
7792 ctx.fs.fontId = state.fontId;
7793 return ctx.fs.getPathBounds(dch, bounds);
7796 /** [charOutline] will return [NVGPathOutline].
7798 some usage samples:
7801 float[4] bounds = void;
7803 nvg.scale(0.5, 0.5);
7804 nvg.translate(500, 800);
7805 nvg.evenOddFill;
7807 nvg.newPath();
7808 nvg.charToPath('&', bounds[]);
7809 conwriteln(bounds[]);
7810 nvg.fillPaint(nvg.linearGradient(0, 0, 600, 600, NVGColor("#f70"), NVGColor("#ff0")));
7811 nvg.strokeColor(NVGColor("#0f0"));
7812 nvg.strokeWidth = 3;
7813 nvg.fill();
7814 nvg.stroke();
7815 // glyph bounds
7816 nvg.newPath();
7817 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
7818 nvg.strokeColor(NVGColor("#00f"));
7819 nvg.stroke();
7821 nvg.newPath();
7822 nvg.charToPath('g', bounds[]);
7823 conwriteln(bounds[]);
7824 nvg.fill();
7825 nvg.strokeColor(NVGColor("#0f0"));
7826 nvg.stroke();
7827 // glyph bounds
7828 nvg.newPath();
7829 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
7830 nvg.strokeColor(NVGColor("#00f"));
7831 nvg.stroke();
7833 nvg.newPath();
7834 nvg.moveTo(0, 0);
7835 nvg.lineTo(600, 0);
7836 nvg.strokeColor(NVGColor("#0ff"));
7837 nvg.stroke();
7839 if (auto ol = nvg.charOutline('Q')) {
7840 scope(exit) ol.kill();
7841 nvg.newPath();
7842 conwriteln("==== length: ", ol.length, " ====");
7843 foreach (const ref cmd; ol.commands) {
7844 //conwriteln(" ", cmd.code, ": ", cmd.args[]);
7845 assert(cmd.valid);
7846 final switch (cmd.code) {
7847 case cmd.Kind.MoveTo: nvg.moveTo(cmd.args[0], cmd.args[1]); break;
7848 case cmd.Kind.LineTo: nvg.lineTo(cmd.args[0], cmd.args[1]); break;
7849 case cmd.Kind.QuadTo: nvg.quadTo(cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]); break;
7850 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;
7853 nvg.strokeColor(NVGColor("#f00"));
7854 nvg.stroke();
7858 Group: text_api
7860 public struct NVGPathOutline {
7861 private nothrow @trusted @nogc:
7862 struct DataStore {
7863 uint rc; // refcount
7864 ubyte* data;
7865 uint used;
7866 uint size;
7867 uint ccount; // number of commands
7868 float[4] bounds = 0; /// outline bounds
7869 nothrow @trusted @nogc:
7870 void putBytes (const(void)[] b) {
7871 if (b.length == 0) return;
7872 if (b.length >= int.max/8) assert(0, "NanoVega: out of memory");
7873 if (int.max/8-used < b.length) assert(0, "NanoVega: out of memory");
7874 if (used+cast(uint)b.length > size) {
7875 import core.stdc.stdlib : realloc;
7876 uint newsz = size;
7877 while (newsz < used+cast(uint)b.length) newsz = (newsz == 0 ? 1024 : newsz < 32768 ? newsz*2 : newsz+8192);
7878 assert(used+cast(uint)b.length <= newsz);
7879 data = cast(ubyte*)realloc(data, newsz);
7880 if (data is null) assert(0, "NanoVega: out of memory");
7881 size = newsz;
7883 import core.stdc.string : memcpy;
7884 memcpy(data+used, b.ptr, b.length);
7885 used += cast(uint)b.length;
7887 void putCommand (ubyte cmd) { pragma(inline, true); ++ccount; putBytes((&cmd)[0..1]); }
7888 void putArgs (const(float)[] f...) { pragma(inline, true); putBytes(f[]); }
7891 static void incRef (DataStore* ds) {
7892 pragma(inline, true);
7893 if (ds !is null) {
7894 ++ds.rc;
7895 //{ import core.stdc.stdio; printf("ods(%p): incref: newrc=%u\n", ds, ds.rc); }
7899 static void decRef (DataStore* ds) {
7900 version(aliced) pragma(inline, true);
7901 if (ds !is null) {
7902 //{ import core.stdc.stdio; printf("ods(%p): decref: newrc=%u\n", ds, ds.rc-1); }
7903 if (--ds.rc == 0) {
7904 import core.stdc.stdlib : free;
7905 import core.stdc.string : memset;
7906 if (ds.data !is null) free(ds.data);
7907 memset(ds, 0, DataStore.sizeof); // just in case
7908 free(ds);
7909 //{ import core.stdc.stdio; printf(" ods(%p): killed.\n"); }
7914 private:
7915 static NVGPathOutline createNew () {
7916 import core.stdc.stdlib : malloc;
7917 import core.stdc.string : memset;
7918 auto ds = cast(DataStore*)malloc(DataStore.sizeof);
7919 if (ds is null) assert(0, "NanoVega: out of memory");
7920 memset(ds, 0, DataStore.sizeof);
7921 ds.rc = 1;
7922 NVGPathOutline res;
7923 res.dsaddr = cast(usize)ds;
7924 return res;
7927 private:
7928 usize dsaddr; // fool GC
7930 @property inout(DataStore)* ds () inout pure { pragma(inline, true); return cast(DataStore*)dsaddr; }
7932 public:
7933 /// commands
7934 static struct Command {
7936 enum Kind : ubyte {
7937 MoveTo, ///
7938 LineTo, ///
7939 QuadTo, ///
7940 BezierTo, ///
7941 End, /// no more commands (this command is not `valid`!)
7944 Kind code; ///
7945 const(float)[] args; ///
7946 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (code >= Kind.min && code < Kind.End && args.length >= 2); } ///
7948 static uint arglen (Kind code) pure nothrow @safe @nogc {
7949 pragma(inline, true);
7950 return
7951 code == Kind.MoveTo || code == Kind.LineTo ? 2 :
7952 code == Kind.QuadTo ? 4 :
7953 code == Kind.BezierTo ? 6 :
7957 /// perform NanoVega command with stored data.
7958 void perform (NVGContext ctx) const nothrow @trusted @nogc {
7959 if (ctx is null) return;
7960 final switch (code) {
7961 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(args.ptr[0..2]); break;
7962 case Kind.LineTo: if (args.length > 1) ctx.lineTo(args.ptr[0..2]); break;
7963 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(args.ptr[0..4]); break;
7964 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(args.ptr[0..6]); break;
7965 case Kind.End: break;
7969 /// perform NanoVega command with stored data, transforming points with [xform] transformation matrix.
7970 void perform() (NVGContext ctx, in auto ref NVGMatrix xform) const nothrow @trusted @nogc {
7971 if (ctx is null || !valid) return;
7972 float[6] pts = void;
7973 pts[0..args.length] = args[];
7974 foreach (immutable pidx; 0..args.length/2) xform.point(pts.ptr[pidx*2+0], pts.ptr[pidx*2+1]);
7975 final switch (code) {
7976 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(pts.ptr[0..2]); break;
7977 case Kind.LineTo: if (args.length > 1) ctx.lineTo(pts.ptr[0..2]); break;
7978 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(pts.ptr[0..4]); break;
7979 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(pts.ptr[0..6]); break;
7980 case Kind.End: break;
7985 public:
7986 /// Create new path with quadratic bezier (first command is MoveTo, second command is QuadTo).
7987 static NVGPathOutline createNewQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) {
7988 auto res = createNew();
7989 res.ds.putCommand(Command.Kind.MoveTo);
7990 res.ds.putArgs(x0, y0);
7991 res.ds.putCommand(Command.Kind.QuadTo);
7992 res.ds.putArgs(cx, cy, x, y);
7993 return res;
7996 /// Create new path with cubic bezier (first command is MoveTo, second command is BezierTo).
7997 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) {
7998 auto res = createNew();
7999 res.ds.putCommand(Command.Kind.MoveTo);
8000 res.ds.putArgs(x1, y1);
8001 res.ds.putCommand(Command.Kind.BezierTo);
8002 res.ds.putArgs(x2, y2, x3, y3, x4, y4);
8003 return res;
8006 public:
8007 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); }
8008 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); }
8010 void opAssign() (in auto ref NVGPathOutline a) {
8011 incRef(cast(DataStore*)a.dsaddr);
8012 decRef(cast(DataStore*)dsaddr);
8013 dsaddr = a.dsaddr;
8016 /// Clear storage.
8017 void clear () {
8018 pragma(inline, true);
8019 decRef(ds);
8020 dsaddr = 0;
8023 /// Is this outline empty?
8024 @property empty () const pure { pragma(inline, true); return (dsaddr == 0 || ds.ccount == 0); }
8026 /// Returns number of commands in outline.
8027 @property int length () const pure { pragma(inline, true); return (dsaddr ? ds.ccount : 0); }
8029 /// Returns "flattened" path. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8030 NVGPathOutline flatten () const { pragma(inline, true); return flattenInternal(null); }
8032 /// Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8033 NVGPathOutline flatten() (in auto ref NVGMatrix mt) const { pragma(inline, true); return flattenInternal(&mt); }
8035 // Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8036 private NVGPathOutline flattenInternal (scope NVGMatrix* tfm) const {
8037 import core.stdc.string : memset;
8039 NVGPathOutline res;
8040 if (dsaddr == 0 || ds.ccount == 0) { res = this; return res; } // nothing to do
8042 // check if we need to flatten the path
8043 if (tfm is null) {
8044 bool dowork = false;
8045 foreach (const ref cs; commands) {
8046 if (cs.code != Command.Kind.MoveTo && cs.code != Command.Kind.LineTo) {
8047 dowork = true;
8048 break;
8051 if (!dowork) { res = this; return res; } // nothing to do
8054 NVGcontextinternal ctx;
8055 memset(&ctx, 0, ctx.sizeof);
8056 ctx.cache = nvg__allocPathCache();
8057 scope(exit) {
8058 import core.stdc.stdlib : free;
8059 nvg__deletePathCache(ctx.cache);
8062 ctx.tessTol = 0.25f;
8063 ctx.angleTol = 0; // 0 -- angle tolerance for McSeem Bezier rasterizer
8064 ctx.cuspLimit = 0; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps)
8065 ctx.distTol = 0.01f;
8066 ctx.tesselatortype = NVGTesselation.DeCasteljau;
8068 nvg__addPath(&ctx); // we need this for `nvg__addPoint()`
8070 // has some curves or transformations, convert path
8071 res = createNew();
8072 float[8] args = void;
8074 res.ds.bounds = [float.max, float.max, -float.max, -float.max];
8076 float lastX = float.max, lastY = float.max;
8077 bool lastWasMove = false;
8079 void addPoint (float x, float y, Command.Kind cmd=Command.Kind.LineTo) nothrow @trusted @nogc {
8080 if (tfm !is null) tfm.point(x, y);
8081 bool isMove = (cmd == Command.Kind.MoveTo);
8082 if (isMove) {
8083 // moveto
8084 if (lastWasMove && nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return;
8085 } else {
8086 // lineto
8087 if (nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return;
8089 lastWasMove = isMove;
8090 lastX = x;
8091 lastY = y;
8092 res.ds.putCommand(cmd);
8093 res.ds.putArgs(x, y);
8094 res.ds.bounds.ptr[0] = nvg__min(res.ds.bounds.ptr[0], x);
8095 res.ds.bounds.ptr[1] = nvg__min(res.ds.bounds.ptr[1], y);
8096 res.ds.bounds.ptr[2] = nvg__max(res.ds.bounds.ptr[2], x);
8097 res.ds.bounds.ptr[3] = nvg__max(res.ds.bounds.ptr[3], y);
8100 // sorry for this pasta
8101 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 {
8102 ctx.cache.npoints = 0;
8103 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) {
8104 nvg__tesselateBezier(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner);
8105 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) {
8106 nvg__tesselateBezierMcSeem(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner);
8107 } else {
8108 nvg__tesselateBezierAFD(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, PointFlag.Corner);
8110 // add generated points
8111 foreach (const ref pt; ctx.cache.points[0..ctx.cache.npoints]) addPoint(pt.x, pt.y);
8114 void flattenQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) {
8115 flattenBezier(
8116 x0, y0,
8117 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
8118 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
8119 x, y,
8124 float cx = 0, cy = 0;
8125 foreach (const ref cs; commands) {
8126 switch (cs.code) {
8127 case Command.Kind.LineTo:
8128 case Command.Kind.MoveTo:
8129 addPoint(cs.args[0], cs.args[1], cs.code);
8130 cx = cs.args[0];
8131 cy = cs.args[1];
8132 break;
8133 case Command.Kind.QuadTo:
8134 flattenQuad(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3]);
8135 cx = cs.args[2];
8136 cy = cs.args[3];
8137 break;
8138 case Command.Kind.BezierTo:
8139 flattenBezier(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3], cs.args[4], cs.args[5], 0);
8140 cx = cs.args[4];
8141 cy = cs.args[5];
8142 break;
8143 default:
8144 break;
8148 return res;
8151 /// Returns forward range with all glyph commands.
8152 auto commands () const nothrow @trusted @nogc {
8153 static struct Range {
8154 private nothrow @trusted @nogc:
8155 usize dsaddr;
8156 uint cpos; // current position in data
8157 uint cleft; // number of commands left
8158 @property const(ubyte)* data () inout pure { pragma(inline, true); return (dsaddr ? (cast(DataStore*)dsaddr).data : null); }
8159 public:
8160 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); }
8161 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); }
8162 void opAssign() (in auto ref Range a) {
8163 incRef(cast(DataStore*)a.dsaddr);
8164 decRef(cast(DataStore*)dsaddr);
8165 dsaddr = a.dsaddr;
8166 cpos = a.cpos;
8167 cleft = a.cleft;
8169 float[4] bounds () const pure { float[4] res = 0; pragma(inline, true); if (dsaddr) res[] = (cast(DataStore*)dsaddr).bounds[]; return res; } /// outline bounds
8170 @property bool empty () const pure { pragma(inline, true); return (cleft == 0); }
8171 @property int length () const pure { pragma(inline, true); return cleft; }
8172 @property Range save () const { pragma(inline, true); Range res = this; return res; }
8173 @property Command front () const {
8174 Command res = void;
8175 if (cleft > 0) {
8176 res.code = cast(Command.Kind)data[cpos];
8177 switch (res.code) {
8178 case Command.Kind.MoveTo:
8179 case Command.Kind.LineTo:
8180 res.args = (cast(const(float*))(data+cpos+1))[0..1*2];
8181 break;
8182 case Command.Kind.QuadTo:
8183 res.args = (cast(const(float*))(data+cpos+1))[0..2*2];
8184 break;
8185 case Command.Kind.BezierTo:
8186 res.args = (cast(const(float*))(data+cpos+1))[0..3*2];
8187 break;
8188 default:
8189 res.code = Command.Kind.End;
8190 res.args = null;
8191 break;
8193 } else {
8194 res.code = Command.Kind.End;
8195 res.args = null;
8197 return res;
8199 void popFront () {
8200 if (cleft <= 1) { cleft = 0; return; } // don't waste time skipping last command
8201 --cleft;
8202 switch (data[cpos]) {
8203 case Command.Kind.MoveTo:
8204 case Command.Kind.LineTo:
8205 cpos += 1+1*2*cast(uint)float.sizeof;
8206 break;
8207 case Command.Kind.QuadTo:
8208 cpos += 1+2*2*cast(uint)float.sizeof;
8209 break;
8210 case Command.Kind.BezierTo:
8211 cpos += 1+3*2*cast(uint)float.sizeof;
8212 break;
8213 default:
8214 cleft = 0;
8215 break;
8219 if (dsaddr) {
8220 incRef(cast(DataStore*)dsaddr); // range anchors it
8221 return Range(dsaddr, 0, ds.ccount);
8222 } else {
8223 return Range.init;
8228 public alias NVGGlyphOutline = NVGPathOutline; /// For backwards compatibility.
8230 /// Destroy glyph outiline and free allocated memory.
8231 /// Group: text_api
8232 public void kill (ref NVGPathOutline ol) nothrow @trusted @nogc {
8233 pragma(inline, true);
8234 ol.clear();
8237 static if (is(typeof(&fons__nvg__toOutline))) {
8238 public enum NanoVegaHasCharOutline = true; ///
8239 } else {
8240 public enum NanoVegaHasCharOutline = false; ///
8243 /// Returns glyph outlines as array of commands. Vertical 0 is baseline.
8244 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead.
8245 /// Returns `null` if there is no such glyph, or current font is not scalable.
8246 /// Group: text_api
8247 public NVGPathOutline charOutline (NVGContext ctx, dchar dch) nothrow @trusted @nogc {
8248 import core.stdc.stdlib : malloc;
8249 import core.stdc.string : memcpy;
8250 NVGstate* state = nvg__getState(ctx);
8251 ctx.fs.fontId = state.fontId;
8252 auto oline = NVGPathOutline.createNew();
8253 if (!ctx.fs.toOutline(dch, oline.ds)) oline.clear();
8254 return oline;
8258 float nvg__quantize (float a, float d) pure nothrow @safe @nogc {
8259 pragma(inline, true);
8260 return (cast(int)(a/d+0.5f))*d;
8263 float nvg__getFontScale (NVGstate* state) /*pure*/ nothrow @safe @nogc {
8264 pragma(inline, true);
8265 return nvg__min(nvg__quantize(nvg__getAverageScale(state.xform), 0.01f), 4.0f);
8268 void nvg__flushTextTexture (NVGContext ctx) nothrow @trusted @nogc {
8269 int[4] dirty = void;
8270 if (ctx.fs.validateTexture(dirty.ptr)) {
8271 auto fontImage = &ctx.fontImages[ctx.fontImageIdx];
8272 // Update texture
8273 if (fontImage.valid) {
8274 int iw, ih;
8275 const(ubyte)* data = ctx.fs.getTextureData(&iw, &ih);
8276 int x = dirty[0];
8277 int y = dirty[1];
8278 int w = dirty[2]-dirty[0];
8279 int h = dirty[3]-dirty[1];
8280 ctx.params.renderUpdateTexture(ctx.params.userPtr, fontImage.id, x, y, w, h, data);
8285 bool nvg__allocTextAtlas (NVGContext ctx) nothrow @trusted @nogc {
8286 int iw, ih;
8287 nvg__flushTextTexture(ctx);
8288 if (ctx.fontImageIdx >= NVG_MAX_FONTIMAGES-1) return false;
8289 // if next fontImage already have a texture
8290 if (ctx.fontImages[ctx.fontImageIdx+1].valid) {
8291 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx+1], iw, ih);
8292 } else {
8293 // calculate the new font image size and create it
8294 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx], iw, ih);
8295 if (iw > ih) ih *= 2; else iw *= 2;
8296 if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE;
8297 ctx.fontImages[ctx.fontImageIdx+1].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, iw, ih, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null);
8298 if (ctx.fontImages[ctx.fontImageIdx+1].id > 0) {
8299 ctx.fontImages[ctx.fontImageIdx+1].ctx = ctx;
8300 ctx.nvg__imageIncRef(ctx.fontImages[ctx.fontImageIdx+1].id, false); // don't increment driver refcount
8303 ++ctx.fontImageIdx;
8304 ctx.fs.resetAtlas(iw, ih);
8305 return true;
8308 void nvg__renderText (NVGContext ctx, NVGVertex* verts, int nverts) nothrow @trusted @nogc {
8309 NVGstate* state = nvg__getState(ctx);
8310 NVGPaint paint = state.fill;
8312 // Render triangles.
8313 paint.image = ctx.fontImages[ctx.fontImageIdx];
8315 // Apply global alpha
8316 paint.innerColor.a *= state.alpha;
8317 paint.middleColor.a *= state.alpha;
8318 paint.outerColor.a *= state.alpha;
8320 ctx.params.renderTriangles(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &paint, &state.scissor, verts, nverts);
8322 ++ctx.drawCallCount;
8323 ctx.textTriCount += nverts/3;
8326 /// Draws text string at specified location. Returns next x position.
8327 /// Group: text_api
8328 public float text(T) (NVGContext ctx, float x, float y, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8329 NVGstate* state = nvg__getState(ctx);
8330 FONSTextIter!T iter, prevIter;
8331 FONSQuad q;
8332 NVGVertex* verts;
8333 float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8334 float invscale = 1.0f/scale;
8335 int cverts = 0;
8336 int nverts = 0;
8338 if (state.fontId == FONS_INVALID) return x;
8339 if (str.length == 0) return x;
8341 ctx.fs.size = state.fontSize*scale;
8342 ctx.fs.spacing = state.letterSpacing*scale;
8343 ctx.fs.blur = state.fontBlur*scale;
8344 ctx.fs.textAlign = state.textAlign;
8345 ctx.fs.fontId = state.fontId;
8347 cverts = nvg__max(2, cast(int)(str.length))*6; // conservative estimate
8348 verts = nvg__allocTempVerts(ctx, cverts);
8349 if (verts is null) return x;
8351 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Required)) return x;
8352 prevIter = iter;
8353 while (iter.next(q)) {
8354 float[4*2] c = void;
8355 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8356 if (nverts != 0) {
8357 // TODO: add back-end bit to do this just once per frame
8358 nvg__flushTextTexture(ctx);
8359 nvg__renderText(ctx, verts, nverts);
8360 nverts = 0;
8362 if (!nvg__allocTextAtlas(ctx)) break; // no memory :(
8363 iter = prevIter;
8364 iter.next(q); // try again
8365 if (iter.prevGlyphIndex < 0) {
8366 // still can not find glyph, try replacement
8367 iter = prevIter;
8368 if (!iter.getDummyChar(q)) break;
8371 prevIter = iter;
8372 // transform corners
8373 state.xform.point(&c[0], &c[1], q.x0*invscale, q.y0*invscale);
8374 state.xform.point(&c[2], &c[3], q.x1*invscale, q.y0*invscale);
8375 state.xform.point(&c[4], &c[5], q.x1*invscale, q.y1*invscale);
8376 state.xform.point(&c[6], &c[7], q.x0*invscale, q.y1*invscale);
8377 // create triangles
8378 if (nverts+6 <= cverts) {
8379 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts;
8380 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts;
8381 nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); ++nverts;
8382 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts;
8383 nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); ++nverts;
8384 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts;
8388 // TODO: add back-end bit to do this just once per frame
8389 if (nverts > 0) {
8390 nvg__flushTextTexture(ctx);
8391 nvg__renderText(ctx, verts, nverts);
8394 return iter.nextx/scale;
8397 /** Draws multi-line text string at specified location wrapped at the specified width.
8398 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8399 * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8401 * Group: text_api
8403 public void textBox(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8404 NVGstate* state = nvg__getState(ctx);
8405 if (state.fontId == FONS_INVALID) return;
8407 NVGTextRow!T[2] rows;
8408 auto oldAlign = state.textAlign;
8409 scope(exit) state.textAlign = oldAlign;
8410 auto halign = state.textAlign.horizontal;
8411 float lineh = 0;
8413 ctx.textMetrics(null, null, &lineh);
8414 state.textAlign.horizontal = NVGTextAlign.H.Left;
8415 for (;;) {
8416 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]);
8417 //{ import core.stdc.stdio : printf; printf("slen=%u; rlen=%u; bw=%f\n", cast(uint)str.length, cast(uint)rres.length, cast(double)breakRowWidth); }
8418 if (rres.length == 0) break;
8419 foreach (ref row; rres) {
8420 final switch (halign) {
8421 case NVGTextAlign.H.Left: ctx.text(x, y, row.row); break;
8422 case NVGTextAlign.H.Center: ctx.text(x+breakRowWidth*0.5f-row.width*0.5f, y, row.row); break;
8423 case NVGTextAlign.H.Right: ctx.text(x+breakRowWidth-row.width, y, row.row); break;
8425 y += lineh*state.lineHeight;
8427 str = rres[$-1].rest;
8431 private template isGoodPositionDelegate(DG) {
8432 private DG dg;
8433 static if (is(typeof({ NVGGlyphPosition pos; bool res = dg(pos); })) ||
8434 is(typeof({ NVGGlyphPosition pos; dg(pos); })))
8435 enum isGoodPositionDelegate = true;
8436 else
8437 enum isGoodPositionDelegate = false;
8440 /** Calculates the glyph x positions of the specified text.
8441 * Measured values are returned in local coordinate space.
8443 * Group: text_api
8445 public NVGGlyphPosition[] textGlyphPositions(T) (NVGContext ctx, float x, float y, const(T)[] str, NVGGlyphPosition[] positions) nothrow @trusted @nogc
8446 if (isAnyCharType!T)
8448 if (str.length == 0 || positions.length == 0) return positions[0..0];
8449 usize posnum;
8450 auto len = ctx.textGlyphPositions(x, y, str, (in ref NVGGlyphPosition pos) {
8451 positions.ptr[posnum++] = pos;
8452 return (posnum < positions.length);
8454 return positions[0..len];
8457 /// Ditto.
8458 public int textGlyphPositions(T, DG) (NVGContext ctx, float x, float y, const(T)[] str, scope DG dg)
8459 if (isAnyCharType!T && isGoodPositionDelegate!DG)
8461 import std.traits : ReturnType;
8462 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true;
8464 NVGstate* state = nvg__getState(ctx);
8465 float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8466 float invscale = 1.0f/scale;
8467 FONSTextIter!T iter, prevIter;
8468 FONSQuad q;
8469 int npos = 0;
8471 if (str.length == 0) return 0;
8473 ctx.fs.size = state.fontSize*scale;
8474 ctx.fs.spacing = state.letterSpacing*scale;
8475 ctx.fs.blur = state.fontBlur*scale;
8476 ctx.fs.textAlign = state.textAlign;
8477 ctx.fs.fontId = state.fontId;
8479 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Optional)) return npos;
8480 prevIter = iter;
8481 while (iter.next(q)) {
8482 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8483 if (!nvg__allocTextAtlas(ctx)) break; // no memory
8484 iter = prevIter;
8485 iter.next(q); // try again
8486 if (iter.prevGlyphIndex < 0) {
8487 // still can not find glyph, try replacement
8488 iter = prevIter;
8489 if (!iter.getDummyChar(q)) break;
8492 prevIter = iter;
8493 NVGGlyphPosition position = void; //WARNING!
8494 position.strpos = cast(usize)(iter.stringp-str.ptr);
8495 position.x = iter.x*invscale;
8496 position.minx = nvg__min(iter.x, q.x0)*invscale;
8497 position.maxx = nvg__max(iter.nextx, q.x1)*invscale;
8498 ++npos;
8499 static if (RetBool) { if (!dg(position)) return npos; } else dg(position);
8502 return npos;
8505 private template isGoodRowDelegate(CT, DG) {
8506 private DG dg;
8507 static if (is(typeof({ NVGTextRow!CT row; bool res = dg(row); })) ||
8508 is(typeof({ NVGTextRow!CT row; dg(row); })))
8509 enum isGoodRowDelegate = true;
8510 else
8511 enum isGoodRowDelegate = false;
8514 /** Breaks the specified text into lines.
8515 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8516 * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8518 * Group: text_api
8520 public NVGTextRow!T[] textBreakLines(T) (NVGContext ctx, const(T)[] str, float breakRowWidth, NVGTextRow!T[] rows) nothrow @trusted @nogc
8521 if (isAnyCharType!T)
8523 if (rows.length == 0) return rows;
8524 if (rows.length > int.max-1) rows = rows[0..int.max-1];
8525 int nrow = 0;
8526 auto count = ctx.textBreakLines(str, breakRowWidth, (in ref NVGTextRow!T row) {
8527 rows[nrow++] = row;
8528 return (nrow < rows.length);
8530 return rows[0..count];
8533 /** Breaks the specified text into lines.
8534 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8535 * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8536 * Returns number of rows.
8538 * Group: text_api
8540 public int textBreakLines(T, DG) (NVGContext ctx, const(T)[] str, float breakRowWidth, scope DG dg)
8541 if (isAnyCharType!T && isGoodRowDelegate!(T, DG))
8543 import std.traits : ReturnType;
8544 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true;
8546 enum NVGcodepointType : int {
8547 Space,
8548 NewLine,
8549 Char,
8552 NVGstate* state = nvg__getState(ctx);
8553 float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8554 float invscale = 1.0f/scale;
8555 FONSTextIter!T iter, prevIter;
8556 FONSQuad q;
8557 int nrows = 0;
8558 float rowStartX = 0;
8559 float rowWidth = 0;
8560 float rowMinX = 0;
8561 float rowMaxX = 0;
8562 int rowStart = 0;
8563 int rowEnd = 0;
8564 int wordStart = 0;
8565 float wordStartX = 0;
8566 float wordMinX = 0;
8567 int breakEnd = 0;
8568 float breakWidth = 0;
8569 float breakMaxX = 0;
8570 int type = NVGcodepointType.Space, ptype = NVGcodepointType.Space;
8571 uint pcodepoint = 0;
8573 if (state.fontId == FONS_INVALID) return 0;
8574 if (str.length == 0 || dg is null) return 0;
8576 ctx.fs.size = state.fontSize*scale;
8577 ctx.fs.spacing = state.letterSpacing*scale;
8578 ctx.fs.blur = state.fontBlur*scale;
8579 ctx.fs.textAlign = state.textAlign;
8580 ctx.fs.fontId = state.fontId;
8582 breakRowWidth *= scale;
8584 enum Phase {
8585 Normal, // searching for breaking point
8586 SkipBlanks, // skip leading blanks
8588 Phase phase = Phase.SkipBlanks; // don't skip blanks on first line
8590 if (!iter.setup(ctx.fs, 0, 0, str, FONSBitmapFlag.Optional)) return 0;
8591 prevIter = iter;
8592 while (iter.next(q)) {
8593 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8594 if (!nvg__allocTextAtlas(ctx)) break; // no memory
8595 iter = prevIter;
8596 iter.next(q); // try again
8597 if (iter.prevGlyphIndex < 0) {
8598 // still can not find glyph, try replacement
8599 iter = prevIter;
8600 if (!iter.getDummyChar(q)) break;
8603 prevIter = iter;
8604 switch (iter.codepoint) {
8605 case 9: // \t
8606 case 11: // \v
8607 case 12: // \f
8608 case 32: // space
8609 case 0x00a0: // NBSP
8610 type = NVGcodepointType.Space;
8611 break;
8612 case 10: // \n
8613 type = (pcodepoint == 13 ? NVGcodepointType.Space : NVGcodepointType.NewLine);
8614 break;
8615 case 13: // \r
8616 type = (pcodepoint == 10 ? NVGcodepointType.Space : NVGcodepointType.NewLine);
8617 break;
8618 case 0x0085: // NEL
8619 case 0x2028: // Line Separator
8620 case 0x2029: // Paragraph Separator
8621 type = NVGcodepointType.NewLine;
8622 break;
8623 default:
8624 type = NVGcodepointType.Char;
8625 break;
8627 if (phase == Phase.SkipBlanks) {
8628 // fix row start
8629 rowStart = cast(int)(iter.stringp-str.ptr);
8630 rowEnd = rowStart;
8631 rowStartX = iter.x;
8632 rowWidth = iter.nextx-rowStartX; // q.x1-rowStartX;
8633 rowMinX = q.x0-rowStartX;
8634 rowMaxX = q.x1-rowStartX;
8635 wordStart = rowStart;
8636 wordStartX = iter.x;
8637 wordMinX = q.x0-rowStartX;
8638 breakEnd = rowStart;
8639 breakWidth = 0.0;
8640 breakMaxX = 0.0;
8641 if (type == NVGcodepointType.Space) continue;
8642 phase = Phase.Normal;
8645 if (type == NVGcodepointType.NewLine) {
8646 // always handle new lines
8647 NVGTextRow!T row;
8648 row.string = str;
8649 row.start = rowStart;
8650 row.end = rowEnd;
8651 row.width = rowWidth*invscale;
8652 row.minx = rowMinX*invscale;
8653 row.maxx = rowMaxX*invscale;
8654 ++nrows;
8655 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8656 phase = Phase.SkipBlanks;
8657 } else {
8658 float nextWidth = iter.nextx-rowStartX;
8659 // track last non-white space character
8660 if (type == NVGcodepointType.Char) {
8661 rowEnd = cast(int)(iter.nextp-str.ptr);
8662 rowWidth = iter.nextx-rowStartX;
8663 rowMaxX = q.x1-rowStartX;
8665 // track last end of a word
8666 if (ptype == NVGcodepointType.Char && type == NVGcodepointType.Space) {
8667 breakEnd = cast(int)(iter.stringp-str.ptr);
8668 breakWidth = rowWidth;
8669 breakMaxX = rowMaxX;
8671 // track last beginning of a word
8672 if (ptype == NVGcodepointType.Space && type == NVGcodepointType.Char) {
8673 wordStart = cast(int)(iter.stringp-str.ptr);
8674 wordStartX = iter.x;
8675 wordMinX = q.x0-rowStartX;
8677 // break to new line when a character is beyond break width
8678 if (type == NVGcodepointType.Char && nextWidth > breakRowWidth) {
8679 // the run length is too long, need to break to new line
8680 NVGTextRow!T row;
8681 row.string = str;
8682 if (breakEnd == rowStart) {
8683 // the current word is longer than the row length, just break it from here
8684 row.start = rowStart;
8685 row.end = cast(int)(iter.stringp-str.ptr);
8686 row.width = rowWidth*invscale;
8687 row.minx = rowMinX*invscale;
8688 row.maxx = rowMaxX*invscale;
8689 ++nrows;
8690 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8691 rowStartX = iter.x;
8692 rowStart = cast(int)(iter.stringp-str.ptr);
8693 rowEnd = cast(int)(iter.nextp-str.ptr);
8694 rowWidth = iter.nextx-rowStartX;
8695 rowMinX = q.x0-rowStartX;
8696 rowMaxX = q.x1-rowStartX;
8697 wordStart = rowStart;
8698 wordStartX = iter.x;
8699 wordMinX = q.x0-rowStartX;
8700 } else {
8701 // break the line from the end of the last word, and start new line from the beginning of the new
8702 //{ import core.stdc.stdio : printf; printf("rowStart=%u; rowEnd=%u; breakEnd=%u; len=%u\n", rowStart, rowEnd, breakEnd, cast(uint)str.length); }
8703 row.start = rowStart;
8704 row.end = breakEnd;
8705 row.width = breakWidth*invscale;
8706 row.minx = rowMinX*invscale;
8707 row.maxx = breakMaxX*invscale;
8708 ++nrows;
8709 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8710 rowStartX = wordStartX;
8711 rowStart = wordStart;
8712 rowEnd = cast(int)(iter.nextp-str.ptr);
8713 rowWidth = iter.nextx-rowStartX;
8714 rowMinX = wordMinX;
8715 rowMaxX = q.x1-rowStartX;
8716 // no change to the word start
8718 // set null break point
8719 breakEnd = rowStart;
8720 breakWidth = 0.0;
8721 breakMaxX = 0.0;
8725 pcodepoint = iter.codepoint;
8726 ptype = type;
8729 // break the line from the end of the last word, and start new line from the beginning of the new
8730 if (phase != Phase.SkipBlanks && rowStart < str.length) {
8731 //{ import core.stdc.stdio : printf; printf(" rowStart=%u; len=%u\n", rowStart, cast(uint)str.length); }
8732 NVGTextRow!T row;
8733 row.string = str;
8734 row.start = rowStart;
8735 row.end = cast(int)str.length;
8736 row.width = rowWidth*invscale;
8737 row.minx = rowMinX*invscale;
8738 row.maxx = rowMaxX*invscale;
8739 ++nrows;
8740 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8743 return nrows;
8746 /** Returns iterator which you can use to calculate text bounds and advancement.
8747 * This is usable when you need to do some text layouting with wrapping, to avoid
8748 * guesswork ("will advancement for this space stay the same?"), and Schlemiel's
8749 * algorithm. Note that you can copy the returned struct to save iterator state.
8751 * You can check if iterator is valid with [valid] property, put new chars with
8752 * [put] method, get current advance with [advance] property, and current
8753 * bounds with `getBounds(ref float[4] bounds)` method.
8755 * $(WARNING Don't change font parameters while iterating! Or use [restoreFont] method.)
8757 * Group: text_api
8759 public struct TextBoundsIterator {
8760 private:
8761 NVGContext ctx;
8762 FONSTextBoundsIterator fsiter; // fontstash iterator
8763 float scale, invscale, xscaled, yscaled;
8764 // font settings
8765 float fsSize, fsSpacing, fsBlur;
8766 int fsFontId;
8767 NVGTextAlign fsAlign;
8769 public:
8770 /// Setups iteration. Takes current font parameters from the given NanoVega context.
8771 this (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { reset(actx, ax, ay); }
8773 /// Resets iteration. Takes current font parameters from the given NanoVega context.
8774 void reset (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc {
8775 fsiter = fsiter.init;
8776 this = this.init;
8777 if (actx is null) return;
8778 NVGstate* state = nvg__getState(actx);
8779 if (state is null) return;
8780 if (state.fontId == FONS_INVALID) { ctx = null; return; }
8782 ctx = actx;
8783 scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8784 invscale = 1.0f/scale;
8786 fsSize = state.fontSize*scale;
8787 fsSpacing = state.letterSpacing*scale;
8788 fsBlur = state.fontBlur*scale;
8789 fsAlign = state.textAlign;
8790 fsFontId = state.fontId;
8791 restoreFont();
8793 xscaled = ax*scale;
8794 yscaled = ay*scale;
8795 fsiter.reset(ctx.fs, xscaled, yscaled);
8798 /// Restart iteration. Will not restore font.
8799 void restart () nothrow @trusted @nogc {
8800 if (ctx !is null) fsiter.reset(ctx.fs, xscaled, yscaled);
8803 /// Restore font settings for the context.
8804 void restoreFont () nothrow @trusted @nogc {
8805 if (ctx !is null) {
8806 ctx.fs.size = fsSize;
8807 ctx.fs.spacing = fsSpacing;
8808 ctx.fs.blur = fsBlur;
8809 ctx.fs.textAlign = fsAlign;
8810 ctx.fs.fontId = fsFontId;
8814 /// Is this iterator valid?
8815 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null); }
8817 /// Add chars.
8818 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { pragma(inline, true); if (ctx !is null) fsiter.put(str[]); }
8820 /// Returns current advance
8821 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null ? fsiter.advance*invscale : 0); }
8823 /// Returns current text bounds.
8824 void getBounds (ref float[4] bounds) nothrow @trusted @nogc {
8825 if (ctx !is null) {
8826 fsiter.getBounds(bounds);
8827 ctx.fs.getLineBounds(yscaled, &bounds[1], &bounds[3]);
8828 bounds[0] *= invscale;
8829 bounds[1] *= invscale;
8830 bounds[2] *= invscale;
8831 bounds[3] *= invscale;
8832 } else {
8833 bounds[] = 0;
8837 /// Returns current horizontal text bounds.
8838 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc {
8839 if (ctx !is null) {
8840 fsiter.getHBounds(xmin, xmax);
8841 xmin *= invscale;
8842 xmax *= invscale;
8846 /// Returns current vertical text bounds.
8847 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc {
8848 if (ctx !is null) {
8849 //fsiter.getVBounds(ymin, ymax);
8850 ctx.fs.getLineBounds(yscaled, &ymin, &ymax);
8851 ymin *= invscale;
8852 ymax *= invscale;
8857 /// Returns font line height (without line spacing), measured in local coordinate space.
8858 /// Group: text_api
8859 public float textFontHeight (NVGContext ctx) nothrow @trusted @nogc {
8860 float res = void;
8861 ctx.textMetrics(null, null, &res);
8862 return res;
8865 /// Returns font ascender (positive), measured in local coordinate space.
8866 /// Group: text_api
8867 public float textFontAscender (NVGContext ctx) nothrow @trusted @nogc {
8868 float res = void;
8869 ctx.textMetrics(&res, null, null);
8870 return res;
8873 /// Returns font descender (negative), measured in local coordinate space.
8874 /// Group: text_api
8875 public float textFontDescender (NVGContext ctx) nothrow @trusted @nogc {
8876 float res = void;
8877 ctx.textMetrics(null, &res, null);
8878 return res;
8881 /** Measures the specified text string. Returns horizontal and vertical sizes of the measured text.
8882 * Measured values are returned in local coordinate space.
8884 * Group: text_api
8886 public void textExtents(T) (NVGContext ctx, const(T)[] str, float *w, float *h) nothrow @trusted @nogc if (isAnyCharType!T) {
8887 float[4] bnd = void;
8888 ctx.textBounds(0, 0, str, bnd[]);
8889 if (!ctx.fs.getFontAA(nvg__getState(ctx).fontId)) {
8890 if (w !is null) *w = nvg__lrintf(bnd.ptr[2]-bnd.ptr[0]);
8891 if (h !is null) *h = nvg__lrintf(bnd.ptr[3]-bnd.ptr[1]);
8892 } else {
8893 if (w !is null) *w = bnd.ptr[2]-bnd.ptr[0];
8894 if (h !is null) *h = bnd.ptr[3]-bnd.ptr[1];
8898 /** Measures the specified text string. Returns horizontal size of the measured text.
8899 * Measured values are returned in local coordinate space.
8901 * Group: text_api
8903 public float textWidth(T) (NVGContext ctx, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8904 float w = void;
8905 ctx.textExtents(str, &w, null);
8906 return w;
8909 /** Measures the specified text string. Parameter bounds should be a float[4],
8910 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax]
8911 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
8912 * Measured values are returned in local coordinate space.
8914 * Group: text_api
8916 public float textBounds(T) (NVGContext ctx, float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc
8917 if (isAnyCharType!T)
8919 NVGstate* state = nvg__getState(ctx);
8921 if (state.fontId == FONS_INVALID) {
8922 bounds[] = 0;
8923 return 0;
8926 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8927 ctx.fs.size = state.fontSize*scale;
8928 ctx.fs.spacing = state.letterSpacing*scale;
8929 ctx.fs.blur = state.fontBlur*scale;
8930 ctx.fs.textAlign = state.textAlign;
8931 ctx.fs.fontId = state.fontId;
8933 float[4] b = void;
8934 immutable float width = ctx.fs.getTextBounds(x*scale, y*scale, str, b[]);
8935 immutable float invscale = 1.0f/scale;
8936 if (bounds.length) {
8937 // use line bounds for height
8938 ctx.fs.getLineBounds(y*scale, b.ptr+1, b.ptr+3);
8939 if (bounds.length > 0) bounds.ptr[0] = b.ptr[0]*invscale;
8940 if (bounds.length > 1) bounds.ptr[1] = b.ptr[1]*invscale;
8941 if (bounds.length > 2) bounds.ptr[2] = b.ptr[2]*invscale;
8942 if (bounds.length > 3) bounds.ptr[3] = b.ptr[3]*invscale;
8944 return width*invscale;
8947 /// Ditto.
8948 public void textBoxBounds(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str, float[] bounds) if (isAnyCharType!T) {
8949 NVGstate* state = nvg__getState(ctx);
8950 NVGTextRow!T[2] rows;
8951 float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8952 float invscale = 1.0f/scale;
8953 float lineh = 0, rminy = 0, rmaxy = 0;
8954 float minx, miny, maxx, maxy;
8956 if (state.fontId == FONS_INVALID) {
8957 bounds[] = 0;
8958 return;
8961 auto oldAlign = state.textAlign;
8962 scope(exit) state.textAlign = oldAlign;
8963 auto halign = state.textAlign.horizontal;
8965 ctx.textMetrics(null, null, &lineh);
8966 state.textAlign.horizontal = NVGTextAlign.H.Left;
8968 minx = maxx = x;
8969 miny = maxy = y;
8971 ctx.fs.size = state.fontSize*scale;
8972 ctx.fs.spacing = state.letterSpacing*scale;
8973 ctx.fs.blur = state.fontBlur*scale;
8974 ctx.fs.textAlign = state.textAlign;
8975 ctx.fs.fontId = state.fontId;
8976 ctx.fs.getLineBounds(0, &rminy, &rmaxy);
8977 rminy *= invscale;
8978 rmaxy *= invscale;
8980 for (;;) {
8981 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]);
8982 if (rres.length == 0) break;
8983 foreach (ref row; rres) {
8984 float rminx, rmaxx, dx = 0;
8985 // horizontal bounds
8986 final switch (halign) {
8987 case NVGTextAlign.H.Left: dx = 0; break;
8988 case NVGTextAlign.H.Center: dx = breakRowWidth*0.5f-row.width*0.5f; break;
8989 case NVGTextAlign.H.Right: dx = breakRowWidth-row.width; break;
8991 rminx = x+row.minx+dx;
8992 rmaxx = x+row.maxx+dx;
8993 minx = nvg__min(minx, rminx);
8994 maxx = nvg__max(maxx, rmaxx);
8995 // vertical bounds
8996 miny = nvg__min(miny, y+rminy);
8997 maxy = nvg__max(maxy, y+rmaxy);
8998 y += lineh*state.lineHeight;
9000 str = rres[$-1].rest;
9003 if (bounds.length) {
9004 if (bounds.length > 0) bounds.ptr[0] = minx;
9005 if (bounds.length > 1) bounds.ptr[1] = miny;
9006 if (bounds.length > 2) bounds.ptr[2] = maxx;
9007 if (bounds.length > 3) bounds.ptr[3] = maxy;
9011 /// Returns the vertical metrics based on the current text style. Measured values are returned in local coordinate space.
9012 /// Group: text_api
9013 public void textMetrics (NVGContext ctx, float* ascender, float* descender, float* lineh) nothrow @trusted @nogc {
9014 NVGstate* state = nvg__getState(ctx);
9016 if (state.fontId == FONS_INVALID) {
9017 if (ascender !is null) *ascender *= 0;
9018 if (descender !is null) *descender *= 0;
9019 if (lineh !is null) *lineh *= 0;
9020 return;
9023 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9024 immutable float invscale = 1.0f/scale;
9026 ctx.fs.size = state.fontSize*scale;
9027 ctx.fs.spacing = state.letterSpacing*scale;
9028 ctx.fs.blur = state.fontBlur*scale;
9029 ctx.fs.textAlign = state.textAlign;
9030 ctx.fs.fontId = state.fontId;
9032 ctx.fs.getVertMetrics(ascender, descender, lineh);
9033 if (ascender !is null) *ascender *= invscale;
9034 if (descender !is null) *descender *= invscale;
9035 if (lineh !is null) *lineh *= invscale;
9039 // ////////////////////////////////////////////////////////////////////////// //
9040 // fontstash
9041 // ////////////////////////////////////////////////////////////////////////// //
9042 import core.stdc.stdlib : malloc, realloc, free;
9043 import core.stdc.string : memset, memcpy, strncpy, strcmp, strlen;
9044 import core.stdc.stdio : FILE, fopen, fclose, fseek, ftell, fread, SEEK_END, SEEK_SET;
9046 public:
9047 // welcome to version hell!
9048 version(nanovg_force_stb_ttf) {
9049 } else {
9050 version(nanovg_force_detect) {} else version(nanovg_use_freetype) { version = nanovg_use_freetype_ii; }
9052 version(nanovg_ignore_iv_stb_ttf) enum nanovg_ignore_iv_stb_ttf = true; else enum nanovg_ignore_iv_stb_ttf = false;
9053 //version(nanovg_ignore_mono);
9055 version(nanovg_force_stb_ttf) {
9056 private enum NanoVegaForceFreeType = false;
9057 } else {
9058 version (nanovg_builtin_freetype_bindings) {
9059 version(Posix) {
9060 private enum NanoVegaForceFreeType = true;
9061 } else {
9062 private enum NanoVegaForceFreeType = false;
9064 } else {
9065 version(Posix) {
9066 private enum NanoVegaForceFreeType = true;
9067 } else {
9068 private enum NanoVegaForceFreeType = false;
9073 version(nanovg_use_freetype_ii) {
9074 enum NanoVegaIsUsingSTBTTF = false;
9075 //pragma(msg, "iv.freetype: forced");
9076 } else {
9077 static if (NanoVegaForceFreeType) {
9078 enum NanoVegaIsUsingSTBTTF = false;
9079 } else {
9080 static if (!nanovg_ignore_iv_stb_ttf && __traits(compiles, { import iv.stb.ttf; })) {
9081 import iv.stb.ttf;
9082 enum NanoVegaIsUsingSTBTTF = true;
9083 version(nanovg_report_stb_ttf) pragma(msg, "iv.stb.ttf");
9084 } else static if (__traits(compiles, { import arsd.ttf; })) {
9085 import arsd.ttf;
9086 enum NanoVegaIsUsingSTBTTF = true;
9087 version(nanovg_report_stb_ttf) pragma(msg, "arsd.ttf");
9088 } else static if (__traits(compiles, { import stb_truetype; })) {
9089 import stb_truetype;
9090 enum NanoVegaIsUsingSTBTTF = true;
9091 version(nanovg_report_stb_ttf) pragma(msg, "stb_truetype");
9092 } else static if (__traits(compiles, { import iv.freetype; })) {
9093 version (nanovg_builtin_freetype_bindings) {
9094 enum NanoVegaIsUsingSTBTTF = false;
9095 version = nanovg_builtin_freetype_bindings;
9096 } else {
9097 import iv.freetype;
9098 enum NanoVegaIsUsingSTBTTF = false;
9100 version(nanovg_report_stb_ttf) pragma(msg, "freetype");
9101 } else {
9102 static assert(0, "no stb_ttf/iv.freetype found!");
9108 // ////////////////////////////////////////////////////////////////////////// //
9109 //version = nanovg_ft_mono;
9111 /// Invald font id.
9112 /// Group: font_stash
9113 public enum FONS_INVALID = -1;
9115 public enum FONSBitmapFlag : uint {
9116 Required = 0,
9117 Optional = 1,
9120 public enum FONSError : int {
9121 NoError = 0,
9122 AtlasFull = 1, // Font atlas is full.
9123 StatesOverflow = 2, // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES.
9124 StatesUnderflow = 3, // Trying to pop too many states fonsPopState().
9127 /// Initial parameters for new FontStash.
9128 /// Group: font_stash
9129 public struct FONSParams {
9130 enum Flag : uint {
9131 ZeroTopLeft = 0U, // default
9132 ZeroBottomLeft = 1U,
9134 int width, height;
9135 Flag flags = Flag.ZeroTopLeft;
9136 void* userPtr;
9137 bool function (void* uptr, int width, int height) nothrow @trusted @nogc renderCreate;
9138 int function (void* uptr, int width, int height) nothrow @trusted @nogc renderResize;
9139 void function (void* uptr, int* rect, const(ubyte)* data) nothrow @trusted @nogc renderUpdate;
9140 void function (void* uptr) nothrow @trusted @nogc renderDelete;
9141 @property bool isZeroTopLeft () const pure nothrow @trusted @nogc { pragma(inline, true); return ((flags&Flag.ZeroBottomLeft) == 0); }
9144 //TODO: document this
9145 public struct FONSQuad {
9146 float x0=0, y0=0, s0=0, t0=0;
9147 float x1=0, y1=0, s1=0, t1=0;
9150 //TODO: document this
9151 public struct FONSTextIter(CT) if (isAnyCharType!CT) {
9152 alias CharType = CT;
9153 float x=0, y=0, nextx=0, nexty=0, scale=0, spacing=0;
9154 uint codepoint;
9155 short isize, iblur;
9156 FONSContext stash;
9157 FONSfont* font;
9158 int prevGlyphIndex;
9159 const(CT)* s; // string
9160 const(CT)* n; // next
9161 const(CT)* e; // end
9162 FONSBitmapFlag bitmapOption;
9163 static if (is(CT == char)) {
9164 uint utf8state;
9167 this (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { setup(astash, ax, ay, astr, abitmapOption); }
9168 ~this () nothrow @trusted @nogc { pragma(inline, true); static if (is(CT == char)) utf8state = 0; s = n = e = null; }
9170 @property const(CT)* stringp () const pure nothrow @trusted @nogc { pragma(inline, true); return s; }
9171 @property const(CT)* nextp () const pure nothrow @trusted @nogc { pragma(inline, true); return n; }
9172 @property const(CT)* endp () const pure nothrow @trusted @nogc { pragma(inline, true); return e; }
9174 bool setup (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc {
9175 import core.stdc.string : memset;
9177 memset(&this, 0, this.sizeof);
9178 if (astash is null) return false;
9180 FONSstate* state = astash.getState;
9182 if (state.font < 0 || state.font >= astash.nfonts) return false;
9183 font = astash.fonts[state.font];
9184 if (font is null || font.fdata is null) return false;
9186 isize = cast(short)(state.size*10.0f);
9187 iblur = cast(short)state.blur;
9188 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
9190 // align horizontally
9191 if (state.talign.left) {
9192 // empty
9193 } else if (state.talign.right) {
9194 immutable float width = astash.getTextBounds(ax, ay, astr, null);
9195 ax -= width;
9196 } else if (state.talign.center) {
9197 immutable float width = astash.getTextBounds(ax, ay, astr, null);
9198 ax -= width*0.5f;
9201 // align vertically
9202 ay += astash.getVertAlign(font, state.talign, isize);
9204 x = nextx = ax;
9205 y = nexty = ay;
9206 spacing = state.spacing;
9208 if (astr.ptr is null) {
9209 static if (is(CharType == char)) astr = "";
9210 else static if (is(CharType == wchar)) astr = ""w;
9211 else static if (is(CharType == dchar)) astr = ""d;
9212 else static assert(0, "wtf?!");
9214 s = astr.ptr;
9215 n = astr.ptr;
9216 e = astr.ptr+astr.length;
9218 codepoint = 0;
9219 prevGlyphIndex = -1;
9220 bitmapOption = abitmapOption;
9221 stash = astash;
9223 return true;
9226 bool getDummyChar (ref FONSQuad quad) nothrow @trusted @nogc {
9227 if (stash is null || font is null) return false;
9228 // get glyph and quad
9229 x = nextx;
9230 y = nexty;
9231 FONSglyph* glyph = stash.getGlyph(font, 0xFFFD, isize, iblur, bitmapOption);
9232 if (glyph !is null) {
9233 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9234 prevGlyphIndex = glyph.index;
9235 return true;
9236 } else {
9237 prevGlyphIndex = -1;
9238 return false;
9242 bool next (ref FONSQuad quad) nothrow @trusted @nogc {
9243 if (stash is null || font is null) return false;
9244 FONSglyph* glyph = null;
9245 static if (is(CharType == char)) {
9246 const(char)* str = this.n;
9247 this.s = this.n;
9248 if (str is this.e) return false;
9249 const(char)* e = this.e;
9250 for (; str !is e; ++str) {
9251 /*if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;*/
9252 mixin(DecUtfMixin!("this.utf8state", "this.codepoint", "*cast(const(ubyte)*)str"));
9253 if (utf8state) continue;
9254 ++str; // 'cause we'll break anyway
9255 // get glyph and quad
9256 x = nextx;
9257 y = nexty;
9258 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption);
9259 if (glyph !is null) {
9260 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9261 prevGlyphIndex = glyph.index;
9262 } else {
9263 prevGlyphIndex = -1;
9265 break;
9267 this.n = str;
9268 } else {
9269 const(CharType)* str = this.n;
9270 this.s = this.n;
9271 if (str is this.e) return false;
9272 codepoint = cast(uint)(*str++);
9273 if (codepoint > dchar.max) codepoint = 0xFFFD;
9274 // get glyph and quad
9275 x = nextx;
9276 y = nexty;
9277 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption);
9278 if (glyph !is null) {
9279 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9280 prevGlyphIndex = glyph.index;
9281 } else {
9282 prevGlyphIndex = -1;
9284 this.n = str;
9286 return true;
9291 // ////////////////////////////////////////////////////////////////////////// //
9292 //static if (!HasAST) version = nanovg_use_freetype_ii_x;
9294 /*version(nanovg_use_freetype_ii_x)*/ static if (!NanoVegaIsUsingSTBTTF) {
9295 version(nanovg_builtin_freetype_bindings) {
9296 pragma(lib, "freetype");
9297 private extern(C) nothrow @trusted @nogc {
9298 private import core.stdc.config : c_long, c_ulong;
9299 alias FT_Pos = c_long;
9300 // config/ftconfig.h
9301 alias FT_Int16 = short;
9302 alias FT_UInt16 = ushort;
9303 alias FT_Int32 = int;
9304 alias FT_UInt32 = uint;
9305 alias FT_Fast = int;
9306 alias FT_UFast = uint;
9307 alias FT_Int64 = long;
9308 alias FT_Uint64 = ulong;
9309 // fttypes.h
9310 alias FT_Bool = ubyte;
9311 alias FT_FWord = short;
9312 alias FT_UFWord = ushort;
9313 alias FT_Char = char;
9314 alias FT_Byte = ubyte;
9315 alias FT_Bytes = FT_Byte*;
9316 alias FT_Tag = FT_UInt32;
9317 alias FT_String = char;
9318 alias FT_Short = short;
9319 alias FT_UShort = ushort;
9320 alias FT_Int = int;
9321 alias FT_UInt = uint;
9322 alias FT_Long = c_long;
9323 alias FT_ULong = c_ulong;
9324 alias FT_F2Dot14 = short;
9325 alias FT_F26Dot6 = c_long;
9326 alias FT_Fixed = c_long;
9327 alias FT_Error = int;
9328 alias FT_Pointer = void*;
9329 alias FT_Offset = usize;
9330 alias FT_PtrDist = ptrdiff_t;
9332 struct FT_UnitVector {
9333 FT_F2Dot14 x;
9334 FT_F2Dot14 y;
9337 struct FT_Matrix {
9338 FT_Fixed xx, xy;
9339 FT_Fixed yx, yy;
9342 struct FT_Data {
9343 const(FT_Byte)* pointer;
9344 FT_Int length;
9346 alias FT_Face = FT_FaceRec*;
9347 struct FT_FaceRec {
9348 FT_Long num_faces;
9349 FT_Long face_index;
9350 FT_Long face_flags;
9351 FT_Long style_flags;
9352 FT_Long num_glyphs;
9353 FT_String* family_name;
9354 FT_String* style_name;
9355 FT_Int num_fixed_sizes;
9356 FT_Bitmap_Size* available_sizes;
9357 FT_Int num_charmaps;
9358 FT_CharMap* charmaps;
9359 FT_Generic generic;
9360 FT_BBox bbox;
9361 FT_UShort units_per_EM;
9362 FT_Short ascender;
9363 FT_Short descender;
9364 FT_Short height;
9365 FT_Short max_advance_width;
9366 FT_Short max_advance_height;
9367 FT_Short underline_position;
9368 FT_Short underline_thickness;
9369 FT_GlyphSlot glyph;
9370 FT_Size size;
9371 FT_CharMap charmap;
9372 FT_Driver driver;
9373 FT_Memory memory;
9374 FT_Stream stream;
9375 FT_ListRec sizes_list;
9376 FT_Generic autohint;
9377 void* extensions;
9378 FT_Face_Internal internal;
9380 struct FT_Bitmap_Size {
9381 FT_Short height;
9382 FT_Short width;
9383 FT_Pos size;
9384 FT_Pos x_ppem;
9385 FT_Pos y_ppem;
9387 alias FT_CharMap = FT_CharMapRec*;
9388 struct FT_CharMapRec {
9389 FT_Face face;
9390 FT_Encoding encoding;
9391 FT_UShort platform_id;
9392 FT_UShort encoding_id;
9394 extern(C) nothrow @nogc { alias FT_Generic_Finalizer = void function (void* object); }
9395 struct FT_Generic {
9396 void* data;
9397 FT_Generic_Finalizer finalizer;
9399 struct FT_Vector {
9400 FT_Pos x;
9401 FT_Pos y;
9403 struct FT_BBox {
9404 FT_Pos xMin, yMin;
9405 FT_Pos xMax, yMax;
9407 alias FT_Pixel_Mode = int;
9408 enum {
9409 FT_PIXEL_MODE_NONE = 0,
9410 FT_PIXEL_MODE_MONO,
9411 FT_PIXEL_MODE_GRAY,
9412 FT_PIXEL_MODE_GRAY2,
9413 FT_PIXEL_MODE_GRAY4,
9414 FT_PIXEL_MODE_LCD,
9415 FT_PIXEL_MODE_LCD_V,
9416 FT_PIXEL_MODE_MAX
9418 struct FT_Bitmap {
9419 uint rows;
9420 uint width;
9421 int pitch;
9422 ubyte* buffer;
9423 ushort num_grays;
9424 ubyte pixel_mode;
9425 ubyte palette_mode;
9426 void* palette;
9428 struct FT_Outline {
9429 short n_contours;
9430 short n_points;
9431 FT_Vector* points;
9432 byte* tags;
9433 short* contours;
9434 int flags;
9436 alias FT_GlyphSlot = FT_GlyphSlotRec*;
9437 struct FT_GlyphSlotRec {
9438 FT_Library library;
9439 FT_Face face;
9440 FT_GlyphSlot next;
9441 FT_UInt reserved;
9442 FT_Generic generic;
9443 FT_Glyph_Metrics metrics;
9444 FT_Fixed linearHoriAdvance;
9445 FT_Fixed linearVertAdvance;
9446 FT_Vector advance;
9447 FT_Glyph_Format format;
9448 FT_Bitmap bitmap;
9449 FT_Int bitmap_left;
9450 FT_Int bitmap_top;
9451 FT_Outline outline;
9452 FT_UInt num_subglyphs;
9453 FT_SubGlyph subglyphs;
9454 void* control_data;
9455 c_long control_len;
9456 FT_Pos lsb_delta;
9457 FT_Pos rsb_delta;
9458 void* other;
9459 FT_Slot_Internal internal;
9461 alias FT_Size = FT_SizeRec*;
9462 struct FT_SizeRec {
9463 FT_Face face;
9464 FT_Generic generic;
9465 FT_Size_Metrics metrics;
9466 FT_Size_Internal internal;
9468 alias FT_Encoding = FT_Tag;
9469 alias FT_Face_Internal = void*;
9470 alias FT_Driver = void*;
9471 alias FT_Memory = void*;
9472 alias FT_Stream = void*;
9473 alias FT_Library = void*;
9474 alias FT_SubGlyph = void*;
9475 alias FT_Slot_Internal = void*;
9476 alias FT_Size_Internal = void*;
9477 alias FT_ListNode = FT_ListNodeRec*;
9478 alias FT_List = FT_ListRec*;
9479 struct FT_ListNodeRec {
9480 FT_ListNode prev;
9481 FT_ListNode next;
9482 void* data;
9484 struct FT_ListRec {
9485 FT_ListNode head;
9486 FT_ListNode tail;
9488 struct FT_Glyph_Metrics {
9489 FT_Pos width;
9490 FT_Pos height;
9491 FT_Pos horiBearingX;
9492 FT_Pos horiBearingY;
9493 FT_Pos horiAdvance;
9494 FT_Pos vertBearingX;
9495 FT_Pos vertBearingY;
9496 FT_Pos vertAdvance;
9498 alias FT_Glyph_Format = FT_Tag;
9499 FT_Tag FT_MAKE_TAG (char x1, char x2, char x3, char x4) pure nothrow @safe @nogc {
9500 pragma(inline, true);
9501 return cast(FT_UInt32)((x1<<24)|(x2<<16)|(x3<<8)|x4);
9503 enum : FT_Tag {
9504 FT_GLYPH_FORMAT_NONE = 0,
9505 FT_GLYPH_FORMAT_COMPOSITE = FT_MAKE_TAG('c','o','m','p'),
9506 FT_GLYPH_FORMAT_BITMAP = FT_MAKE_TAG('b','i','t','s'),
9507 FT_GLYPH_FORMAT_OUTLINE = FT_MAKE_TAG('o','u','t','l'),
9508 FT_GLYPH_FORMAT_PLOTTER = FT_MAKE_TAG('p','l','o','t'),
9510 struct FT_Size_Metrics {
9511 FT_UShort x_ppem;
9512 FT_UShort y_ppem;
9514 FT_Fixed x_scale;
9515 FT_Fixed y_scale;
9517 FT_Pos ascender;
9518 FT_Pos descender;
9519 FT_Pos height;
9520 FT_Pos max_advance;
9522 enum FT_LOAD_DEFAULT = 0x0U;
9523 enum FT_LOAD_NO_SCALE = 1U<<0;
9524 enum FT_LOAD_NO_HINTING = 1U<<1;
9525 enum FT_LOAD_RENDER = 1U<<2;
9526 enum FT_LOAD_NO_BITMAP = 1U<<3;
9527 enum FT_LOAD_VERTICAL_LAYOUT = 1U<<4;
9528 enum FT_LOAD_FORCE_AUTOHINT = 1U<<5;
9529 enum FT_LOAD_CROP_BITMAP = 1U<<6;
9530 enum FT_LOAD_PEDANTIC = 1U<<7;
9531 enum FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1U<<9;
9532 enum FT_LOAD_NO_RECURSE = 1U<<10;
9533 enum FT_LOAD_IGNORE_TRANSFORM = 1U<<11;
9534 enum FT_LOAD_MONOCHROME = 1U<<12;
9535 enum FT_LOAD_LINEAR_DESIGN = 1U<<13;
9536 enum FT_LOAD_NO_AUTOHINT = 1U<<15;
9537 enum FT_LOAD_COLOR = 1U<<20;
9538 enum FT_LOAD_COMPUTE_METRICS = 1U<<21;
9539 enum FT_FACE_FLAG_KERNING = 1U<<6;
9540 alias FT_Kerning_Mode = int;
9541 enum /*FT_Kerning_Mode*/ {
9542 FT_KERNING_DEFAULT = 0,
9543 FT_KERNING_UNFITTED,
9544 FT_KERNING_UNSCALED
9546 extern(C) nothrow @nogc {
9547 alias FT_Outline_MoveToFunc = int function (const(FT_Vector)*, void*);
9548 alias FT_Outline_LineToFunc = int function (const(FT_Vector)*, void*);
9549 alias FT_Outline_ConicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, void*);
9550 alias FT_Outline_CubicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, const(FT_Vector)*, void*);
9552 struct FT_Outline_Funcs {
9553 FT_Outline_MoveToFunc move_to;
9554 FT_Outline_LineToFunc line_to;
9555 FT_Outline_ConicToFunc conic_to;
9556 FT_Outline_CubicToFunc cubic_to;
9557 int shift;
9558 FT_Pos delta;
9561 FT_Error FT_Init_FreeType (FT_Library*);
9562 FT_Error FT_New_Memory_Face (FT_Library, const(FT_Byte)*, FT_Long, FT_Long, FT_Face*);
9563 FT_UInt FT_Get_Char_Index (FT_Face, FT_ULong);
9564 FT_Error FT_Set_Pixel_Sizes (FT_Face, FT_UInt, FT_UInt);
9565 FT_Error FT_Load_Glyph (FT_Face, FT_UInt, FT_Int32);
9566 FT_Error FT_Get_Advance (FT_Face, FT_UInt, FT_Int32, FT_Fixed*);
9567 FT_Error FT_Get_Kerning (FT_Face, FT_UInt, FT_UInt, FT_UInt, FT_Vector*);
9568 void FT_Outline_Get_CBox (const(FT_Outline)*, FT_BBox*);
9569 FT_Error FT_Outline_Decompose (FT_Outline*, const(FT_Outline_Funcs)*, void*);
9571 } else {
9572 import iv.freetype;
9575 struct FONSttFontImpl {
9576 FT_Face font;
9577 bool mono; // no aa?
9580 __gshared FT_Library ftLibrary;
9582 int fons__tt_init (FONSContext context) nothrow @trusted @nogc {
9583 FT_Error ftError;
9584 //FONS_NOTUSED(context);
9585 ftError = FT_Init_FreeType(&ftLibrary);
9586 return (ftError == 0);
9589 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc {
9590 font.mono = v;
9593 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc {
9594 return font.mono;
9597 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc {
9598 FT_Error ftError;
9599 //font.font.userdata = stash;
9600 ftError = FT_New_Memory_Face(ftLibrary, cast(const(FT_Byte)*)data, dataSize, 0, &font.font);
9601 return ftError == 0;
9604 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc {
9605 *ascent = font.font.ascender;
9606 *descent = font.font.descender;
9607 *lineGap = font.font.height-(*ascent - *descent);
9610 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc {
9611 return size/(font.font.ascender-font.font.descender);
9614 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc {
9615 return FT_Get_Char_Index(font.font, codepoint);
9618 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 {
9619 FT_Error ftError;
9620 FT_GlyphSlot ftGlyph;
9621 //version(nanovg_ignore_mono) enum exflags = 0;
9622 //else version(nanovg_ft_mono) enum exflags = FT_LOAD_MONOCHROME; else enum exflags = 0;
9623 uint exflags = (font.mono ? FT_LOAD_MONOCHROME : 0);
9624 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)));
9625 if (ftError) return 0;
9626 ftError = FT_Load_Glyph(font.font, glyph, FT_LOAD_RENDER|/*FT_LOAD_NO_AUTOHINT|*/exflags);
9627 if (ftError) return 0;
9628 ftError = FT_Get_Advance(font.font, glyph, FT_LOAD_NO_SCALE|/*FT_LOAD_NO_AUTOHINT|*/exflags, cast(FT_Fixed*)advance);
9629 if (ftError) return 0;
9630 ftGlyph = font.font.glyph;
9631 *lsb = cast(int)ftGlyph.metrics.horiBearingX;
9632 *x0 = ftGlyph.bitmap_left;
9633 *x1 = *x0+ftGlyph.bitmap.width;
9634 *y0 = -ftGlyph.bitmap_top;
9635 *y1 = *y0+ftGlyph.bitmap.rows;
9636 return 1;
9639 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc {
9640 FT_GlyphSlot ftGlyph = font.font.glyph;
9641 //FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap
9642 //version(nanovg_ignore_mono) enum RenderAA = true;
9643 //else version(nanovg_ft_mono) enum RenderAA = false;
9644 //else enum RenderAA = true;
9645 if (font.mono) {
9646 auto src = ftGlyph.bitmap.buffer;
9647 auto dst = output;
9648 auto spt = ftGlyph.bitmap.pitch;
9649 if (spt < 0) spt = -spt;
9650 foreach (int y; 0..ftGlyph.bitmap.rows) {
9651 ubyte count = 0, b = 0;
9652 auto s = src;
9653 auto d = dst;
9654 foreach (int x; 0..ftGlyph.bitmap.width) {
9655 if (count-- == 0) { count = 7; b = *s++; } else b <<= 1;
9656 *d++ = (b&0x80 ? 255 : 0);
9658 src += spt;
9659 dst += outStride;
9661 } else {
9662 auto src = ftGlyph.bitmap.buffer;
9663 auto dst = output;
9664 auto spt = ftGlyph.bitmap.pitch;
9665 if (spt < 0) spt = -spt;
9666 foreach (int y; 0..ftGlyph.bitmap.rows) {
9667 import core.stdc.string : memcpy;
9668 //dst[0..ftGlyph.bitmap.width] = src[0..ftGlyph.bitmap.width];
9669 memcpy(dst, src, ftGlyph.bitmap.width);
9670 src += spt;
9671 dst += outStride;
9676 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc {
9677 FT_Vector ftKerning;
9678 version(none) {
9679 // fitted kerning
9680 FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning);
9681 //{ import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d\n", glyph1, glyph2, ftKerning.x, ftKerning.y); }
9682 return cast(int)ftKerning.x; // round up and convert to integer
9683 } else {
9684 // unfitted kerning
9685 //FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNFITTED, &ftKerning);
9686 if (glyph1 <= 0 || glyph2 <= 0 || (font.font.face_flags&FT_FACE_FLAG_KERNING) == 0) return 0;
9687 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;
9688 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning)) return 0;
9689 version(none) {
9690 if (ftKerning.x) {
9691 //{ import core.stdc.stdio : printf; printf("has kerning: %u\n", cast(uint)(font.font.face_flags&FT_FACE_FLAG_KERNING)); }
9692 { import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d (size=%g)\n", glyph1, glyph2, ftKerning.x, ftKerning.y, cast(double)size); }
9695 version(none) {
9696 FT_Vector kk;
9697 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNSCALED, &kk)) assert(0, "wtf?!");
9698 auto kadvfrac = FT_MulFix(kk.x, font.font.size.metrics.x_scale); // 1/64 of pixel
9699 //return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
9700 //assert(ftKerning.x == kadvfrac);
9701 if (ftKerning.x || kadvfrac) {
9702 { 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); }
9704 //return cast(int)(kadvfrac+(kadvfrac < 0 ? -31 : 32)>>6); // round up and convert to integer
9705 return kadvfrac/64.0f;
9707 //return cast(int)(ftKerning.x+(ftKerning.x < 0 ? -31 : 32)>>6); // round up and convert to integer
9708 return ftKerning.x/64.0f;
9712 extern(C) nothrow @trusted @nogc {
9713 static struct OutlinerData {
9714 @disable this (this);
9715 void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); }
9716 NVGContext vg;
9717 NVGPathOutline.DataStore* ol;
9718 FT_BBox outlineBBox;
9719 nothrow @trusted @nogc:
9720 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; }
9721 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
9724 int fons__nvg__moveto_cb (const(FT_Vector)* to, void* user) {
9725 auto odata = cast(OutlinerData*)user;
9726 if (odata.vg !is null) odata.vg.moveTo(odata.transx(to.x), odata.transy(to.y));
9727 if (odata.ol !is null) {
9728 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo);
9729 odata.ol.putArgs(odata.transx(to.x));
9730 odata.ol.putArgs(odata.transy(to.y));
9732 return 0;
9735 int fons__nvg__lineto_cb (const(FT_Vector)* to, void* user) {
9736 auto odata = cast(OutlinerData*)user;
9737 if (odata.vg !is null) odata.vg.lineTo(odata.transx(to.x), odata.transy(to.y));
9738 if (odata.ol !is null) {
9739 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo);
9740 odata.ol.putArgs(odata.transx(to.x));
9741 odata.ol.putArgs(odata.transy(to.y));
9743 return 0;
9746 int fons__nvg__quadto_cb (const(FT_Vector)* c1, const(FT_Vector)* to, void* user) {
9747 auto odata = cast(OutlinerData*)user;
9748 if (odata.vg !is null) odata.vg.quadTo(odata.transx(c1.x), odata.transy(c1.y), odata.transx(to.x), odata.transy(to.y));
9749 if (odata.ol !is null) {
9750 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo);
9751 odata.ol.putArgs(odata.transx(c1.x));
9752 odata.ol.putArgs(odata.transy(c1.y));
9753 odata.ol.putArgs(odata.transx(to.x));
9754 odata.ol.putArgs(odata.transy(to.y));
9756 return 0;
9759 int fons__nvg__cubicto_cb (const(FT_Vector)* c1, const(FT_Vector)* c2, const(FT_Vector)* to, void* user) {
9760 auto odata = cast(OutlinerData*)user;
9761 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));
9762 if (odata.ol !is null) {
9763 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo);
9764 odata.ol.putArgs(odata.transx(c1.x));
9765 odata.ol.putArgs(odata.transy(c1.y));
9766 odata.ol.putArgs(odata.transx(c2.x));
9767 odata.ol.putArgs(odata.transy(c2.y));
9768 odata.ol.putArgs(odata.transx(to.x));
9769 odata.ol.putArgs(odata.transy(to.y));
9771 return 0;
9775 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc {
9776 if (bounds.length > 4) bounds = bounds.ptr[0..4];
9778 FT_Outline_Funcs funcs;
9779 funcs.move_to = &fons__nvg__moveto_cb;
9780 funcs.line_to = &fons__nvg__lineto_cb;
9781 funcs.conic_to = &fons__nvg__quadto_cb;
9782 funcs.cubic_to = &fons__nvg__cubicto_cb;
9784 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
9785 if (err) { bounds[] = 0; return false; }
9786 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; }
9788 FT_Outline outline = font.font.glyph.outline;
9790 OutlinerData odata;
9791 odata.vg = vg;
9792 FT_Outline_Get_CBox(&outline, &odata.outlineBBox);
9794 err = FT_Outline_Decompose(&outline, &funcs, &odata);
9795 if (err) { bounds[] = 0; return false; }
9796 if (bounds.length > 0) bounds.ptr[0] = odata.outlineBBox.xMin;
9797 if (bounds.length > 1) bounds.ptr[1] = -odata.outlineBBox.yMax;
9798 if (bounds.length > 2) bounds.ptr[2] = odata.outlineBBox.xMax;
9799 if (bounds.length > 3) bounds.ptr[3] = -odata.outlineBBox.yMin;
9800 return true;
9803 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
9804 FT_Outline_Funcs funcs;
9805 funcs.move_to = &fons__nvg__moveto_cb;
9806 funcs.line_to = &fons__nvg__lineto_cb;
9807 funcs.conic_to = &fons__nvg__quadto_cb;
9808 funcs.cubic_to = &fons__nvg__cubicto_cb;
9810 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
9811 if (err) return false;
9812 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) return false;
9814 FT_Outline outline = font.font.glyph.outline;
9816 OutlinerData odata;
9817 odata.ol = ol;
9818 FT_Outline_Get_CBox(&outline, &odata.outlineBBox);
9820 err = FT_Outline_Decompose(&outline, &funcs, &odata);
9821 if (err) return false;
9822 ol.bounds.ptr[0] = odata.outlineBBox.xMin;
9823 ol.bounds.ptr[1] = -odata.outlineBBox.yMax;
9824 ol.bounds.ptr[2] = odata.outlineBBox.xMax;
9825 ol.bounds.ptr[3] = -odata.outlineBBox.yMin;
9826 return true;
9829 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc {
9830 if (bounds.length > 4) bounds = bounds.ptr[0..4];
9832 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
9833 if (err) return false;
9834 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; }
9836 FT_Outline outline = font.font.glyph.outline;
9837 FT_BBox outlineBBox;
9838 FT_Outline_Get_CBox(&outline, &outlineBBox);
9839 if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin;
9840 if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax;
9841 if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax;
9842 if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin;
9843 return true;
9847 } else {
9848 // ////////////////////////////////////////////////////////////////////////// //
9849 // sorry
9850 import std.traits : isFunctionPointer, isDelegate;
9851 private auto assumeNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) {
9852 import std.traits;
9853 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
9854 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
9857 private auto forceNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) {
9858 try {
9859 return assumeNoThrowNoGC(t)();
9860 } catch (Exception e) {
9861 assert(0, "OOPS!");
9865 struct FONSttFontImpl {
9866 stbtt_fontinfo font;
9867 bool mono; // no aa?
9870 int fons__tt_init (FONSContext context) nothrow @trusted @nogc {
9871 return 1;
9874 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc {
9875 font.mono = v;
9878 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc {
9879 return font.mono;
9882 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc {
9883 int stbError;
9884 font.font.userdata = context;
9885 forceNoThrowNoGC({ stbError = stbtt_InitFont(&font.font, data, 0); });
9886 return stbError;
9889 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc {
9890 forceNoThrowNoGC({ stbtt_GetFontVMetrics(&font.font, ascent, descent, lineGap); });
9893 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc {
9894 float res = void;
9895 forceNoThrowNoGC({ res = stbtt_ScaleForPixelHeight(&font.font, size); });
9896 return res;
9899 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc {
9900 int res;
9901 forceNoThrowNoGC({ res = stbtt_FindGlyphIndex(&font.font, codepoint); });
9902 return res;
9905 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 {
9906 forceNoThrowNoGC({ stbtt_GetGlyphHMetrics(&font.font, glyph, advance, lsb); });
9907 forceNoThrowNoGC({ stbtt_GetGlyphBitmapBox(&font.font, glyph, scale, scale, x0, y0, x1, y1); });
9908 return 1;
9911 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc {
9912 forceNoThrowNoGC({ stbtt_MakeGlyphBitmap(&font.font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); });
9915 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc {
9916 // FUnits -> pixels: pointSize * resolution / (72 points per inch * units_per_em)
9917 // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#converting
9918 float res = void;
9919 forceNoThrowNoGC({
9920 res = stbtt_GetGlyphKernAdvance(&font.font, glyph1, glyph2);
9921 res *= stbtt_ScaleForPixelHeight(&font.font, size);
9924 if (res != 0) {
9925 { 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)); }
9928 //k8: dunno if this is right; i guess it isn't but...
9929 return res;
9932 // old arsd.ttf sux! ;-)
9933 static if (is(typeof(STBTT_vcubic))) {
9935 static struct OutlinerData {
9936 @disable this (this);
9937 void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); }
9938 NVGPathOutline.DataStore* ol;
9939 nothrow @trusted @nogc:
9940 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; }
9941 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
9945 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc {
9946 if (bounds.length > 4) bounds = bounds.ptr[0..4];
9948 bool okflag = false;
9950 forceNoThrowNoGC({
9951 int x0, y0, x1, y1;
9952 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
9953 bounds[] = 0;
9954 return;
9957 if (bounds.length > 0) bounds.ptr[0] = x0;
9958 if (bounds.length > 1) bounds.ptr[1] = -y1;
9959 if (bounds.length > 2) bounds.ptr[2] = x1;
9960 if (bounds.length > 3) bounds.ptr[3] = -y0;
9962 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
9964 stbtt_vertex* verts = null;
9965 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); }
9966 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts);
9967 if (vcount < 1) return;
9969 foreach (const ref vt; verts[0..vcount]) {
9970 switch (vt.type) {
9971 case STBTT_vmove: vg.moveTo(vt.x, transy(vt.y)); break;
9972 case STBTT_vline: vg.lineTo(vt.x, transy(vt.y)); break;
9973 case STBTT_vcurve: vg.quadTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy)); break;
9974 case STBTT_vcubic: vg.bezierTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy), vt.cx1, transy(vt.cy1)); break;
9975 default:
9979 okflag = true;
9982 return okflag;
9985 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
9986 bool okflag = false;
9988 forceNoThrowNoGC({
9989 int x0, y0, x1, y1;
9991 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
9992 ol.bounds[] = 0;
9993 return;
9996 ol.bounds.ptr[0] = x0;
9997 ol.bounds.ptr[1] = -y1;
9998 ol.bounds.ptr[2] = x1;
9999 ol.bounds.ptr[3] = -y0;
10001 stbtt_vertex* verts = null;
10002 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); }
10003 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts);
10004 if (vcount < 1) return;
10006 OutlinerData odata;
10007 odata.ol = ol;
10009 foreach (const ref vt; verts[0..vcount]) {
10010 switch (vt.type) {
10011 case STBTT_vmove:
10012 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo);
10013 odata.ol.putArgs(odata.transx(vt.x));
10014 odata.ol.putArgs(odata.transy(vt.y));
10015 break;
10016 case STBTT_vline:
10017 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo);
10018 odata.ol.putArgs(odata.transx(vt.x));
10019 odata.ol.putArgs(odata.transy(vt.y));
10020 break;
10021 case STBTT_vcurve:
10022 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo);
10023 odata.ol.putArgs(odata.transx(vt.x));
10024 odata.ol.putArgs(odata.transy(vt.y));
10025 odata.ol.putArgs(odata.transx(vt.cx));
10026 odata.ol.putArgs(odata.transy(vt.cy));
10027 break;
10028 case STBTT_vcubic:
10029 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo);
10030 odata.ol.putArgs(odata.transx(vt.x));
10031 odata.ol.putArgs(odata.transy(vt.y));
10032 odata.ol.putArgs(odata.transx(vt.cx));
10033 odata.ol.putArgs(odata.transy(vt.cy));
10034 odata.ol.putArgs(odata.transx(vt.cx1));
10035 odata.ol.putArgs(odata.transy(vt.cy1));
10036 break;
10037 default:
10041 okflag = true;
10044 return okflag;
10047 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc {
10048 if (bounds.length > 4) bounds = bounds.ptr[0..4];
10050 bool okflag = false;
10052 forceNoThrowNoGC({
10053 int x0, y0, x1, y1;
10054 if (stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10055 if (bounds.length > 0) bounds.ptr[0] = x0;
10056 if (bounds.length > 1) bounds.ptr[1] = -y1;
10057 if (bounds.length > 2) bounds.ptr[2] = x1;
10058 if (bounds.length > 3) bounds.ptr[3] = -y0;
10059 okflag = true;
10060 } else {
10061 bounds[] = 0;
10065 return okflag;
10068 } // check for old stb_ttf
10071 } // version
10074 // ////////////////////////////////////////////////////////////////////////// //
10075 private:
10076 enum FONS_HASH_LUT_SIZE = 256;
10077 enum FONS_INIT_FONTS = 4;
10078 enum FONS_INIT_GLYPHS = 256;
10079 enum FONS_INIT_ATLAS_NODES = 256;
10080 enum FONS_VERTEX_COUNT = 1024;
10081 enum FONS_MAX_STATES = 20;
10082 enum FONS_MAX_FALLBACKS = 20;
10085 struct FONSglyph {
10086 uint codepoint;
10087 int index;
10088 int next;
10089 short size, blur;
10090 short x0, y0, x1, y1;
10091 short xadv, xoff, yoff;
10094 // refcounted
10095 struct FONSfontData {
10096 ubyte* data;
10097 int dataSize;
10098 bool freeData;
10099 int rc;
10101 @disable this (this); // no copies
10102 void opAssign() (in auto ref FONSfontData a) { static assert(0, "no copies!"); }
10105 // won't set rc to 1
10106 FONSfontData* fons__createFontData (ubyte* adata, int asize, bool afree) nothrow @trusted @nogc {
10107 import core.stdc.stdlib : malloc;
10108 assert(adata !is null);
10109 assert(asize > 0);
10110 auto res = cast(FONSfontData*)malloc(FONSfontData.sizeof);
10111 if (res is null) assert(0, "FONS: out of memory");
10112 res.data = adata;
10113 res.dataSize = asize;
10114 res.freeData = afree;
10115 res.rc = 0;
10116 return res;
10119 void incref (FONSfontData* fd) pure nothrow @trusted @nogc {
10120 pragma(inline, true);
10121 if (fd !is null) ++fd.rc;
10124 void decref (ref FONSfontData* fd) nothrow @trusted @nogc {
10125 if (fd !is null) {
10126 if (--fd.rc == 0) {
10127 import core.stdc.stdlib : free;
10128 if (fd.freeData && fd.data !is null) {
10129 free(fd.data);
10130 fd.data = null;
10132 free(fd);
10133 fd = null;
10138 // as creating and destroying fonts is a rare operation, malloc some data
10139 struct FONSfont {
10140 FONSttFontImpl font;
10141 char* name; // malloced, strz, always lowercase
10142 uint namelen;
10143 uint namehash;
10144 char* path; // malloced, strz
10145 FONSfontData* fdata;
10146 float ascender;
10147 float descender;
10148 float lineh;
10149 FONSglyph* glyphs;
10150 int cglyphs;
10151 int nglyphs;
10152 int[FONS_HASH_LUT_SIZE] lut;
10153 int[FONS_MAX_FALLBACKS] fallbacks;
10154 int nfallbacks;
10156 @disable this (this);
10157 void opAssign() (in auto ref FONSfont a) { static assert(0, "no copies"); }
10159 static uint djbhash (const(void)[] s) pure nothrow @safe @nogc {
10160 uint hash = 5381;
10161 foreach (ubyte b; cast(const(ubyte)[])s) {
10162 if (b >= 'A' && b <= 'Z') b += 32; // poor man's tolower
10163 hash = ((hash<<5)+hash)+b;
10165 return hash;
10168 // except glyphs
10169 void freeMemory () nothrow @trusted @nogc {
10170 import core.stdc.stdlib : free;
10171 if (name !is null) { free(name); name = null; }
10172 namelen = namehash = 0;
10173 if (path !is null) { free(path); path = null; }
10174 fdata.decref();
10177 // this also calcs name hash
10178 void setName (const(char)[] aname) nothrow @trusted @nogc {
10179 //{ import core.stdc.stdio; printf("setname: [%.*s]\n", cast(uint)aname.length, aname.ptr); }
10180 import core.stdc.stdlib : realloc;
10181 if (aname.length > int.max/32) assert(0, "FONS: invalid font name");
10182 namelen = cast(uint)aname.length;
10183 name = cast(char*)realloc(name, namelen+1);
10184 if (name is null) assert(0, "FONS: out of memory");
10185 if (aname.length) name[0..aname.length] = aname[];
10186 name[namelen] = 0;
10187 // lowercase it
10188 foreach (ref char ch; name[0..namelen]) if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
10189 namehash = djbhash(name[0..namelen]);
10190 //{ import core.stdc.stdio; printf(" [%s] [%.*s] [0x%08x]\n", name, namelen, name, namehash); }
10193 void setPath (const(char)[] apath) nothrow @trusted @nogc {
10194 import core.stdc.stdlib : realloc;
10195 if (apath.length > int.max/32) assert(0, "FONS: invalid font path");
10196 path = cast(char*)realloc(path, apath.length+1);
10197 if (path is null) assert(0, "FONS: out of memory");
10198 if (apath.length) path[0..apath.length] = apath[];
10199 path[apath.length] = 0;
10202 // this won't check hash
10203 bool nameEqu (const(char)[] aname) const pure nothrow @trusted @nogc {
10204 //{ import core.stdc.stdio; printf("nameEqu: aname=[%.*s]; namelen=%u; aslen=%u\n", cast(uint)aname.length, aname.ptr, namelen, cast(uint)aname.length); }
10205 if (namelen != aname.length) return false;
10206 const(char)* ns = name;
10207 // name part
10208 foreach (char ch; aname) {
10209 if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
10210 if (ch != *ns++) return false;
10212 // done (length was checked earlier)
10213 return true;
10216 void clear () nothrow @trusted @nogc {
10217 import core.stdc.stdlib : free;
10218 import core.stdc.string : memset;
10219 if (glyphs !is null) free(glyphs);
10220 freeMemory();
10221 memset(&this, 0, this.sizeof);
10224 FONSglyph* allocGlyph () nothrow @trusted @nogc {
10225 if (nglyphs+1 > cglyphs) {
10226 import core.stdc.stdlib : realloc;
10227 cglyphs = (cglyphs == 0 ? 8 : cglyphs*2);
10228 glyphs = cast(FONSglyph*)realloc(glyphs, FONSglyph.sizeof*cglyphs);
10229 if (glyphs is null) assert(0, "FontStash: out of memory");
10231 ++nglyphs;
10232 return &glyphs[nglyphs-1];
10236 void kill (ref FONSfont* font) nothrow @trusted @nogc {
10237 if (font !is null) {
10238 import core.stdc.stdlib : free;
10239 font.clear();
10240 free(font);
10241 font = null;
10246 // ////////////////////////////////////////////////////////////////////////// //
10247 struct FONSstate {
10248 int font;
10249 NVGTextAlign talign;
10250 float size = 0;
10251 float blur = 0;
10252 float spacing = 0;
10256 // ////////////////////////////////////////////////////////////////////////// //
10257 // atlas based on Skyline Bin Packer by Jukka Jylänki
10258 alias FONSAtlas = FONSatlasInternal*;
10260 struct FONSatlasInternal {
10261 static struct Node {
10262 short x, y, width;
10265 int width, height;
10266 Node* nodes;
10267 int nnodes;
10268 int cnodes;
10270 @disable this (this);
10271 void opAssign() (in auto ref FONSatlasInternal a) { static assert(0, "no copies"); }
10273 nothrow @trusted @nogc:
10274 static FONSAtlas create (int w, int h, int nnodes) {
10275 import core.stdc.stdlib : malloc;
10276 import core.stdc.string : memset;
10278 FONSAtlas atlas = cast(FONSAtlas)malloc(FONSatlasInternal.sizeof);
10279 if (atlas is null) assert(0, "FontStash: out of memory");
10280 memset(atlas, 0, FONSatlasInternal.sizeof);
10282 atlas.width = w;
10283 atlas.height = h;
10285 // allocate space for skyline nodes
10286 atlas.nodes = cast(Node*)malloc(Node.sizeof*nnodes);
10287 if (atlas.nodes is null) assert(0, "FontStash: out of memory");
10288 memset(atlas.nodes, 0, Node.sizeof*nnodes);
10289 atlas.nnodes = 0;
10290 atlas.cnodes = nnodes;
10292 // init root node
10293 atlas.nodes[0].x = 0;
10294 atlas.nodes[0].y = 0;
10295 atlas.nodes[0].width = cast(short)w;
10296 ++atlas.nnodes;
10298 return atlas;
10301 void clear () {
10302 import core.stdc.stdlib : free;
10303 import core.stdc.string : memset;
10305 if (nodes !is null) free(nodes);
10306 memset(&this, 0, this.sizeof);
10309 void insertNode (int idx, int x, int y, int w) {
10310 if (nnodes+1 > cnodes) {
10311 import core.stdc.stdlib : realloc;
10312 cnodes = (cnodes == 0 ? 8 : cnodes*2);
10313 nodes = cast(Node*)realloc(nodes, Node.sizeof*cnodes);
10314 if (nodes is null) assert(0, "FontStash: out of memory");
10316 for (int i = nnodes; i > idx; --i) nodes[i] = nodes[i-1];
10317 nodes[idx].x = cast(short)x;
10318 nodes[idx].y = cast(short)y;
10319 nodes[idx].width = cast(short)w;
10320 ++nnodes;
10323 void removeNode (int idx) {
10324 if (nnodes == 0) return;
10325 foreach (immutable int i; idx+1..nnodes) nodes[i-1] = nodes[i];
10326 --nnodes;
10329 // insert node for empty space
10330 void expand (int w, int h) {
10331 if (w > width) insertNode(nnodes, width, 0, w-width);
10332 width = w;
10333 height = h;
10336 void reset (int w, int h) {
10337 width = w;
10338 height = h;
10339 nnodes = 0;
10340 // init root node
10341 nodes[0].x = 0;
10342 nodes[0].y = 0;
10343 nodes[0].width = cast(short)w;
10344 ++nnodes;
10347 void addSkylineLevel (int idx, int x, int y, int w, int h) {
10348 insertNode(idx, x, y+h, w);
10350 // delete skyline segments that fall under the shadow of the new segment
10351 for (int i = idx+1; i < nnodes; ++i) {
10352 if (nodes[i].x < nodes[i-1].x+nodes[i-1].width) {
10353 int shrink = nodes[i-1].x+nodes[i-1].width-nodes[i].x;
10354 nodes[i].x += cast(short)shrink;
10355 nodes[i].width -= cast(short)shrink;
10356 if (nodes[i].width <= 0) {
10357 removeNode(i);
10358 --i;
10359 } else {
10360 break;
10362 } else {
10363 break;
10367 // Merge same height skyline segments that are next to each other
10368 for (int i = 0; i < nnodes-1; ++i) {
10369 if (nodes[i].y == nodes[i+1].y) {
10370 nodes[i].width += nodes[i+1].width;
10371 removeNode(i+1);
10372 --i;
10377 // checks if there is enough space at the location of skyline span 'i',
10378 // and return the max height of all skyline spans under that at that location,
10379 // (think tetris block being dropped at that position); or -1 if no space found
10380 int rectFits (int i, int w, int h) {
10381 int x = nodes[i].x;
10382 int y = nodes[i].y;
10383 if (x+w > width) return -1;
10384 int spaceLeft = w;
10385 while (spaceLeft > 0) {
10386 if (i == nnodes) return -1;
10387 y = nvg__max(y, nodes[i].y);
10388 if (y+h > height) return -1;
10389 spaceLeft -= nodes[i].width;
10390 ++i;
10392 return y;
10395 bool addRect (int rw, int rh, int* rx, int* ry) {
10396 int besth = height, bestw = width, besti = -1;
10397 int bestx = -1, besty = -1;
10399 // Bottom left fit heuristic.
10400 for (int i = 0; i < nnodes; ++i) {
10401 int y = rectFits(i, rw, rh);
10402 if (y != -1) {
10403 if (y+rh < besth || (y+rh == besth && nodes[i].width < bestw)) {
10404 besti = i;
10405 bestw = nodes[i].width;
10406 besth = y+rh;
10407 bestx = nodes[i].x;
10408 besty = y;
10413 if (besti == -1) return false;
10415 // perform the actual packing
10416 addSkylineLevel(besti, bestx, besty, rw, rh);
10418 *rx = bestx;
10419 *ry = besty;
10421 return true;
10425 void kill (ref FONSAtlas atlas) nothrow @trusted @nogc {
10426 if (atlas !is null) {
10427 import core.stdc.stdlib : free;
10428 atlas.clear();
10429 free(atlas);
10430 atlas = null;
10435 // ////////////////////////////////////////////////////////////////////////// //
10436 /// FontStash context (internal definition). Don't use it derectly, it was made public only to generate documentation.
10437 /// Group: font_stash
10438 public struct FONScontextInternal {
10439 private:
10440 FONSParams params;
10441 float itw, ith;
10442 ubyte* texData;
10443 int[4] dirtyRect;
10444 FONSfont** fonts; // actually, a simple hash table; can't grow yet
10445 int cfonts; // allocated
10446 int nfonts; // used (so we can track hash table stats)
10447 int* hashidx; // [hsize] items; holds indicies in [fonts] array
10448 int hused, hsize;// used items and total items in [hashidx]
10449 FONSAtlas atlas;
10450 FONSstate[FONS_MAX_STATES] states;
10451 int nstates;
10453 void delegate (FONSError error, int val) nothrow @trusted @nogc handleError;
10455 @disable this (this);
10456 void opAssign() (in auto ref FONScontextInternal ctx) { static assert(0, "FONS copying is not allowed"); }
10458 private:
10459 static bool strequci (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
10460 if (s0.length != s1.length) return false;
10461 const(char)* sp0 = s0.ptr;
10462 const(char)* sp1 = s1.ptr;
10463 foreach (immutable _; 0..s0.length) {
10464 char c0 = *sp0++;
10465 char c1 = *sp1++;
10466 if (c0 != c1) {
10467 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man tolower
10468 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man tolower
10469 if (c0 != c1) return false;
10472 return true;
10475 inout(FONSstate)* getState () inout pure nothrow @trusted @nogc {
10476 pragma(inline, true);
10477 return cast(inout)(&states[(nstates > 0 ? nstates-1 : 0)]);
10480 // simple linear probing; returns [FONS_INVALID] if not found
10481 int findNameInHash (const(char)[] name) const pure nothrow @trusted @nogc {
10482 if (nfonts == 0) return FONS_INVALID;
10483 auto nhash = FONSfont.djbhash(name);
10484 //{ import core.stdc.stdio; printf("findinhash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10485 auto res = nhash%hsize;
10486 // hash will never be 100% full, so this loop is safe
10487 for (;;) {
10488 int idx = hashidx[res];
10489 if (idx == -1) break;
10490 auto font = fonts[idx];
10491 if (font is null) assert(0, "FONS internal error");
10492 if (font.namehash == nhash && font.nameEqu(name)) return idx;
10493 //{ import core.stdc.stdio; printf("findinhash chained: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10494 res = (res+1)%hsize;
10496 return FONS_INVALID;
10499 // should be called $(B before) freeing `fonts[fidx]`
10500 void removeIndexFromHash (int fidx) nothrow @trusted @nogc {
10501 if (fidx < 0 || fidx >= nfonts) assert(0, "FONS internal error");
10502 if (fonts[fidx] is null) assert(0, "FONS internal error");
10503 if (hused != nfonts) assert(0, "FONS internal error");
10504 auto nhash = fonts[fidx].namehash;
10505 auto res = nhash%hsize;
10506 // hash will never be 100% full, so this loop is safe
10507 for (;;) {
10508 int idx = hashidx[res];
10509 if (idx == -1) assert(0, "FONS INTERNAL ERROR");
10510 if (idx == fidx) {
10511 // i found her! copy rest here
10512 int nidx = (res+1)%hsize;
10513 for (;;) {
10514 if ((hashidx[res] = hashidx[nidx]) == -1) break; // so it will copy `-1` too
10515 res = nidx;
10516 nidx = (nidx+1)%hsize;
10518 return;
10520 res = (res+1)%hsize;
10524 // add font with the given index to hash
10525 // prerequisite: font should not exists in hash
10526 void addIndexToHash (int idx) nothrow @trusted @nogc {
10527 if (idx < 0 || idx >= nfonts) assert(0, "FONS internal error");
10528 if (fonts[idx] is null) assert(0, "FONS internal error");
10529 import core.stdc.stdlib : realloc;
10530 auto nhash = fonts[idx].namehash;
10531 //{ import core.stdc.stdio; printf("addtohash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10532 // allocate new hash table if there was none
10533 if (hsize == 0) {
10534 enum InitSize = 256;
10535 auto newlist = cast(int*)realloc(null, InitSize*hashidx[0].sizeof);
10536 if (newlist is null) assert(0, "FONS: out of memory");
10537 newlist[0..InitSize] = -1;
10538 hsize = InitSize;
10539 hused = 0;
10540 hashidx = newlist;
10542 int res = cast(int)(nhash%hsize);
10543 // need to rehash? we want our hash table 50% full at max
10544 if (hashidx[res] != -1 && hused >= hsize/2) {
10545 uint nsz = hsize*2;
10546 if (nsz > 1024*1024) assert(0, "FONS: out of memory for fonts");
10547 auto newlist = cast(int*)realloc(fonts, nsz*hashidx[0].sizeof);
10548 if (newlist is null) assert(0, "FONS: out of memory");
10549 newlist[0..nsz] = -1;
10550 hused = 0;
10551 // rehash
10552 foreach (immutable fidx, FONSfont* ff; fonts[0..nfonts]) {
10553 if (ff is null) continue;
10554 // find slot for this font (guaranteed to have one)
10555 uint newslot = ff.namehash%nsz;
10556 while (newlist[newslot] != -1) newslot = (newslot+1)%nsz;
10557 newlist[newslot] = cast(int)fidx;
10558 ++hused;
10560 hsize = nsz;
10561 hashidx = newlist;
10562 // we added everything, including [idx], so nothing more to do here
10563 } else {
10564 // find slot (guaranteed to have one)
10565 while (hashidx[res] != -1) res = (res+1)%hsize;
10566 // i found her!
10567 hashidx[res] = idx;
10568 ++hused;
10572 void addWhiteRect (int w, int h) nothrow @trusted @nogc {
10573 int gx, gy;
10574 ubyte* dst;
10576 if (!atlas.addRect(w, h, &gx, &gy)) return;
10578 // Rasterize
10579 dst = &texData[gx+gy*params.width];
10580 foreach (int y; 0..h) {
10581 foreach (int x; 0..w) {
10582 dst[x] = 0xff;
10584 dst += params.width;
10587 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], gx);
10588 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], gy);
10589 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], gx+w);
10590 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], gy+h);
10593 // returns fid, not hash slot
10594 int allocFontAt (int atidx) nothrow @trusted @nogc {
10595 if (atidx >= 0 && atidx >= nfonts) assert(0, "internal NanoVega fontstash error");
10597 if (atidx < 0) {
10598 if (nfonts >= cfonts) {
10599 import core.stdc.stdlib : realloc;
10600 import core.stdc.string : memset;
10601 assert(nfonts == cfonts);
10602 int newsz = cfonts+64;
10603 if (newsz > 65535) assert(0, "FONS: too many fonts");
10604 auto newlist = cast(FONSfont**)realloc(fonts, newsz*(FONSfont*).sizeof);
10605 if (newlist is null) assert(0, "FONS: out of memory");
10606 memset(newlist+cfonts, 0, (newsz-cfonts)*(FONSfont*).sizeof);
10607 fonts = newlist;
10608 cfonts = newsz;
10610 assert(nfonts < cfonts);
10613 FONSfont* font = cast(FONSfont*)malloc(FONSfont.sizeof);
10614 if (font is null) assert(0, "FONS: out of memory");
10615 memset(font, 0, FONSfont.sizeof);
10617 font.glyphs = cast(FONSglyph*)malloc(FONSglyph.sizeof*FONS_INIT_GLYPHS);
10618 if (font.glyphs is null) assert(0, "FONS: out of memory");
10619 font.cglyphs = FONS_INIT_GLYPHS;
10620 font.nglyphs = 0;
10622 if (atidx < 0) {
10623 fonts[nfonts] = font;
10624 return nfonts++;
10625 } else {
10626 fonts[atidx] = font;
10627 return atidx;
10631 // 0: ooops
10632 int findGlyphForCP (FONSfont *font, dchar dch, FONSfont** renderfont) nothrow @trusted @nogc {
10633 if (renderfont !is null) *renderfont = font;
10634 if (font is null || font.fdata is null) return 0;
10635 auto g = fons__tt_getGlyphIndex(&font.font, cast(uint)dch);
10636 // try to find the glyph in fallback fonts
10637 if (g == 0) {
10638 foreach (immutable i; 0..font.nfallbacks) {
10639 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]];
10640 if (fallbackFont !is null) {
10641 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, cast(uint)dch);
10642 if (fallbackIndex != 0) {
10643 if (renderfont !is null) *renderfont = fallbackFont;
10644 return g;
10648 // no char, try to find replacement one
10649 if (dch != 0xFFFD) {
10650 g = fons__tt_getGlyphIndex(&font.font, 0xFFFD);
10651 if (g == 0) {
10652 foreach (immutable i; 0..font.nfallbacks) {
10653 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]];
10654 if (fallbackFont !is null) {
10655 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, 0xFFFD);
10656 if (fallbackIndex != 0) {
10657 if (renderfont !is null) *renderfont = fallbackFont;
10658 return g;
10665 return g;
10668 void clear () nothrow @trusted @nogc {
10669 import core.stdc.stdlib : free;
10671 if (params.renderDelete !is null) params.renderDelete(params.userPtr);
10672 foreach (immutable int i; 0..nfonts) fonts[i].kill();
10674 if (atlas !is null) atlas.kill();
10675 if (fonts !is null) free(fonts);
10676 if (texData !is null) free(texData);
10677 if (hashidx !is null) free(hashidx);
10680 // add font from another fontstash
10681 int addCookedFont (FONSfont* font) nothrow @trusted @nogc {
10682 if (font is null || font.fdata is null) return FONS_INVALID;
10683 font.fdata.incref();
10684 auto res = addFontWithData(font.name[0..font.namelen], font.fdata, !font.font.mono);
10685 if (res == FONS_INVALID) font.fdata.decref(); // oops
10686 return res;
10689 // fdata refcount must be already increased; it won't be changed
10690 int addFontWithData (const(char)[] name, FONSfontData* fdata, bool defAA) nothrow @trusted @nogc {
10691 int i, ascent, descent, fh, lineGap;
10693 if (name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID;
10694 if (name.length > 32767) return FONS_INVALID;
10695 if (fdata is null) return FONS_INVALID;
10697 // find a font with the given name
10698 int newidx;
10699 FONSfont* oldfont = null;
10700 int oldidx = findNameInHash(name);
10701 if (oldidx != FONS_INVALID) {
10702 // replacement font
10703 oldfont = fonts[oldidx];
10704 newidx = oldidx;
10705 } else {
10706 // new font, allocate new bucket
10707 newidx = -1;
10710 newidx = allocFontAt(newidx);
10711 FONSfont* font = fonts[newidx];
10712 font.setName(name);
10713 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; // init hash lookup
10714 font.fdata = fdata; // set the font data (don't change reference count)
10715 fons__tt_setMono(&this, &font.font, !defAA);
10717 // init font
10718 if (!fons__tt_loadFont(&this, &font.font, fdata.data, fdata.dataSize)) {
10719 // we promised to not free data on error, so just clear the data store (it will be freed by the caller)
10720 font.fdata = null;
10721 font.kill();
10722 if (oldidx != FONS_INVALID) {
10723 assert(oldidx == newidx);
10724 fonts[oldidx] = oldfont;
10725 } else {
10726 assert(newidx == nfonts-1);
10727 fonts[newidx] = null;
10728 --nfonts;
10730 return FONS_INVALID;
10731 } else {
10732 // free old font data, if any
10733 if (oldfont !is null) oldfont.kill();
10736 // add font to name hash
10737 if (oldidx == FONS_INVALID) addIndexToHash(newidx);
10739 // store normalized line height
10740 // the real line height is got by multiplying the lineh by font size
10741 fons__tt_getFontVMetrics(&font.font, &ascent, &descent, &lineGap);
10742 fh = ascent-descent;
10743 font.ascender = cast(float)ascent/cast(float)fh;
10744 font.descender = cast(float)descent/cast(float)fh;
10745 font.lineh = cast(float)(fh+lineGap)/cast(float)fh;
10747 //{ import core.stdc.stdio; printf("created font [%.*s] (idx=%d)...\n", cast(uint)name.length, name.ptr, idx); }
10748 return newidx;
10751 // isize: size*10
10752 float getVertAlign (FONSfont* font, NVGTextAlign talign, short isize) pure nothrow @trusted @nogc {
10753 if (params.isZeroTopLeft) {
10754 final switch (talign.vertical) {
10755 case NVGTextAlign.V.Top: return font.ascender*cast(float)isize/10.0f;
10756 case NVGTextAlign.V.Middle: return (font.ascender+font.descender)/2.0f*cast(float)isize/10.0f;
10757 case NVGTextAlign.V.Baseline: return 0.0f;
10758 case NVGTextAlign.V.Bottom: return font.descender*cast(float)isize/10.0f;
10760 } else {
10761 final switch (talign.vertical) {
10762 case NVGTextAlign.V.Top: return -font.ascender*cast(float)isize/10.0f;
10763 case NVGTextAlign.V.Middle: return -(font.ascender+font.descender)/2.0f*cast(float)isize/10.0f;
10764 case NVGTextAlign.V.Baseline: return 0.0f;
10765 case NVGTextAlign.V.Bottom: return -font.descender*cast(float)isize/10.0f;
10768 assert(0);
10771 public:
10772 /** Create new FontStash context. It can be destroyed with `fs.kill()` later.
10774 * Note that if you don't plan to rasterize glyphs (i.e. you will use created
10775 * FontStash only to measure text), you can simply pass `FONSParams.init`).
10777 static FONSContext create() (in auto ref FONSParams params) nothrow @trusted @nogc {
10778 import core.stdc.string : memcpy;
10780 FONSContext stash = null;
10782 // allocate memory for the font stash
10783 stash = cast(FONSContext)malloc(FONScontextInternal.sizeof);
10784 if (stash is null) goto error;
10785 memset(stash, 0, FONScontextInternal.sizeof);
10787 memcpy(&stash.params, &params, params.sizeof);
10788 if (stash.params.width < 1) stash.params.width = 32;
10789 if (stash.params.height < 1) stash.params.height = 32;
10791 // initialize implementation library
10792 if (!fons__tt_init(stash)) goto error;
10794 if (stash.params.renderCreate !is null) {
10795 if (!stash.params.renderCreate(stash.params.userPtr, stash.params.width, stash.params.height)) goto error;
10798 stash.atlas = FONSAtlas.create(stash.params.width, stash.params.height, FONS_INIT_ATLAS_NODES);
10799 if (stash.atlas is null) goto error;
10801 // don't allocate space for fonts: hash manager will do that for us later
10802 //stash.cfonts = 0;
10803 //stash.nfonts = 0;
10805 // create texture for the cache
10806 stash.itw = 1.0f/stash.params.width;
10807 stash.ith = 1.0f/stash.params.height;
10808 stash.texData = cast(ubyte*)malloc(stash.params.width*stash.params.height);
10809 if (stash.texData is null) goto error;
10810 memset(stash.texData, 0, stash.params.width*stash.params.height);
10812 stash.dirtyRect.ptr[0] = stash.params.width;
10813 stash.dirtyRect.ptr[1] = stash.params.height;
10814 stash.dirtyRect.ptr[2] = 0;
10815 stash.dirtyRect.ptr[3] = 0;
10817 // add white rect at 0, 0 for debug drawing
10818 stash.addWhiteRect(2, 2);
10820 stash.pushState();
10821 stash.clearState();
10823 return stash;
10825 error:
10826 stash.kill();
10827 return null;
10830 public:
10831 /// Add fallback font (FontStash will try to find missing glyph in all fallback fonts before giving up).
10832 bool addFallbackFont (int base, int fallback) nothrow @trusted @nogc {
10833 FONSfont* baseFont = fonts[base];
10834 if (baseFont !is null && baseFont.nfallbacks < FONS_MAX_FALLBACKS) {
10835 baseFont.fallbacks.ptr[baseFont.nfallbacks++] = fallback;
10836 return true;
10838 return false;
10841 @property void size (float size) nothrow @trusted @nogc { pragma(inline, true); getState.size = size; } /// Set current font size.
10842 @property float size () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.size; } /// Get current font size.
10844 @property void spacing (float spacing) nothrow @trusted @nogc { pragma(inline, true); getState.spacing = spacing; } /// Set current letter spacing.
10845 @property float spacing () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.spacing; } /// Get current letter spacing.
10847 @property void blur (float blur) nothrow @trusted @nogc { pragma(inline, true); getState.blur = blur; } /// Set current letter blur.
10848 @property float blur () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.blur; } /// Get current letter blur.
10850 @property void textAlign (NVGTextAlign talign) nothrow @trusted @nogc { pragma(inline, true); getState.talign = talign; } /// Set current text align.
10851 @property NVGTextAlign textAlign () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.talign; } /// Get current text align.
10853 @property void fontId (int font) nothrow @trusted @nogc { pragma(inline, true); getState.font = font; } /// Set current font id.
10854 @property int fontId () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.font; } /// Get current font id.
10856 @property void fontId (const(char)[] name) nothrow @trusted @nogc { pragma(inline, true); getState.font = getFontByName(name); } /// Set current font using its name.
10858 /// Check if FontStash has a font with the given name loaded.
10859 bool hasFont (const(char)[] name) const pure nothrow @trusted @nogc { pragma(inline, true); return (getFontByName(name) >= 0); }
10861 /// Get AA for the current font, or for the specified font.
10862 bool getFontAA (int font=-1) nothrow @trusted @nogc {
10863 FONSstate* state = getState;
10864 if (font < 0) font = state.font;
10865 if (font < 0 || font >= nfonts) return false;
10866 FONSfont* f = fonts[font];
10867 return (f !is null ? !f.font.mono : false);
10870 /// Push current state. Returns `false` if state stack overflowed.
10871 bool pushState () nothrow @trusted @nogc {
10872 if (nstates >= FONS_MAX_STATES) {
10873 if (handleError !is null) handleError(FONSError.StatesOverflow, 0);
10874 return false;
10876 if (nstates > 0) {
10877 import core.stdc.string : memcpy;
10878 memcpy(&states[nstates], &states[nstates-1], FONSstate.sizeof);
10880 ++nstates;
10881 return true;
10884 /// Pop current state. Returns `false` if state stack underflowed.
10885 bool popState () nothrow @trusted @nogc {
10886 if (nstates <= 1) {
10887 if (handleError !is null) handleError(FONSError.StatesUnderflow, 0);
10888 return false;
10890 --nstates;
10891 return true;
10894 /// Clear current state (i.e. set it to some sane defaults).
10895 void clearState () nothrow @trusted @nogc {
10896 FONSstate* state = getState;
10897 state.size = 12.0f;
10898 state.font = 0;
10899 state.blur = 0;
10900 state.spacing = 0;
10901 state.talign.reset;
10904 private enum NoAlias = ":noaa";
10906 /** Add font to FontStash.
10908 * Load scalable font from disk, and add it to FontStash. If you will try to load a font
10909 * with same name and path several times, FontStash will load it only once. Also, you can
10910 * load new disk font for any existing logical font.
10912 * Params:
10913 * name = logical font name, that will be used to select this font later.
10914 * path = path to disk file with your font.
10915 * defAA = should FontStash use antialiased font rasterizer?
10917 * Returns:
10918 * font id or [FONS_INVALID].
10920 int addFont (const(char)[] name, const(char)[] path, bool defAA=false) nothrow @trusted {
10921 if (path.length == 0 || name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID;
10922 if (path.length > 32768) return FONS_INVALID; // arbitrary limit
10924 // if font path ends with ":noaa", turn off antialiasing
10925 if (path.length >= NoAlias.length && strequci(path[$-NoAlias.length..$], NoAlias)) {
10926 path = path[0..$-NoAlias.length];
10927 if (path.length == 0) return FONS_INVALID;
10928 defAA = false;
10931 // if font name ends with ":noaa", turn off antialiasing
10932 if (name.length > NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) {
10933 name = name[0..$-NoAlias.length];
10934 defAA = false;
10937 // find a font with the given name
10938 int fidx = findNameInHash(name);
10939 //{ import core.stdc.stdio; printf("loading font '%.*s' [%s] (fidx=%d)...\n", cast(uint)path.length, path.ptr, fontnamebuf.ptr, fidx); }
10941 int loadFontFile (const(char)[] path) {
10942 // check if existing font (if any) has the same path
10943 if (fidx >= 0) {
10944 import core.stdc.string : strlen;
10945 auto plen = (fonts[fidx].path !is null ? strlen(fonts[fidx].path) : 0);
10946 version(Posix) {
10947 //{ 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); }
10948 if (plen == path.length && fonts[fidx].path[0..plen] == path) {
10949 //{ import core.stdc.stdio; printf("*** font [%.*s] already loaded from [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)plen, path.ptr); }
10950 // i found her!
10951 return fidx;
10953 } else {
10954 if (plen == path.length && strequci(fonts[fidx].path[0..plen], path)) {
10955 // i found her!
10956 return fidx;
10960 version(Windows) {
10961 // special shitdows check: this will reject fontconfig font names (but still allow things like "c:myfont")
10962 foreach (immutable char ch; path[(path.length >= 2 && path[1] == ':' ? 2 : 0)..$]) if (ch == ':') return FONS_INVALID;
10964 // either no such font, or different path
10965 //{ import core.stdc.stdio; printf("trying font [%.*s] from file [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)path.length, path.ptr); }
10966 int xres = FONS_INVALID;
10967 try {
10968 import core.stdc.stdlib : free, malloc;
10969 static if (NanoVegaHasIVVFS) {
10970 auto fl = VFile(path);
10971 auto dataSize = fl.size;
10972 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID;
10973 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize);
10974 if (data is null) assert(0, "out of memory in NanoVega fontstash");
10975 scope(failure) free(data); // oops
10976 fl.rawReadExact(data[0..cast(uint)dataSize]);
10977 fl.close();
10978 } else {
10979 import core.stdc.stdio : FILE, fopen, fclose, fread, ftell, fseek;
10980 import std.internal.cstring : tempCString;
10981 auto fl = fopen(path.tempCString, "rb");
10982 if (fl is null) return FONS_INVALID;
10983 scope(exit) fclose(fl);
10984 if (fseek(fl, 0, 2/*SEEK_END*/) != 0) return FONS_INVALID;
10985 auto dataSize = ftell(fl);
10986 if (fseek(fl, 0, 0/*SEEK_SET*/) != 0) return FONS_INVALID;
10987 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID;
10988 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize);
10989 if (data is null) assert(0, "out of memory in NanoVega fontstash");
10990 scope(failure) free(data); // oops
10991 ubyte* dptr = data;
10992 auto left = cast(uint)dataSize;
10993 while (left > 0) {
10994 auto rd = fread(dptr, 1, left, fl);
10995 if (rd == 0) { free(data); return FONS_INVALID; } // unexpected EOF or reading error, it doesn't matter
10996 dptr += rd;
10997 left -= rd;
11000 scope(failure) free(data); // oops
11001 // create font data
11002 FONSfontData* fdata = fons__createFontData(data, cast(int)dataSize, true); // free data
11003 fdata.incref();
11004 xres = addFontWithData(name, fdata, defAA);
11005 if (xres == FONS_INVALID) {
11006 fdata.decref(); // this will free [data] and [fdata]
11007 } else {
11008 // remember path
11009 fonts[xres].setPath(path);
11011 } catch (Exception e) {
11012 // oops; sorry
11014 return xres;
11017 // first try direct path
11018 auto res = loadFontFile(path);
11019 // if loading failed, try fontconfig (if fontconfig is available)
11020 static if (NanoVegaHasFontConfig) {
11021 if (res == FONS_INVALID && fontconfigAvailable) {
11022 // idiotic fontconfig NEVER fails; let's skip it if `path` looks like a path
11023 bool ok = true;
11024 if (path.length > 4 && (path[$-4..$] == ".ttf" || path[$-4..$] == ".ttc")) ok = false;
11025 if (ok) { foreach (immutable char ch; path) if (ch == '/') { ok = false; break; } }
11026 if (ok) {
11027 import std.internal.cstring : tempCString;
11028 FcPattern* pat = FcNameParse(path.tempCString);
11029 if (pat !is null) {
11030 scope(exit) FcPatternDestroy(pat);
11031 if (FcConfigSubstitute(null, pat, FcMatchPattern)) {
11032 FcDefaultSubstitute(pat);
11033 // find the font
11034 FcResult result;
11035 FcPattern* font = FcFontMatch(null, pat, &result);
11036 if (font !is null) {
11037 scope(exit) FcPatternDestroy(font);
11038 char* file = null;
11039 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
11040 if (file !is null && file[0]) {
11041 import core.stdc.string : strlen;
11042 res = loadFontFile(file[0..strlen(file)]);
11051 return res;
11054 /** Add font to FontStash, using data from memory.
11056 * And already loaded font to FontStash. You can replace existing logical fonts.
11057 * But note that you can't remove logical font by passing "empty" data.
11059 * $(WARNING If [FONS_INVALID] returned, `data` won't be freed even if `freeData` is `true`.)
11061 * Params:
11062 * name = logical font name, that will be used to select this font later.
11063 * data = font data.
11064 * dataSize = font data size.
11065 * freeData = should FontStash take ownership of the font data?
11066 * defAA = should FontStash use antialiased font rasterizer?
11068 * Returns:
11069 * font id or [FONS_INVALID].
11071 int addFontMem (const(char)[] name, ubyte* data, int dataSize, bool freeData, bool defAA=false) nothrow @trusted @nogc {
11072 if (data is null || dataSize < 16) return FONS_INVALID;
11073 FONSfontData* fdata = fons__createFontData(data, dataSize, freeData);
11074 fdata.incref();
11075 auto res = addFontWithData(name, fdata, defAA);
11076 if (res == FONS_INVALID) {
11077 // we promised to not free data on error
11078 fdata.freeData = false;
11079 fdata.decref(); // this will free [fdata]
11081 return res;
11084 /** Add fonts from another FontStash.
11086 * This is more effective (and faster) than reloading fonts, because internally font data
11087 * is reference counted.
11089 void addFontsFrom (FONSContext source) nothrow @trusted @nogc {
11090 if (source is null) return;
11091 foreach (FONSfont* font; source.fonts[0..source.nfonts]) {
11092 if (font !is null) {
11093 auto newidx = addCookedFont(font);
11094 FONSfont* newfont = fonts[newidx];
11095 assert(newfont !is null);
11096 assert(newfont.path is null);
11097 // copy path
11098 if (font.path !is null && font.path[0]) {
11099 import core.stdc.stdlib : malloc;
11100 import core.stdc.string : strcpy, strlen;
11101 newfont.path = cast(char*)malloc(strlen(font.path)+1);
11102 if (newfont.path is null) assert(0, "FONS: out of memory");
11103 strcpy(newfont.path, font.path);
11109 /// Returns logical font name corresponding to the given font id, or `null`.
11110 /// $(WARNING Copy returned name, as name buffer can be invalidated by next FontStash API call!)
11111 const(char)[] getNameById (int idx) const pure nothrow @trusted @nogc {
11112 if (idx < 0 || idx >= nfonts || fonts[idx] is null) return null;
11113 return fonts[idx].name[0..fonts[idx].namelen];
11116 /// Returns font id corresponding to the given logical font name, or [FONS_INVALID].
11117 int getFontByName (const(char)[] name) const pure nothrow @trusted @nogc {
11118 //{ import core.stdc.stdio; printf("fonsGetFontByName: [%.*s]\n", cast(uint)name.length, name.ptr); }
11119 // remove ":noaa" suffix
11120 if (name.length >= NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) {
11121 name = name[0..$-NoAlias.length];
11123 if (name.length == 0) return FONS_INVALID;
11124 return findNameInHash(name);
11127 /** Measures the specified text string. Parameter bounds should be a float[4],
11128 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax]
11129 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
11131 float getTextBounds(T) (float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc if (isAnyCharType!T) {
11132 FONSstate* state = getState;
11133 uint codepoint;
11134 uint utf8state = 0;
11135 FONSQuad q;
11136 FONSglyph* glyph = null;
11137 int prevGlyphIndex = -1;
11138 short isize = cast(short)(state.size*10.0f);
11139 short iblur = cast(short)state.blur;
11140 FONSfont* font;
11142 if (state.font < 0 || state.font >= nfonts) return 0;
11143 font = fonts[state.font];
11144 if (font is null || font.fdata is null) return 0;
11146 float scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
11148 // Align vertically.
11149 y += getVertAlign(font, state.talign, isize);
11151 float minx = x, maxx = x;
11152 float miny = y, maxy = y;
11153 float startx = x;
11155 foreach (T ch; str) {
11156 static if (T.sizeof == 1) {
11157 //if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;
11158 mixin(DecUtfMixin!("utf8state", "codepoint", "(cast(ubyte)ch)"));
11159 if (utf8state) continue;
11160 } else {
11161 static if (T.sizeof == 4) {
11162 if (ch > dchar.max) ch = 0xFFFD;
11164 codepoint = cast(uint)ch;
11166 glyph = getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional);
11167 if (glyph !is null) {
11168 getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q);
11169 if (q.x0 < minx) minx = q.x0;
11170 if (q.x1 > maxx) maxx = q.x1;
11171 if (params.isZeroTopLeft) {
11172 if (q.y0 < miny) miny = q.y0;
11173 if (q.y1 > maxy) maxy = q.y1;
11174 } else {
11175 if (q.y1 < miny) miny = q.y1;
11176 if (q.y0 > maxy) maxy = q.y0;
11178 prevGlyphIndex = glyph.index;
11179 } else {
11180 //{ import core.stdc.stdio; printf("NO GLYPH FOR 0x%04x\n", cast(uint)codepoint); }
11181 prevGlyphIndex = -1;
11185 float advance = x-startx;
11186 //{ import core.stdc.stdio; printf("***: x=%g; startx=%g; advance=%g\n", cast(double)x, cast(double)startx, cast(double)advance); }
11188 // Align horizontally
11189 if (state.talign.left) {
11190 // empty
11191 } else if (state.talign.right) {
11192 minx -= advance;
11193 maxx -= advance;
11194 } else if (state.talign.center) {
11195 minx -= advance*0.5f;
11196 maxx -= advance*0.5f;
11199 if (bounds.length) {
11200 if (bounds.length > 0) bounds.ptr[0] = minx;
11201 if (bounds.length > 1) bounds.ptr[1] = miny;
11202 if (bounds.length > 2) bounds.ptr[2] = maxx;
11203 if (bounds.length > 3) bounds.ptr[3] = maxy;
11206 return advance;
11209 /// Returns various font metrics. Any argument can be `null` if you aren't interested in its value.
11210 void getVertMetrics (float* ascender, float* descender, float* lineh) nothrow @trusted @nogc {
11211 FONSstate* state = getState;
11212 if (state.font < 0 || state.font >= nfonts) {
11213 if (ascender !is null) *ascender = 0;
11214 if (descender !is null) *descender = 0;
11215 if (lineh !is null) *lineh = 0;
11216 } else {
11217 FONSfont* font = fonts[state.font];
11218 if (font is null || font.fdata is null) {
11219 if (ascender !is null) *ascender = 0;
11220 if (descender !is null) *descender = 0;
11221 if (lineh !is null) *lineh = 0;
11222 } else {
11223 short isize = cast(short)(state.size*10.0f);
11224 if (ascender !is null) *ascender = font.ascender*isize/10.0f;
11225 if (descender !is null) *descender = font.descender*isize/10.0f;
11226 if (lineh !is null) *lineh = font.lineh*isize/10.0f;
11231 /// Returns line bounds. Any argument can be `null` if you aren't interested in its value.
11232 void getLineBounds (float y, float* minyp, float* maxyp) nothrow @trusted @nogc {
11233 FONSfont* font;
11234 FONSstate* state = getState;
11235 short isize;
11237 if (minyp !is null) *minyp = 0;
11238 if (maxyp !is null) *maxyp = 0;
11240 if (state.font < 0 || state.font >= nfonts) return;
11241 font = fonts[state.font];
11242 isize = cast(short)(state.size*10.0f);
11243 if (font is null || font.fdata is null) return;
11245 y += getVertAlign(font, state.talign, isize);
11247 if (params.isZeroTopLeft) {
11248 immutable float miny = y-font.ascender*cast(float)isize/10.0f;
11249 immutable float maxy = miny+font.lineh*isize/10.0f;
11250 if (minyp !is null) *minyp = miny;
11251 if (maxyp !is null) *maxyp = maxy;
11252 } else {
11253 immutable float maxy = y+font.descender*cast(float)isize/10.0f;
11254 immutable float miny = maxy-font.lineh*isize/10.0f;
11255 if (minyp !is null) *minyp = miny;
11256 if (maxyp !is null) *maxyp = maxy;
11260 /// Returns font line height.
11261 float fontHeight () nothrow @trusted @nogc {
11262 float res = void;
11263 getVertMetrics(null, null, &res);
11264 return res;
11267 /// Returns font ascender (positive).
11268 float fontAscender () nothrow @trusted @nogc {
11269 float res = void;
11270 getVertMetrics(&res, null, null);
11271 return res;
11274 /// Returns font descender (negative).
11275 float fontDescender () nothrow @trusted @nogc {
11276 float res = void;
11277 getVertMetrics(null, &res, null);
11278 return res;
11281 //TODO: document this
11282 const(ubyte)* getTextureData (int* width, int* height) nothrow @trusted @nogc {
11283 if (width !is null) *width = params.width;
11284 if (height !is null) *height = params.height;
11285 return texData;
11288 //TODO: document this
11289 bool validateTexture (int* dirty) nothrow @trusted @nogc {
11290 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) {
11291 dirty[0] = dirtyRect.ptr[0];
11292 dirty[1] = dirtyRect.ptr[1];
11293 dirty[2] = dirtyRect.ptr[2];
11294 dirty[3] = dirtyRect.ptr[3];
11295 // reset dirty rect
11296 dirtyRect.ptr[0] = params.width;
11297 dirtyRect.ptr[1] = params.height;
11298 dirtyRect.ptr[2] = 0;
11299 dirtyRect.ptr[3] = 0;
11300 return true;
11302 return false;
11305 //TODO: document this
11306 void errorCallback (void delegate (FONSError error, int val) nothrow @trusted @nogc callback) nothrow @trusted @nogc {
11307 handleError = callback;
11310 //TODO: document this
11311 void getAtlasSize (int* width, int* height) const pure nothrow @trusted @nogc {
11312 if (width !is null) *width = params.width;
11313 if (height !is null) *height = params.height;
11316 //TODO: document this
11317 bool expandAtlas (int width, int height) nothrow @trusted @nogc {
11318 import core.stdc.stdlib : free;
11319 import core.stdc.string : memcpy, memset;
11321 int maxy = 0;
11322 ubyte* data = null;
11324 width = nvg__max(width, params.width);
11325 height = nvg__max(height, params.height);
11327 if (width == params.width && height == params.height) return true;
11329 // Flush pending glyphs.
11330 flush();
11332 // Create new texture
11333 if (params.renderResize !is null) {
11334 if (params.renderResize(params.userPtr, width, height) == 0) return false;
11336 // Copy old texture data over.
11337 data = cast(ubyte*)malloc(width*height);
11338 if (data is null) return 0;
11339 foreach (immutable int i; 0..params.height) {
11340 ubyte* dst = &data[i*width];
11341 ubyte* src = &texData[i*params.width];
11342 memcpy(dst, src, params.width);
11343 if (width > params.width) memset(dst+params.width, 0, width-params.width);
11345 if (height > params.height) memset(&data[params.height*width], 0, (height-params.height)*width);
11347 free(texData);
11348 texData = data;
11350 // Increase atlas size
11351 atlas.expand(width, height);
11353 // Add existing data as dirty.
11354 foreach (immutable int i; 0..atlas.nnodes) maxy = nvg__max(maxy, atlas.nodes[i].y);
11355 dirtyRect.ptr[0] = 0;
11356 dirtyRect.ptr[1] = 0;
11357 dirtyRect.ptr[2] = params.width;
11358 dirtyRect.ptr[3] = maxy;
11360 params.width = width;
11361 params.height = height;
11362 itw = 1.0f/params.width;
11363 ith = 1.0f/params.height;
11365 return true;
11368 //TODO: document this
11369 bool resetAtlas (int width, int height) nothrow @trusted @nogc {
11370 import core.stdc.stdlib : realloc;
11371 import core.stdc.string : memcpy, memset;
11373 // flush pending glyphs
11374 flush();
11376 // create new texture
11377 if (params.renderResize !is null) {
11378 if (params.renderResize(params.userPtr, width, height) == 0) return false;
11381 // reset atlas
11382 atlas.reset(width, height);
11384 // clear texture data
11385 texData = cast(ubyte*)realloc(texData, width*height);
11386 if (texData is null) assert(0, "FONS: out of memory");
11387 memset(texData, 0, width*height);
11389 // reset dirty rect
11390 dirtyRect.ptr[0] = width;
11391 dirtyRect.ptr[1] = height;
11392 dirtyRect.ptr[2] = 0;
11393 dirtyRect.ptr[3] = 0;
11395 // Reset cached glyphs
11396 foreach (FONSfont* font; fonts[0..nfonts]) {
11397 if (font !is null) {
11398 font.nglyphs = 0;
11399 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1;
11403 params.width = width;
11404 params.height = height;
11405 itw = 1.0f/params.width;
11406 ith = 1.0f/params.height;
11408 // Add white rect at 0, 0 for debug drawing.
11409 addWhiteRect(2, 2);
11411 return true;
11414 //TODO: document this
11415 bool getPathBounds (dchar dch, float[] bounds) nothrow @trusted @nogc {
11416 if (bounds.length > 4) bounds = bounds.ptr[0..4];
11417 static if (is(typeof(&fons__nvg__bounds))) {
11418 FONSstate* state = getState;
11419 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; }
11420 FONSfont* font;
11421 auto g = findGlyphForCP(fonts[state.font], dch, &font);
11422 if (g == 0) { bounds[] = 0; return false; }
11423 assert(font !is null);
11424 return fons__nvg__bounds(&font.font, g, bounds);
11425 } else {
11426 bounds[] = 0;
11427 return false;
11431 //TODO: document this
11432 bool toPath() (NVGContext vg, dchar dch, float[] bounds=null) nothrow @trusted @nogc {
11433 if (bounds.length > 4) bounds = bounds.ptr[0..4];
11434 static if (is(typeof(&fons__nvg__toPath))) {
11435 if (vg is null) { bounds[] = 0; return false; }
11436 FONSstate* state = getState;
11437 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; }
11438 FONSfont* font;
11439 auto g = findGlyphForCP(fonts[state.font], dch, &font);
11440 if (g == 0) { bounds[] = 0; return false; }
11441 assert(font !is null);
11442 return fons__nvg__toPath(vg, &font.font, g, bounds);
11443 } else {
11444 bounds[] = 0;
11445 return false;
11449 //TODO: document this
11450 bool toOutline (dchar dch, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
11451 if (ol is null) return false;
11452 static if (is(typeof(&fons__nvg__toOutline))) {
11453 FONSstate* state = getState;
11454 if (state.font < 0 || state.font >= nfonts) return false;
11455 FONSfont* font;
11456 auto g = findGlyphForCP(fonts[state.font], dch, &font);
11457 if (g == 0) return false;
11458 assert(font !is null);
11459 return fons__nvg__toOutline(&font.font, g, ol);
11460 } else {
11461 return false;
11465 //TODO: document this
11466 FONSglyph* getGlyph (FONSfont* font, uint codepoint, short isize, short iblur, FONSBitmapFlag bitmapOption) nothrow @trusted @nogc {
11467 static uint fons__hashint() (uint a) pure nothrow @safe @nogc {
11468 pragma(inline, true);
11469 a += ~(a<<15);
11470 a ^= (a>>10);
11471 a += (a<<3);
11472 a ^= (a>>6);
11473 a += ~(a<<11);
11474 a ^= (a>>16);
11475 return a;
11478 // based on Exponential blur, Jani Huhtanen, 2006
11479 enum APREC = 16;
11480 enum ZPREC = 7;
11482 static void fons__blurCols (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc {
11483 foreach (immutable int y; 0..h) {
11484 int z = 0; // force zero border
11485 foreach (int x; 1..w) {
11486 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC;
11487 dst[x] = cast(ubyte)(z>>ZPREC);
11489 dst[w-1] = 0; // force zero border
11490 z = 0;
11491 for (int x = w-2; x >= 0; --x) {
11492 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC;
11493 dst[x] = cast(ubyte)(z>>ZPREC);
11495 dst[0] = 0; // force zero border
11496 dst += dstStride;
11500 static void fons__blurRows (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc {
11501 foreach (immutable int x; 0..w) {
11502 int z = 0; // force zero border
11503 for (int y = dstStride; y < h*dstStride; y += dstStride) {
11504 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC;
11505 dst[y] = cast(ubyte)(z>>ZPREC);
11507 dst[(h-1)*dstStride] = 0; // force zero border
11508 z = 0;
11509 for (int y = (h-2)*dstStride; y >= 0; y -= dstStride) {
11510 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC;
11511 dst[y] = cast(ubyte)(z>>ZPREC);
11513 dst[0] = 0; // force zero border
11514 ++dst;
11518 static void fons__blur (ubyte* dst, int w, int h, int dstStride, int blur) nothrow @trusted @nogc {
11519 import std.math : expf = exp;
11520 if (blur < 1) return;
11521 // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity)
11522 immutable float sigma = cast(float)blur*0.57735f; // 1/sqrt(3)
11523 int alpha = cast(int)((1<<APREC)*(1.0f-expf(-2.3f/(sigma+1.0f))));
11524 fons__blurRows(dst, w, h, dstStride, alpha);
11525 fons__blurCols(dst, w, h, dstStride, alpha);
11526 fons__blurRows(dst, w, h, dstStride, alpha);
11527 fons__blurCols(dst, w, h, dstStride, alpha);
11528 //fons__blurrows(dst, w, h, dstStride, alpha);
11529 //fons__blurcols(dst, w, h, dstStride, alpha);
11532 int advance, lsb, x0, y0, x1, y1, gx, gy;
11533 FONSglyph* glyph = null;
11534 float size = isize/10.0f;
11535 FONSfont* renderFont = font;
11537 if (isize < 2) return null;
11538 if (iblur > 20) iblur = 20;
11539 int pad = iblur+2;
11541 // Find code point and size.
11542 uint h = fons__hashint(codepoint)&(FONS_HASH_LUT_SIZE-1);
11543 int i = font.lut.ptr[h];
11544 while (i != -1) {
11545 //if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) return &font.glyphs[i];
11546 if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) {
11547 glyph = &font.glyphs[i];
11548 // Negative coordinate indicates there is no bitmap data created.
11549 if (bitmapOption == FONSBitmapFlag.Optional || (glyph.x0 >= 0 && glyph.y0 >= 0)) return glyph;
11550 // At this point, glyph exists but the bitmap data is not yet created.
11551 break;
11553 i = font.glyphs[i].next;
11556 // Create a new glyph or rasterize bitmap data for a cached glyph.
11557 //scale = fons__tt_getPixelHeightScale(&font.font, size);
11558 int g = findGlyphForCP(font, cast(dchar)codepoint, &renderFont);
11559 // It is possible that we did not find a fallback glyph.
11560 // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph.
11562 float scale = fons__tt_getPixelHeightScale(&renderFont.font, size);
11563 fons__tt_buildGlyphBitmap(&renderFont.font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1);
11564 int gw = x1-x0+pad*2;
11565 int gh = y1-y0+pad*2;
11567 // Determines the spot to draw glyph in the atlas.
11568 if (bitmapOption == FONSBitmapFlag.Required) {
11569 // Find free spot for the rect in the atlas.
11570 bool added = atlas.addRect(gw, gh, &gx, &gy);
11571 if (!added && handleError !is null) {
11572 // Atlas is full, let the user to resize the atlas (or not), and try again.
11573 handleError(FONSError.AtlasFull, 0);
11574 added = atlas.addRect(gw, gh, &gx, &gy);
11576 if (!added) return null;
11577 } else {
11578 // Negative coordinate indicates there is no bitmap data created.
11579 gx = -1;
11580 gy = -1;
11583 // Init glyph.
11584 if (glyph is null) {
11585 glyph = font.allocGlyph();
11586 glyph.codepoint = codepoint;
11587 glyph.size = isize;
11588 glyph.blur = iblur;
11589 glyph.next = 0;
11591 // Insert char to hash lookup.
11592 glyph.next = font.lut.ptr[h];
11593 font.lut.ptr[h] = font.nglyphs-1;
11595 glyph.index = g;
11596 glyph.x0 = cast(short)gx;
11597 glyph.y0 = cast(short)gy;
11598 glyph.x1 = cast(short)(glyph.x0+gw);
11599 glyph.y1 = cast(short)(glyph.y0+gh);
11600 glyph.xadv = cast(short)(scale*advance*10.0f);
11601 glyph.xoff = cast(short)(x0-pad);
11602 glyph.yoff = cast(short)(y0-pad);
11604 if (bitmapOption == FONSBitmapFlag.Optional) return glyph;
11606 // Rasterize
11607 ubyte* dst = &texData[(glyph.x0+pad)+(glyph.y0+pad)*params.width];
11608 fons__tt_renderGlyphBitmap(&font.font, dst, gw-pad*2, gh-pad*2, params.width, scale, scale, g);
11610 // Make sure there is one pixel empty border.
11611 dst = &texData[glyph.x0+glyph.y0*params.width];
11612 foreach (immutable int y; 0..gh) {
11613 dst[y*params.width] = 0;
11614 dst[gw-1+y*params.width] = 0;
11616 foreach (immutable int x; 0..gw) {
11617 dst[x] = 0;
11618 dst[x+(gh-1)*params.width] = 0;
11621 // Debug code to color the glyph background
11622 version(none) {
11623 foreach (immutable yy; 0..gh) {
11624 foreach (immutable xx; 0..gw) {
11625 int a = cast(int)dst[xx+yy*params.width]+42;
11626 if (a > 255) a = 255;
11627 dst[xx+yy*params.width] = cast(ubyte)a;
11632 // Blur
11633 if (iblur > 0) {
11634 ubyte* bdst = &texData[glyph.x0+glyph.y0*params.width];
11635 fons__blur(bdst, gw, gh, params.width, iblur);
11638 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], glyph.x0);
11639 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], glyph.y0);
11640 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], glyph.x1);
11641 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], glyph.y1);
11643 return glyph;
11646 //TODO: document this
11647 void getQuad (FONSfont* font, int prevGlyphIndex, FONSglyph* glyph, float size, float scale, float spacing, float* x, float* y, FONSQuad* q) nothrow @trusted @nogc {
11648 if (prevGlyphIndex >= 0) {
11649 immutable float adv = fons__tt_getGlyphKernAdvance(&font.font, size, prevGlyphIndex, glyph.index)/**scale*/; //k8: do we really need scale here?
11650 //if (adv != 0) { import core.stdc.stdio; printf("adv=%g (scale=%g; spacing=%g)\n", cast(double)adv, cast(double)scale, cast(double)spacing); }
11651 *x += cast(int)(adv+spacing /*+0.5f*/); //k8: for me, it looks better this way (with non-aa fonts)
11654 // Each glyph has 2px border to allow good interpolation,
11655 // one pixel to prevent leaking, and one to allow good interpolation for rendering.
11656 // Inset the texture region by one pixel for correct interpolation.
11657 immutable float xoff = cast(short)(glyph.xoff+1);
11658 immutable float yoff = cast(short)(glyph.yoff+1);
11659 immutable float x0 = cast(float)(glyph.x0+1);
11660 immutable float y0 = cast(float)(glyph.y0+1);
11661 immutable float x1 = cast(float)(glyph.x1-1);
11662 immutable float y1 = cast(float)(glyph.y1-1);
11664 if (params.isZeroTopLeft) {
11665 immutable float rx = cast(float)cast(int)(*x+xoff);
11666 immutable float ry = cast(float)cast(int)(*y+yoff);
11668 q.x0 = rx;
11669 q.y0 = ry;
11670 q.x1 = rx+x1-x0;
11671 q.y1 = ry+y1-y0;
11673 q.s0 = x0*itw;
11674 q.t0 = y0*ith;
11675 q.s1 = x1*itw;
11676 q.t1 = y1*ith;
11677 } else {
11678 immutable float rx = cast(float)cast(int)(*x+xoff);
11679 immutable float ry = cast(float)cast(int)(*y-yoff);
11681 q.x0 = rx;
11682 q.y0 = ry;
11683 q.x1 = rx+x1-x0;
11684 q.y1 = ry-y1+y0;
11686 q.s0 = x0*itw;
11687 q.t0 = y0*ith;
11688 q.s1 = x1*itw;
11689 q.t1 = y1*ith;
11692 *x += cast(int)(glyph.xadv/10.0f+0.5f);
11695 void flush () nothrow @trusted @nogc {
11696 // flush texture
11697 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) {
11698 if (params.renderUpdate !is null) params.renderUpdate(params.userPtr, dirtyRect.ptr, texData);
11699 // reset dirty rect
11700 dirtyRect.ptr[0] = params.width;
11701 dirtyRect.ptr[1] = params.height;
11702 dirtyRect.ptr[2] = 0;
11703 dirtyRect.ptr[3] = 0;
11708 /// Free all resources used by the `stash`, and `stash` itself.
11709 /// Group: font_stash
11710 public void kill (ref FONSContext stash) nothrow @trusted @nogc {
11711 import core.stdc.stdlib : free;
11712 if (stash is null) return;
11713 stash.clear();
11714 free(stash);
11715 stash = null;
11719 // ////////////////////////////////////////////////////////////////////////// //
11720 // Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de>
11721 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
11723 enum FONS_UTF8_ACCEPT = 0;
11724 enum FONS_UTF8_REJECT = 12;
11726 static immutable ubyte[364] utf8d = [
11727 // The first part of the table maps bytes to character classes that
11728 // to reduce the size of the transition table and create bitmasks.
11729 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,
11730 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,
11731 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,
11732 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,
11733 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,
11734 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,
11735 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,
11736 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,
11738 // The second part is a transition table that maps a combination
11739 // of a state of the automaton and a character class to a state.
11740 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
11741 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12,
11742 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12,
11743 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12,
11744 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
11747 private enum DecUtfMixin(string state, string codep, string byte_) =
11749 uint type_ = utf8d.ptr[`~byte_~`];
11750 `~codep~` = (`~state~` != FONS_UTF8_ACCEPT ? (`~byte_~`&0x3fu)|(`~codep~`<<6) : (0xff>>type_)&`~byte_~`);
11751 if ((`~state~` = utf8d.ptr[256+`~state~`+type_]) == FONS_UTF8_REJECT) {
11752 `~state~` = FONS_UTF8_ACCEPT;
11753 `~codep~` = 0xFFFD;
11758 uint fons__decutf8 (uint* state, uint* codep, uint byte_) {
11759 pragma(inline, true);
11760 uint type = utf8d.ptr[byte_];
11761 *codep = (*state != FONS_UTF8_ACCEPT ? (byte_&0x3fu)|(*codep<<6) : (0xff>>type)&byte_);
11762 *state = utf8d.ptr[256 + *state+type];
11763 return *state;
11768 // ////////////////////////////////////////////////////////////////////////// //
11769 /// This iterator can be used to do text measurement.
11770 /// $(WARNING Don't add new fonts to stash while you are iterating, or you WILL get segfault!)
11771 /// Group: font_stash
11772 public struct FONSTextBoundsIterator {
11773 private:
11774 FONSContext stash;
11775 FONSstate state;
11776 uint codepoint = 0xFFFD;
11777 uint utf8state = 0;
11778 int prevGlyphIndex = -1;
11779 short isize, iblur;
11780 float scale = 0;
11781 FONSfont* font;
11782 float startx = 0, x = 0, y = 0;
11783 float minx = 0, miny = 0, maxx = 0, maxy = 0;
11785 private:
11786 void clear () nothrow @trusted @nogc {
11787 import core.stdc.string : memset;
11788 memset(&this, 0, this.sizeof);
11789 this.prevGlyphIndex = -1;
11790 this.codepoint = 0xFFFD;
11793 public:
11794 /// Initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator.
11795 this (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { reset(astash, ax, ay); }
11797 /// (Re)initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator.
11798 void reset (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc {
11799 clear();
11801 if (astash is null || astash.nstates == 0) return;
11803 stash = astash;
11804 state = *stash.getState;
11806 if (state.font < 0 || state.font >= stash.nfonts) { clear(); return; }
11807 font = stash.fonts[state.font];
11808 if (font is null || font.fdata is null) { clear(); return; }
11810 x = ax;
11811 y = ay;
11812 isize = cast(short)(state.size*10.0f);
11813 iblur = cast(short)state.blur;
11814 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
11816 // align vertically
11817 y += astash.getVertAlign(font, state.talign, isize);
11819 minx = maxx = x;
11820 miny = maxy = y;
11821 startx = x;
11824 /// Can this iterator be used?
11825 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null); }
11827 /// Put some text into iterator, calculate new values.
11828 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) {
11829 enum DoCodePointMixin = q{
11830 glyph = stash.getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional);
11831 if (glyph !is null) {
11832 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q);
11833 if (q.x0 < minx) minx = q.x0;
11834 if (q.x1 > maxx) maxx = q.x1;
11835 if (stash.params.isZeroTopLeft) {
11836 if (q.y0 < miny) miny = q.y0;
11837 if (q.y1 > maxy) maxy = q.y1;
11838 } else {
11839 if (q.y1 < miny) miny = q.y1;
11840 if (q.y0 > maxy) maxy = q.y0;
11842 prevGlyphIndex = glyph.index;
11843 } else {
11844 prevGlyphIndex = -1;
11848 if (stash is null || str.length == 0) return; // alas
11850 FONSQuad q;
11851 FONSglyph* glyph;
11853 static if (is(T == char)) {
11854 foreach (char ch; str) {
11855 mixin(DecUtfMixin!("utf8state", "codepoint", "cast(ubyte)ch"));
11856 if (utf8state) continue; // full char is not collected yet
11857 mixin(DoCodePointMixin);
11859 } else {
11860 if (utf8state) {
11861 utf8state = 0;
11862 codepoint = 0xFFFD;
11863 mixin(DoCodePointMixin);
11865 foreach (T dch; str) {
11866 static if (is(T == dchar)) {
11867 if (dch > dchar.max) dch = 0xFFFD;
11869 codepoint = cast(uint)dch;
11870 mixin(DoCodePointMixin);
11875 /// Returns current advance.
11876 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null ? x-startx : 0); }
11878 /// Returns current text bounds.
11879 void getBounds (ref float[4] bounds) const pure nothrow @safe @nogc {
11880 if (stash is null) { bounds[] = 0; return; }
11881 float lminx = minx, lmaxx = maxx;
11882 // align horizontally
11883 if (state.talign.left) {
11884 // empty
11885 } else if (state.talign.right) {
11886 float ca = advance;
11887 lminx -= ca;
11888 lmaxx -= ca;
11889 } else if (state.talign.center) {
11890 float ca = advance*0.5f;
11891 lminx -= ca;
11892 lmaxx -= ca;
11894 bounds[0] = lminx;
11895 bounds[1] = miny;
11896 bounds[2] = lmaxx;
11897 bounds[3] = maxy;
11900 /// Returns current horizontal text bounds.
11901 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc {
11902 if (stash !is null) {
11903 float lminx = minx, lmaxx = maxx;
11904 // align horizontally
11905 if (state.talign.left) {
11906 // empty
11907 } else if (state.talign.right) {
11908 float ca = advance;
11909 lminx -= ca;
11910 lmaxx -= ca;
11911 } else if (state.talign.center) {
11912 float ca = advance*0.5f;
11913 lminx -= ca;
11914 lmaxx -= ca;
11916 xmin = lminx;
11917 xmax = lmaxx;
11918 } else {
11919 xmin = xmax = 0;
11923 /// Returns current vertical text bounds.
11924 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc {
11925 pragma(inline, true);
11926 if (stash !is null) {
11927 ymin = miny;
11928 ymax = maxy;
11929 } else {
11930 ymin = ymax = 0;
11934 /// Returns font line height.
11935 float lineHeight () nothrow @trusted @nogc {
11936 pragma(inline, true);
11937 return (stash !is null ? stash.fonts[state.font].lineh*cast(short)(state.size*10.0f)/10.0f : 0);
11940 /// Returns font ascender (positive).
11941 float ascender () nothrow @trusted @nogc {
11942 pragma(inline, true);
11943 return (stash !is null ? stash.fonts[state.font].ascender*cast(short)(state.size*10.0f)/10.0f : 0);
11946 /// Returns font descender (negative).
11947 float descender () nothrow @trusted @nogc {
11948 pragma(inline, true);
11949 return (stash !is null ? stash.fonts[state.font].descender*cast(short)(state.size*10.0f)/10.0f : 0);
11954 // ////////////////////////////////////////////////////////////////////////// //
11955 // backgl
11956 // ////////////////////////////////////////////////////////////////////////// //
11957 import core.stdc.stdlib : malloc, realloc, free;
11958 import core.stdc.string : memcpy, memset;
11960 static if (__VERSION__ < 2076) {
11961 private auto DGNoThrowNoGC(T) (scope T t) /*if (isFunctionPointer!T || isDelegate!T)*/ {
11962 import std.traits;
11963 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
11964 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
11969 //import arsd.simpledisplay;
11970 version(nanovg_builtin_opengl_bindings) { import arsd.simpledisplay; } else { import iv.glbinds; }
11972 private:
11973 // sdpy is missing that yet
11974 static if (!is(typeof(GL_STENCIL_BUFFER_BIT))) enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
11977 // OpenGL API missing from simpledisplay
11978 private extern(System) nothrow @nogc {
11979 alias GLvoid = void;
11980 alias GLboolean = ubyte;
11981 alias GLuint = uint;
11982 alias GLenum = uint;
11983 alias GLchar = char;
11984 alias GLsizei = int;
11985 alias GLfloat = float;
11986 alias GLsizeiptr = ptrdiff_t;
11988 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
11990 enum uint GL_INVALID_ENUM = 0x0500;
11992 enum uint GL_ZERO = 0;
11993 enum uint GL_ONE = 1;
11995 enum uint GL_FLOAT = 0x1406;
11997 enum uint GL_STREAM_DRAW = 0x88E0;
11999 enum uint GL_CCW = 0x0901;
12001 enum uint GL_STENCIL_TEST = 0x0B90;
12002 enum uint GL_SCISSOR_TEST = 0x0C11;
12004 enum uint GL_EQUAL = 0x0202;
12005 enum uint GL_NOTEQUAL = 0x0205;
12007 enum uint GL_ALWAYS = 0x0207;
12008 enum uint GL_KEEP = 0x1E00;
12010 enum uint GL_INCR = 0x1E02;
12012 enum uint GL_INCR_WRAP = 0x8507;
12013 enum uint GL_DECR_WRAP = 0x8508;
12015 enum uint GL_CULL_FACE = 0x0B44;
12016 enum uint GL_BACK = 0x0405;
12018 enum uint GL_FRAGMENT_SHADER = 0x8B30;
12019 enum uint GL_VERTEX_SHADER = 0x8B31;
12021 enum uint GL_COMPILE_STATUS = 0x8B81;
12022 enum uint GL_LINK_STATUS = 0x8B82;
12024 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
12025 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
12026 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
12027 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
12029 enum uint GL_GENERATE_MIPMAP = 0x8191;
12030 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
12032 enum uint GL_RED = 0x1903;
12034 enum uint GL_TEXTURE0 = 0x84C0U;
12035 enum uint GL_TEXTURE1 = 0x84C1U;
12037 enum uint GL_ARRAY_BUFFER = 0x8892;
12039 enum uint GL_SRC_COLOR = 0x0300;
12040 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
12041 enum uint GL_SRC_ALPHA = 0x0302;
12042 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
12043 enum uint GL_DST_ALPHA = 0x0304;
12044 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
12045 enum uint GL_DST_COLOR = 0x0306;
12046 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
12047 enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
12049 enum uint GL_INVERT = 0x150AU;
12051 enum uint GL_DEPTH_STENCIL = 0x84F9U;
12052 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
12054 enum uint GL_FRAMEBUFFER = 0x8D40U;
12055 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
12056 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
12058 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
12059 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
12060 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
12061 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
12062 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
12064 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
12065 enum uint GL_CLEAR = 0x1500U;
12066 enum uint GL_COPY = 0x1503U;
12067 enum uint GL_XOR = 0x1506U;
12069 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
12072 version(Windows) {
12073 private void* kglLoad (const(char)* name) {
12074 void* res = glGetProcAddress(name);
12075 if (res is null) {
12076 import core.sys.windows.windef, core.sys.windows.winbase;
12077 static HINSTANCE dll = null;
12078 if (dll is null) {
12079 dll = LoadLibraryA("opengl32.dll");
12080 if (dll is null) return null; // <32, but idc
12081 return GetProcAddress(dll, name);
12085 } else {
12086 alias kglLoad = glGetProcAddress;
12090 alias glbfn_glStencilMask = void function(GLuint);
12091 __gshared glbfn_glStencilMask glStencilMask_NVGLZ; alias glStencilMask = glStencilMask_NVGLZ;
12092 alias glbfn_glStencilFunc = void function(GLenum, GLint, GLuint);
12093 __gshared glbfn_glStencilFunc glStencilFunc_NVGLZ; alias glStencilFunc = glStencilFunc_NVGLZ;
12094 alias glbfn_glGetShaderInfoLog = void function(GLuint, GLsizei, GLsizei*, GLchar*);
12095 __gshared glbfn_glGetShaderInfoLog glGetShaderInfoLog_NVGLZ; alias glGetShaderInfoLog = glGetShaderInfoLog_NVGLZ;
12096 alias glbfn_glGetProgramInfoLog = void function(GLuint, GLsizei, GLsizei*, GLchar*);
12097 __gshared glbfn_glGetProgramInfoLog glGetProgramInfoLog_NVGLZ; alias glGetProgramInfoLog = glGetProgramInfoLog_NVGLZ;
12098 alias glbfn_glCreateProgram = GLuint function();
12099 __gshared glbfn_glCreateProgram glCreateProgram_NVGLZ; alias glCreateProgram = glCreateProgram_NVGLZ;
12100 alias glbfn_glCreateShader = GLuint function(GLenum);
12101 __gshared glbfn_glCreateShader glCreateShader_NVGLZ; alias glCreateShader = glCreateShader_NVGLZ;
12102 alias glbfn_glShaderSource = void function(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
12103 __gshared glbfn_glShaderSource glShaderSource_NVGLZ; alias glShaderSource = glShaderSource_NVGLZ;
12104 alias glbfn_glCompileShader = void function(GLuint);
12105 __gshared glbfn_glCompileShader glCompileShader_NVGLZ; alias glCompileShader = glCompileShader_NVGLZ;
12106 alias glbfn_glGetShaderiv = void function(GLuint, GLenum, GLint*);
12107 __gshared glbfn_glGetShaderiv glGetShaderiv_NVGLZ; alias glGetShaderiv = glGetShaderiv_NVGLZ;
12108 alias glbfn_glAttachShader = void function(GLuint, GLuint);
12109 __gshared glbfn_glAttachShader glAttachShader_NVGLZ; alias glAttachShader = glAttachShader_NVGLZ;
12110 alias glbfn_glBindAttribLocation = void function(GLuint, GLuint, const(GLchar)*);
12111 __gshared glbfn_glBindAttribLocation glBindAttribLocation_NVGLZ; alias glBindAttribLocation = glBindAttribLocation_NVGLZ;
12112 alias glbfn_glLinkProgram = void function(GLuint);
12113 __gshared glbfn_glLinkProgram glLinkProgram_NVGLZ; alias glLinkProgram = glLinkProgram_NVGLZ;
12114 alias glbfn_glGetProgramiv = void function(GLuint, GLenum, GLint*);
12115 __gshared glbfn_glGetProgramiv glGetProgramiv_NVGLZ; alias glGetProgramiv = glGetProgramiv_NVGLZ;
12116 alias glbfn_glDeleteProgram = void function(GLuint);
12117 __gshared glbfn_glDeleteProgram glDeleteProgram_NVGLZ; alias glDeleteProgram = glDeleteProgram_NVGLZ;
12118 alias glbfn_glDeleteShader = void function(GLuint);
12119 __gshared glbfn_glDeleteShader glDeleteShader_NVGLZ; alias glDeleteShader = glDeleteShader_NVGLZ;
12120 alias glbfn_glGetUniformLocation = GLint function(GLuint, const(GLchar)*);
12121 __gshared glbfn_glGetUniformLocation glGetUniformLocation_NVGLZ; alias glGetUniformLocation = glGetUniformLocation_NVGLZ;
12122 alias glbfn_glGenBuffers = void function(GLsizei, GLuint*);
12123 __gshared glbfn_glGenBuffers glGenBuffers_NVGLZ; alias glGenBuffers = glGenBuffers_NVGLZ;
12124 alias glbfn_glPixelStorei = void function(GLenum, GLint);
12125 __gshared glbfn_glPixelStorei glPixelStorei_NVGLZ; alias glPixelStorei = glPixelStorei_NVGLZ;
12126 alias glbfn_glUniform4fv = void function(GLint, GLsizei, const(GLfloat)*);
12127 __gshared glbfn_glUniform4fv glUniform4fv_NVGLZ; alias glUniform4fv = glUniform4fv_NVGLZ;
12128 alias glbfn_glColorMask = void function(GLboolean, GLboolean, GLboolean, GLboolean);
12129 __gshared glbfn_glColorMask glColorMask_NVGLZ; alias glColorMask = glColorMask_NVGLZ;
12130 alias glbfn_glStencilOpSeparate = void function(GLenum, GLenum, GLenum, GLenum);
12131 __gshared glbfn_glStencilOpSeparate glStencilOpSeparate_NVGLZ; alias glStencilOpSeparate = glStencilOpSeparate_NVGLZ;
12132 alias glbfn_glDrawArrays = void function(GLenum, GLint, GLsizei);
12133 __gshared glbfn_glDrawArrays glDrawArrays_NVGLZ; alias glDrawArrays = glDrawArrays_NVGLZ;
12134 alias glbfn_glStencilOp = void function(GLenum, GLenum, GLenum);
12135 __gshared glbfn_glStencilOp glStencilOp_NVGLZ; alias glStencilOp = glStencilOp_NVGLZ;
12136 alias glbfn_glUseProgram = void function(GLuint);
12137 __gshared glbfn_glUseProgram glUseProgram_NVGLZ; alias glUseProgram = glUseProgram_NVGLZ;
12138 alias glbfn_glCullFace = void function(GLenum);
12139 __gshared glbfn_glCullFace glCullFace_NVGLZ; alias glCullFace = glCullFace_NVGLZ;
12140 alias glbfn_glFrontFace = void function(GLenum);
12141 __gshared glbfn_glFrontFace glFrontFace_NVGLZ; alias glFrontFace = glFrontFace_NVGLZ;
12142 alias glbfn_glActiveTexture = void function(GLenum);
12143 __gshared glbfn_glActiveTexture glActiveTexture_NVGLZ; alias glActiveTexture = glActiveTexture_NVGLZ;
12144 alias glbfn_glBindBuffer = void function(GLenum, GLuint);
12145 __gshared glbfn_glBindBuffer glBindBuffer_NVGLZ; alias glBindBuffer = glBindBuffer_NVGLZ;
12146 alias glbfn_glBufferData = void function(GLenum, GLsizeiptr, const(void)*, GLenum);
12147 __gshared glbfn_glBufferData glBufferData_NVGLZ; alias glBufferData = glBufferData_NVGLZ;
12148 alias glbfn_glEnableVertexAttribArray = void function(GLuint);
12149 __gshared glbfn_glEnableVertexAttribArray glEnableVertexAttribArray_NVGLZ; alias glEnableVertexAttribArray = glEnableVertexAttribArray_NVGLZ;
12150 alias glbfn_glVertexAttribPointer = void function(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
12151 __gshared glbfn_glVertexAttribPointer glVertexAttribPointer_NVGLZ; alias glVertexAttribPointer = glVertexAttribPointer_NVGLZ;
12152 alias glbfn_glUniform1i = void function(GLint, GLint);
12153 __gshared glbfn_glUniform1i glUniform1i_NVGLZ; alias glUniform1i = glUniform1i_NVGLZ;
12154 alias glbfn_glUniform2fv = void function(GLint, GLsizei, const(GLfloat)*);
12155 __gshared glbfn_glUniform2fv glUniform2fv_NVGLZ; alias glUniform2fv = glUniform2fv_NVGLZ;
12156 alias glbfn_glDisableVertexAttribArray = void function(GLuint);
12157 __gshared glbfn_glDisableVertexAttribArray glDisableVertexAttribArray_NVGLZ; alias glDisableVertexAttribArray = glDisableVertexAttribArray_NVGLZ;
12158 alias glbfn_glDeleteBuffers = void function(GLsizei, const(GLuint)*);
12159 __gshared glbfn_glDeleteBuffers glDeleteBuffers_NVGLZ; alias glDeleteBuffers = glDeleteBuffers_NVGLZ;
12160 alias glbfn_glBlendFuncSeparate = void function(GLenum, GLenum, GLenum, GLenum);
12161 __gshared glbfn_glBlendFuncSeparate glBlendFuncSeparate_NVGLZ; alias glBlendFuncSeparate = glBlendFuncSeparate_NVGLZ;
12163 alias glbfn_glLogicOp = void function (GLenum opcode);
12164 __gshared glbfn_glLogicOp glLogicOp_NVGLZ; alias glLogicOp = glLogicOp_NVGLZ;
12165 alias glbfn_glFramebufferTexture2D = void function (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
12166 __gshared glbfn_glFramebufferTexture2D glFramebufferTexture2D_NVGLZ; alias glFramebufferTexture2D = glFramebufferTexture2D_NVGLZ;
12167 alias glbfn_glDeleteFramebuffers = void function (GLsizei n, const(GLuint)* framebuffers);
12168 __gshared glbfn_glDeleteFramebuffers glDeleteFramebuffers_NVGLZ; alias glDeleteFramebuffers = glDeleteFramebuffers_NVGLZ;
12169 alias glbfn_glGenFramebuffers = void function (GLsizei n, GLuint* framebuffers);
12170 __gshared glbfn_glGenFramebuffers glGenFramebuffers_NVGLZ; alias glGenFramebuffers = glGenFramebuffers_NVGLZ;
12171 alias glbfn_glCheckFramebufferStatus = GLenum function (GLenum target);
12172 __gshared glbfn_glCheckFramebufferStatus glCheckFramebufferStatus_NVGLZ; alias glCheckFramebufferStatus = glCheckFramebufferStatus_NVGLZ;
12173 alias glbfn_glBindFramebuffer = void function (GLenum target, GLuint framebuffer);
12174 __gshared glbfn_glBindFramebuffer glBindFramebuffer_NVGLZ; alias glBindFramebuffer = glBindFramebuffer_NVGLZ;
12176 alias glbfn_glGetIntegerv = void function (GLenum pname, GLint* data);
12177 __gshared glbfn_glGetIntegerv glGetIntegerv_NVGLZ; alias glGetIntegerv = glGetIntegerv_NVGLZ;
12179 private void nanovgInitOpenGL () {
12180 __gshared bool initialized = false;
12181 if (initialized) return;
12182 glStencilMask_NVGLZ = cast(glbfn_glStencilMask)glbindGetProcAddress(`glStencilMask`);
12183 if (glStencilMask_NVGLZ is null) assert(0, `OpenGL function 'glStencilMask' not found!`);
12184 glStencilFunc_NVGLZ = cast(glbfn_glStencilFunc)glbindGetProcAddress(`glStencilFunc`);
12185 if (glStencilFunc_NVGLZ is null) assert(0, `OpenGL function 'glStencilFunc' not found!`);
12186 glGetShaderInfoLog_NVGLZ = cast(glbfn_glGetShaderInfoLog)glbindGetProcAddress(`glGetShaderInfoLog`);
12187 if (glGetShaderInfoLog_NVGLZ is null) assert(0, `OpenGL function 'glGetShaderInfoLog' not found!`);
12188 glGetProgramInfoLog_NVGLZ = cast(glbfn_glGetProgramInfoLog)glbindGetProcAddress(`glGetProgramInfoLog`);
12189 if (glGetProgramInfoLog_NVGLZ is null) assert(0, `OpenGL function 'glGetProgramInfoLog' not found!`);
12190 glCreateProgram_NVGLZ = cast(glbfn_glCreateProgram)glbindGetProcAddress(`glCreateProgram`);
12191 if (glCreateProgram_NVGLZ is null) assert(0, `OpenGL function 'glCreateProgram' not found!`);
12192 glCreateShader_NVGLZ = cast(glbfn_glCreateShader)glbindGetProcAddress(`glCreateShader`);
12193 if (glCreateShader_NVGLZ is null) assert(0, `OpenGL function 'glCreateShader' not found!`);
12194 glShaderSource_NVGLZ = cast(glbfn_glShaderSource)glbindGetProcAddress(`glShaderSource`);
12195 if (glShaderSource_NVGLZ is null) assert(0, `OpenGL function 'glShaderSource' not found!`);
12196 glCompileShader_NVGLZ = cast(glbfn_glCompileShader)glbindGetProcAddress(`glCompileShader`);
12197 if (glCompileShader_NVGLZ is null) assert(0, `OpenGL function 'glCompileShader' not found!`);
12198 glGetShaderiv_NVGLZ = cast(glbfn_glGetShaderiv)glbindGetProcAddress(`glGetShaderiv`);
12199 if (glGetShaderiv_NVGLZ is null) assert(0, `OpenGL function 'glGetShaderiv' not found!`);
12200 glAttachShader_NVGLZ = cast(glbfn_glAttachShader)glbindGetProcAddress(`glAttachShader`);
12201 if (glAttachShader_NVGLZ is null) assert(0, `OpenGL function 'glAttachShader' not found!`);
12202 glBindAttribLocation_NVGLZ = cast(glbfn_glBindAttribLocation)glbindGetProcAddress(`glBindAttribLocation`);
12203 if (glBindAttribLocation_NVGLZ is null) assert(0, `OpenGL function 'glBindAttribLocation' not found!`);
12204 glLinkProgram_NVGLZ = cast(glbfn_glLinkProgram)glbindGetProcAddress(`glLinkProgram`);
12205 if (glLinkProgram_NVGLZ is null) assert(0, `OpenGL function 'glLinkProgram' not found!`);
12206 glGetProgramiv_NVGLZ = cast(glbfn_glGetProgramiv)glbindGetProcAddress(`glGetProgramiv`);
12207 if (glGetProgramiv_NVGLZ is null) assert(0, `OpenGL function 'glGetProgramiv' not found!`);
12208 glDeleteProgram_NVGLZ = cast(glbfn_glDeleteProgram)glbindGetProcAddress(`glDeleteProgram`);
12209 if (glDeleteProgram_NVGLZ is null) assert(0, `OpenGL function 'glDeleteProgram' not found!`);
12210 glDeleteShader_NVGLZ = cast(glbfn_glDeleteShader)glbindGetProcAddress(`glDeleteShader`);
12211 if (glDeleteShader_NVGLZ is null) assert(0, `OpenGL function 'glDeleteShader' not found!`);
12212 glGetUniformLocation_NVGLZ = cast(glbfn_glGetUniformLocation)glbindGetProcAddress(`glGetUniformLocation`);
12213 if (glGetUniformLocation_NVGLZ is null) assert(0, `OpenGL function 'glGetUniformLocation' not found!`);
12214 glGenBuffers_NVGLZ = cast(glbfn_glGenBuffers)glbindGetProcAddress(`glGenBuffers`);
12215 if (glGenBuffers_NVGLZ is null) assert(0, `OpenGL function 'glGenBuffers' not found!`);
12216 glPixelStorei_NVGLZ = cast(glbfn_glPixelStorei)glbindGetProcAddress(`glPixelStorei`);
12217 if (glPixelStorei_NVGLZ is null) assert(0, `OpenGL function 'glPixelStorei' not found!`);
12218 glUniform4fv_NVGLZ = cast(glbfn_glUniform4fv)glbindGetProcAddress(`glUniform4fv`);
12219 if (glUniform4fv_NVGLZ is null) assert(0, `OpenGL function 'glUniform4fv' not found!`);
12220 glColorMask_NVGLZ = cast(glbfn_glColorMask)glbindGetProcAddress(`glColorMask`);
12221 if (glColorMask_NVGLZ is null) assert(0, `OpenGL function 'glColorMask' not found!`);
12222 glStencilOpSeparate_NVGLZ = cast(glbfn_glStencilOpSeparate)glbindGetProcAddress(`glStencilOpSeparate`);
12223 if (glStencilOpSeparate_NVGLZ is null) assert(0, `OpenGL function 'glStencilOpSeparate' not found!`);
12224 glDrawArrays_NVGLZ = cast(glbfn_glDrawArrays)glbindGetProcAddress(`glDrawArrays`);
12225 if (glDrawArrays_NVGLZ is null) assert(0, `OpenGL function 'glDrawArrays' not found!`);
12226 glStencilOp_NVGLZ = cast(glbfn_glStencilOp)glbindGetProcAddress(`glStencilOp`);
12227 if (glStencilOp_NVGLZ is null) assert(0, `OpenGL function 'glStencilOp' not found!`);
12228 glUseProgram_NVGLZ = cast(glbfn_glUseProgram)glbindGetProcAddress(`glUseProgram`);
12229 if (glUseProgram_NVGLZ is null) assert(0, `OpenGL function 'glUseProgram' not found!`);
12230 glCullFace_NVGLZ = cast(glbfn_glCullFace)glbindGetProcAddress(`glCullFace`);
12231 if (glCullFace_NVGLZ is null) assert(0, `OpenGL function 'glCullFace' not found!`);
12232 glFrontFace_NVGLZ = cast(glbfn_glFrontFace)glbindGetProcAddress(`glFrontFace`);
12233 if (glFrontFace_NVGLZ is null) assert(0, `OpenGL function 'glFrontFace' not found!`);
12234 glActiveTexture_NVGLZ = cast(glbfn_glActiveTexture)glbindGetProcAddress(`glActiveTexture`);
12235 if (glActiveTexture_NVGLZ is null) assert(0, `OpenGL function 'glActiveTexture' not found!`);
12236 glBindBuffer_NVGLZ = cast(glbfn_glBindBuffer)glbindGetProcAddress(`glBindBuffer`);
12237 if (glBindBuffer_NVGLZ is null) assert(0, `OpenGL function 'glBindBuffer' not found!`);
12238 glBufferData_NVGLZ = cast(glbfn_glBufferData)glbindGetProcAddress(`glBufferData`);
12239 if (glBufferData_NVGLZ is null) assert(0, `OpenGL function 'glBufferData' not found!`);
12240 glEnableVertexAttribArray_NVGLZ = cast(glbfn_glEnableVertexAttribArray)glbindGetProcAddress(`glEnableVertexAttribArray`);
12241 if (glEnableVertexAttribArray_NVGLZ is null) assert(0, `OpenGL function 'glEnableVertexAttribArray' not found!`);
12242 glVertexAttribPointer_NVGLZ = cast(glbfn_glVertexAttribPointer)glbindGetProcAddress(`glVertexAttribPointer`);
12243 if (glVertexAttribPointer_NVGLZ is null) assert(0, `OpenGL function 'glVertexAttribPointer' not found!`);
12244 glUniform1i_NVGLZ = cast(glbfn_glUniform1i)glbindGetProcAddress(`glUniform1i`);
12245 if (glUniform1i_NVGLZ is null) assert(0, `OpenGL function 'glUniform1i' not found!`);
12246 glUniform2fv_NVGLZ = cast(glbfn_glUniform2fv)glbindGetProcAddress(`glUniform2fv`);
12247 if (glUniform2fv_NVGLZ is null) assert(0, `OpenGL function 'glUniform2fv' not found!`);
12248 glDisableVertexAttribArray_NVGLZ = cast(glbfn_glDisableVertexAttribArray)glbindGetProcAddress(`glDisableVertexAttribArray`);
12249 if (glDisableVertexAttribArray_NVGLZ is null) assert(0, `OpenGL function 'glDisableVertexAttribArray' not found!`);
12250 glDeleteBuffers_NVGLZ = cast(glbfn_glDeleteBuffers)glbindGetProcAddress(`glDeleteBuffers`);
12251 if (glDeleteBuffers_NVGLZ is null) assert(0, `OpenGL function 'glDeleteBuffers' not found!`);
12252 glBlendFuncSeparate_NVGLZ = cast(glbfn_glBlendFuncSeparate)glbindGetProcAddress(`glBlendFuncSeparate`);
12253 if (glBlendFuncSeparate_NVGLZ is null) assert(0, `OpenGL function 'glBlendFuncSeparate' not found!`);
12255 glLogicOp_NVGLZ = cast(glbfn_glLogicOp)glbindGetProcAddress(`glLogicOp`);
12256 if (glLogicOp_NVGLZ is null) assert(0, `OpenGL function 'glLogicOp' not found!`);
12257 glFramebufferTexture2D_NVGLZ = cast(glbfn_glFramebufferTexture2D)glbindGetProcAddress(`glFramebufferTexture2D`);
12258 if (glFramebufferTexture2D_NVGLZ is null) assert(0, `OpenGL function 'glFramebufferTexture2D' not found!`);
12259 glDeleteFramebuffers_NVGLZ = cast(glbfn_glDeleteFramebuffers)glbindGetProcAddress(`glDeleteFramebuffers`);
12260 if (glDeleteFramebuffers_NVGLZ is null) assert(0, `OpenGL function 'glDeleteFramebuffers' not found!`);
12261 glGenFramebuffers_NVGLZ = cast(glbfn_glGenFramebuffers)glbindGetProcAddress(`glGenFramebuffers`);
12262 if (glGenFramebuffers_NVGLZ is null) assert(0, `OpenGL function 'glGenFramebuffers' not found!`);
12263 glCheckFramebufferStatus_NVGLZ = cast(glbfn_glCheckFramebufferStatus)glbindGetProcAddress(`glCheckFramebufferStatus`);
12264 if (glCheckFramebufferStatus_NVGLZ is null) assert(0, `OpenGL function 'glCheckFramebufferStatus' not found!`);
12265 glBindFramebuffer_NVGLZ = cast(glbfn_glBindFramebuffer)glbindGetProcAddress(`glBindFramebuffer`);
12266 if (glBindFramebuffer_NVGLZ is null) assert(0, `OpenGL function 'glBindFramebuffer' not found!`);
12268 glGetIntegerv_NVGLZ = cast(glbfn_glGetIntegerv)glbindGetProcAddress(`glGetIntegerv`);
12269 if (glGetIntegerv_NVGLZ is null) assert(0, `OpenGL function 'glGetIntegerv' not found!`);
12271 initialized = true;
12276 /// Context creation flags.
12277 /// Group: context_management
12278 public enum NVGContextFlag : int {
12279 /// Nothing special, i.e. empty flag.
12280 None = 0,
12281 /// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA).
12282 Antialias = 1U<<0,
12283 /** Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little
12284 * slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. */
12285 StencilStrokes = 1U<<1,
12286 /// Flag indicating that additional debug checks are done.
12287 Debug = 1U<<2,
12288 /// Filter (antialias) fonts
12289 FontAA = 1U<<7,
12290 /// Don't filter (antialias) fonts
12291 FontNoAA = 1U<<8,
12292 /// You can use this as a substitute for default flags, for cases like this: `nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);`.
12293 Default = 1U<<31,
12296 public enum NANOVG_GL_USE_STATE_FILTER = true;
12298 /// Returns flags for glClear().
12299 /// Group: context_management
12300 public uint glNVGClearFlags () pure nothrow @safe @nogc {
12301 pragma(inline, true);
12302 return (GL_COLOR_BUFFER_BIT|/*GL_DEPTH_BUFFER_BIT|*/GL_STENCIL_BUFFER_BIT);
12306 // ////////////////////////////////////////////////////////////////////////// //
12307 private:
12309 version = nanovega_shared_stencil;
12310 //version = nanovega_debug_clipping;
12312 enum GLNVGuniformLoc {
12313 ViewSize,
12314 Tex,
12315 Frag,
12316 TMat,
12317 TTr,
12318 ClipTex,
12321 alias GLNVGshaderType = int;
12322 enum /*GLNVGshaderType*/ {
12323 NSVG_SHADER_FILLCOLOR,
12324 NSVG_SHADER_FILLGRAD,
12325 NSVG_SHADER_FILLIMG,
12326 NSVG_SHADER_SIMPLE, // also used for clipfill
12327 NSVG_SHADER_IMG,
12330 struct GLNVGshader {
12331 GLuint prog;
12332 GLuint frag;
12333 GLuint vert;
12334 GLint[GLNVGuniformLoc.max+1] loc;
12337 struct GLNVGtexture {
12338 int id;
12339 GLuint tex;
12340 int width, height;
12341 NVGtexture type;
12342 int flags;
12343 shared int rc; // this can be 0 with tex != 0 -- postponed deletion
12344 int nextfree;
12347 struct GLNVGblend {
12348 bool simple;
12349 GLenum srcRGB;
12350 GLenum dstRGB;
12351 GLenum srcAlpha;
12352 GLenum dstAlpha;
12355 alias GLNVGcallType = int;
12356 enum /*GLNVGcallType*/ {
12357 GLNVG_NONE = 0,
12358 GLNVG_FILL,
12359 GLNVG_CONVEXFILL,
12360 GLNVG_STROKE,
12361 GLNVG_TRIANGLES,
12362 GLNVG_AFFINE, // change affine transformation matrix
12363 GLNVG_PUSHCLIP,
12364 GLNVG_POPCLIP,
12365 GLNVG_RESETCLIP,
12366 GLNVG_CLIP_DDUMP_ON,
12367 GLNVG_CLIP_DDUMP_OFF,
12370 struct GLNVGcall {
12371 int type;
12372 int evenOdd; // for fill
12373 int image;
12374 int pathOffset;
12375 int pathCount;
12376 int triangleOffset;
12377 int triangleCount;
12378 int uniformOffset;
12379 NVGMatrix affine;
12380 GLNVGblend blendFunc;
12381 NVGClipMode clipmode;
12384 struct GLNVGpath {
12385 int fillOffset;
12386 int fillCount;
12387 int strokeOffset;
12388 int strokeCount;
12391 align(1) struct GLNVGfragUniforms {
12392 align(1):
12393 enum UNIFORM_ARRAY_SIZE = 13;
12394 // note: after modifying layout or size of uniform array,
12395 // don't forget to also update the fragment shader source!
12396 align(1) union {
12397 align(1):
12398 align(1) struct {
12399 align(1):
12400 float[12] scissorMat; // matrices are actually 3 vec4s
12401 float[12] paintMat;
12402 NVGColor innerCol;
12403 NVGColor middleCol;
12404 NVGColor outerCol;
12405 float[2] scissorExt;
12406 float[2] scissorScale;
12407 float[2] extent;
12408 float radius;
12409 float feather;
12410 float strokeMult;
12411 float strokeThr;
12412 float texType;
12413 float type;
12414 float doclip;
12415 float midp; // for gradients
12416 float unused2, unused3;
12418 float[4][UNIFORM_ARRAY_SIZE] uniformArray;
12422 enum GLMaskState {
12423 DontMask = -1,
12424 Uninitialized = 0,
12425 Initialized = 1,
12426 JustCleared = 2,
12429 final class GLNVGTextureLocker {}
12431 struct GLNVGcontext {
12432 private import core.thread : ThreadID;
12434 GLNVGshader shader;
12435 GLNVGtexture* textures;
12436 float[2] view;
12437 int freetexid; // -1: none
12438 int ntextures;
12439 int ctextures;
12440 GLuint vertBuf;
12441 int fragSize;
12442 int flags;
12443 // FBOs for masks
12444 GLuint[NVG_MAX_STATES] fbo;
12445 GLuint[2][NVG_MAX_STATES] fboTex; // FBO textures: [0] is color, [1] is stencil
12446 int fboWidth, fboHeight;
12447 GLMaskState[NVG_MAX_STATES] maskStack;
12448 int msp; // mask stack pointer; starts from `0`; points to next free item; see below for logic description
12449 int lastClipFBO; // -666: cache invalidated; -1: don't mask
12450 int lastClipUniOfs;
12451 bool doClipUnion; // specal mode
12452 GLNVGshader shaderFillFBO;
12453 GLNVGshader shaderCopyFBO;
12455 bool inFrame; // will be `true` if we can perform OpenGL operations (used in texture deletion)
12456 shared bool mustCleanTextures; // will be `true` if we should delete some textures
12457 ThreadID mainTID;
12458 uint mainFBO;
12460 // Per frame buffers
12461 GLNVGcall* calls;
12462 int ccalls;
12463 int ncalls;
12464 GLNVGpath* paths;
12465 int cpaths;
12466 int npaths;
12467 NVGVertex* verts;
12468 int cverts;
12469 int nverts;
12470 ubyte* uniforms;
12471 int cuniforms;
12472 int nuniforms;
12473 NVGMatrix lastAffine;
12475 // cached state
12476 static if (NANOVG_GL_USE_STATE_FILTER) {
12477 GLuint boundTexture;
12478 GLuint stencilMask;
12479 GLenum stencilFunc;
12480 GLint stencilFuncRef;
12481 GLuint stencilFuncMask;
12482 GLNVGblend blendFunc;
12486 int glnvg__maxi() (int a, int b) { pragma(inline, true); return (a > b ? a : b); }
12488 void glnvg__bindTexture (GLNVGcontext* gl, GLuint tex) nothrow @trusted @nogc {
12489 static if (NANOVG_GL_USE_STATE_FILTER) {
12490 if (gl.boundTexture != tex) {
12491 gl.boundTexture = tex;
12492 glBindTexture(GL_TEXTURE_2D, tex);
12494 } else {
12495 glBindTexture(GL_TEXTURE_2D, tex);
12499 void glnvg__stencilMask (GLNVGcontext* gl, GLuint mask) nothrow @trusted @nogc {
12500 static if (NANOVG_GL_USE_STATE_FILTER) {
12501 if (gl.stencilMask != mask) {
12502 gl.stencilMask = mask;
12503 glStencilMask(mask);
12505 } else {
12506 glStencilMask(mask);
12510 void glnvg__stencilFunc (GLNVGcontext* gl, GLenum func, GLint ref_, GLuint mask) nothrow @trusted @nogc {
12511 static if (NANOVG_GL_USE_STATE_FILTER) {
12512 if (gl.stencilFunc != func || gl.stencilFuncRef != ref_ || gl.stencilFuncMask != mask) {
12513 gl.stencilFunc = func;
12514 gl.stencilFuncRef = ref_;
12515 gl.stencilFuncMask = mask;
12516 glStencilFunc(func, ref_, mask);
12518 } else {
12519 glStencilFunc(func, ref_, mask);
12523 // texture id is never zero
12524 // sets refcount to one
12525 GLNVGtexture* glnvg__allocTexture (GLNVGcontext* gl) nothrow @trusted @nogc {
12526 GLNVGtexture* tex = null;
12528 int tid = gl.freetexid;
12529 if (tid == -1) {
12530 if (gl.ntextures >= gl.ctextures) {
12531 assert(gl.ntextures == gl.ctextures);
12532 //pragma(msg, GLNVGtexture.sizeof*32);
12533 int ctextures = (gl.ctextures == 0 ? 32 : gl.ctextures+gl.ctextures/2); // 1.5x overallocate
12534 GLNVGtexture* textures = cast(GLNVGtexture*)realloc(gl.textures, GLNVGtexture.sizeof*ctextures);
12535 if (textures is null) assert(0, "NanoVega: out of memory for textures");
12536 memset(&textures[gl.ctextures], 0, (ctextures-gl.ctextures)*GLNVGtexture.sizeof);
12537 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated more textures (n=%d; c=%d; nc=%d)\n", gl.ntextures, gl.ctextures, ctextures); }}
12538 gl.textures = textures;
12539 gl.ctextures = ctextures;
12541 assert(gl.ntextures+1 <= gl.ctextures);
12542 tid = gl.ntextures++;
12543 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf(" got next free texture id %d, ntextures=%d\n", tid+1, gl.ntextures); }}
12544 } else {
12545 gl.freetexid = gl.textures[tid].nextfree;
12547 assert(tid <= gl.ntextures);
12549 assert(gl.textures[tid].id == 0);
12550 tex = &gl.textures[tid];
12551 memset(tex, 0, (*tex).sizeof);
12552 tex.id = tid+1;
12553 tex.rc = 1;
12554 tex.nextfree = -1;
12556 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated texture with id %d (%d)\n", tex.id, tid+1); }}
12558 return tex;
12561 GLNVGtexture* glnvg__findTexture (GLNVGcontext* gl, int id) nothrow @trusted @nogc {
12562 if (id <= 0 || id > gl.ntextures) return null;
12563 if (gl.textures[id-1].id == 0) return null; // free one
12564 assert(gl.textures[id-1].id == id);
12565 return &gl.textures[id-1];
12568 bool glnvg__deleteTexture (GLNVGcontext* gl, ref int id) nothrow @trusted @nogc {
12569 if (id <= 0 || id > gl.ntextures) return false;
12570 auto tx = &gl.textures[id-1];
12571 if (tx.id == 0) { id = 0; return false; } // free one
12572 assert(tx.id == id);
12573 assert(tx.tex != 0);
12574 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("decrefing texture with id %d (%d)\n", tx.id, id); }}
12575 import core.atomic : atomicOp;
12576 if (atomicOp!"-="(tx.rc, 1) == 0) {
12577 import core.thread : ThreadID;
12578 ThreadID mytid;
12579 static if (__VERSION__ < 2076) {
12580 DGNoThrowNoGC(() {
12581 import core.thread; mytid = Thread.getThis.id;
12582 })();
12583 } else {
12584 try { import core.thread; mytid = Thread.getThis.id; } catch (Exception e) {}
12586 if (gl.mainTID == mytid && gl.inFrame) {
12587 // can delete it right now
12588 if ((tx.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tx.tex);
12589 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** deleted texture with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }}
12590 memset(tx, 0, (*tx).sizeof);
12591 //{ import core.stdc.stdio; printf("deleting texture with id %d\n", id); }
12592 tx.nextfree = gl.freetexid;
12593 gl.freetexid = id-1;
12594 } else {
12595 // alas, we aren't doing frame business, so we should postpone deletion
12596 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** POSTPONED texture deletion with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }}
12597 version(aliced) {
12598 synchronized(GLNVGTextureLocker.classinfo) {
12599 tx.id = 0; // mark it as dead
12600 gl.mustCleanTextures = true; // set "need cleanup" flag
12602 } else {
12603 try {
12604 synchronized(GLNVGTextureLocker.classinfo) {
12605 tx.id = 0; // mark it as dead
12606 gl.mustCleanTextures = true; // set "need cleanup" flag
12608 } catch (Exception e) {}
12612 id = 0;
12613 return true;
12616 void glnvg__dumpShaderError (GLuint shader, const(char)* name, const(char)* type) nothrow @trusted @nogc {
12617 import core.stdc.stdio : fprintf, stderr;
12618 GLchar[512+1] str = 0;
12619 GLsizei len = 0;
12620 glGetShaderInfoLog(shader, 512, &len, str.ptr);
12621 if (len > 512) len = 512;
12622 str[len] = '\0';
12623 fprintf(stderr, "Shader %s/%s error:\n%s\n", name, type, str.ptr);
12626 void glnvg__dumpProgramError (GLuint prog, const(char)* name) nothrow @trusted @nogc {
12627 import core.stdc.stdio : fprintf, stderr;
12628 GLchar[512+1] str = 0;
12629 GLsizei len = 0;
12630 glGetProgramInfoLog(prog, 512, &len, str.ptr);
12631 if (len > 512) len = 512;
12632 str[len] = '\0';
12633 fprintf(stderr, "Program %s error:\n%s\n", name, str.ptr);
12636 void glnvg__resetError(bool force=false) (GLNVGcontext* gl) nothrow @trusted @nogc {
12637 static if (!force) {
12638 if ((gl.flags&NVGContextFlag.Debug) == 0) return;
12640 glGetError();
12643 void glnvg__checkError(bool force=false) (GLNVGcontext* gl, const(char)* str) nothrow @trusted @nogc {
12644 GLenum err;
12645 static if (!force) {
12646 if ((gl.flags&NVGContextFlag.Debug) == 0) return;
12648 err = glGetError();
12649 if (err != GL_NO_ERROR) {
12650 import core.stdc.stdio : fprintf, stderr;
12651 fprintf(stderr, "Error %08x after %s\n", err, str);
12652 return;
12656 bool glnvg__createShader (GLNVGshader* shader, const(char)* name, const(char)* header, const(char)* opts, const(char)* vshader, const(char)* fshader) nothrow @trusted @nogc {
12657 GLint status;
12658 GLuint prog, vert, frag;
12659 const(char)*[3] str;
12661 memset(shader, 0, (*shader).sizeof);
12663 prog = glCreateProgram();
12664 vert = glCreateShader(GL_VERTEX_SHADER);
12665 frag = glCreateShader(GL_FRAGMENT_SHADER);
12666 str[0] = header;
12667 str[1] = (opts !is null ? opts : "");
12668 str[2] = vshader;
12669 glShaderSource(vert, 3, cast(const(char)**)str.ptr, null);
12671 glCompileShader(vert);
12672 glGetShaderiv(vert, GL_COMPILE_STATUS, &status);
12673 if (status != GL_TRUE) {
12674 glnvg__dumpShaderError(vert, name, "vert");
12675 return false;
12678 str[0] = header;
12679 str[1] = (opts !is null ? opts : "");
12680 str[2] = fshader;
12681 glShaderSource(frag, 3, cast(const(char)**)str.ptr, null);
12683 glCompileShader(frag);
12684 glGetShaderiv(frag, GL_COMPILE_STATUS, &status);
12685 if (status != GL_TRUE) {
12686 glnvg__dumpShaderError(frag, name, "frag");
12687 return false;
12690 glAttachShader(prog, vert);
12691 glAttachShader(prog, frag);
12693 glBindAttribLocation(prog, 0, "vertex");
12694 glBindAttribLocation(prog, 1, "tcoord");
12696 glLinkProgram(prog);
12697 glGetProgramiv(prog, GL_LINK_STATUS, &status);
12698 if (status != GL_TRUE) {
12699 glnvg__dumpProgramError(prog, name);
12700 return false;
12703 shader.prog = prog;
12704 shader.vert = vert;
12705 shader.frag = frag;
12707 return true;
12710 void glnvg__deleteShader (GLNVGshader* shader) nothrow @trusted @nogc {
12711 if (shader.prog != 0) glDeleteProgram(shader.prog);
12712 if (shader.vert != 0) glDeleteShader(shader.vert);
12713 if (shader.frag != 0) glDeleteShader(shader.frag);
12716 void glnvg__getUniforms (GLNVGshader* shader) nothrow @trusted @nogc {
12717 shader.loc[GLNVGuniformLoc.ViewSize] = glGetUniformLocation(shader.prog, "viewSize");
12718 shader.loc[GLNVGuniformLoc.Tex] = glGetUniformLocation(shader.prog, "tex");
12719 shader.loc[GLNVGuniformLoc.Frag] = glGetUniformLocation(shader.prog, "frag");
12720 shader.loc[GLNVGuniformLoc.TMat] = glGetUniformLocation(shader.prog, "tmat");
12721 shader.loc[GLNVGuniformLoc.TTr] = glGetUniformLocation(shader.prog, "ttr");
12722 shader.loc[GLNVGuniformLoc.ClipTex] = glGetUniformLocation(shader.prog, "clipTex");
12725 void glnvg__killFBOs (GLNVGcontext* gl) nothrow @trusted @nogc {
12726 foreach (immutable fidx, ref GLuint fbo; gl.fbo[]) {
12727 if (fbo != 0) {
12728 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
12729 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
12730 foreach (ref GLuint tid; gl.fboTex.ptr[fidx][]) if (tid != 0) { glDeleteTextures(1, &tid); tid = 0; }
12731 glDeleteFramebuffers(1, &fbo);
12732 fbo = 0;
12735 gl.fboWidth = gl.fboHeight = 0;
12738 // returns `true` is new FBO was created
12739 // will not unbind buffer, if it was created
12740 bool glnvg__allocFBO (GLNVGcontext* gl, int fidx, bool doclear=true) nothrow @trusted @nogc {
12741 assert(fidx >= 0 && fidx < gl.fbo.length);
12742 assert(gl.fboWidth > 0);
12743 assert(gl.fboHeight > 0);
12745 if (gl.fbo.ptr[fidx] != 0) return false; // nothing to do, this FBO is already initialized
12747 glnvg__resetError(gl);
12749 // allocate FBO object
12750 GLuint fbo = 0;
12751 glGenFramebuffers(1, &fbo);
12752 if (fbo == 0) assert(0, "NanoVega: cannot create FBO");
12753 glnvg__checkError(gl, "glnvg__allocFBO: glGenFramebuffers");
12754 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
12755 //scope(exit) glBindFramebuffer(GL_FRAMEBUFFER, 0);
12757 // attach 2D texture to this FBO
12758 GLuint tidColor = 0;
12759 glGenTextures(1, &tidColor);
12760 if (tidColor == 0) assert(0, "NanoVega: cannot create RGBA texture for FBO");
12761 glBindTexture(GL_TEXTURE_2D, tidColor);
12762 //scope(exit) glBindTexture(GL_TEXTURE_2D, 0);
12763 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
12764 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_S");
12765 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
12766 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_T");
12767 //FIXME: linear or nearest?
12768 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
12769 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
12770 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MIN_FILTER");
12771 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
12772 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
12773 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MAG_FILTER");
12774 // empty texture
12775 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
12776 // create texture with only one color channel
12777 glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, gl.fboWidth, gl.fboHeight, 0, GL_RED, GL_UNSIGNED_BYTE, null);
12778 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
12779 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (color)");
12780 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tidColor, 0);
12781 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (color)");
12783 // attach stencil texture to this FBO
12784 GLuint tidStencil = 0;
12785 version(nanovega_shared_stencil) {
12786 if (gl.fboTex.ptr[0].ptr[0] == 0) {
12787 glGenTextures(1, &tidStencil);
12788 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO");
12789 gl.fboTex.ptr[0].ptr[0] = tidStencil;
12790 } else {
12791 tidStencil = gl.fboTex.ptr[0].ptr[0];
12793 if (fidx != 0) gl.fboTex.ptr[fidx].ptr[1] = 0; // stencil texture is shared among FBOs
12794 } else {
12795 glGenTextures(1, &tidStencil);
12796 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO");
12797 gl.fboTex.ptr[0].ptr[0] = tidStencil;
12799 glBindTexture(GL_TEXTURE_2D, tidStencil);
12800 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, gl.fboWidth, gl.fboHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, null);
12801 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (stencil)");
12802 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tidStencil, 0);
12803 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (stencil)");
12806 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
12807 if (status != GL_FRAMEBUFFER_COMPLETE) {
12808 version(all) {
12809 import core.stdc.stdio;
12810 if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) printf("fucked attachement\n");
12811 if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) printf("fucked dimensions\n");
12812 if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) printf("missing attachement\n");
12813 if (status == GL_FRAMEBUFFER_UNSUPPORTED) printf("unsupported\n");
12815 assert(0, "NanoVega: framebuffer creation failed");
12819 // clear 'em all
12820 if (doclear) {
12821 glClearColor(0, 0, 0, 0);
12822 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
12825 // save texture ids
12826 gl.fbo.ptr[fidx] = fbo;
12827 gl.fboTex.ptr[fidx].ptr[0] = tidColor;
12828 version(nanovega_shared_stencil) {} else {
12829 gl.fboTex.ptr[fidx].ptr[1] = tidStencil;
12832 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture);
12834 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): created with index %d\n", gl.msp-1, fidx); }
12836 return true;
12839 // will not unbind buffer
12840 void glnvg__clearFBO (GLNVGcontext* gl, int fidx) nothrow @trusted @nogc {
12841 assert(fidx >= 0 && fidx < gl.fbo.length);
12842 assert(gl.fboWidth > 0);
12843 assert(gl.fboHeight > 0);
12844 assert(gl.fbo.ptr[fidx] != 0);
12845 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[fidx]);
12846 glClearColor(0, 0, 0, 0);
12847 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
12848 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cleared with index %d\n", gl.msp-1, fidx); }
12851 // will not unbind buffer
12852 void glnvg__copyFBOToFrom (GLNVGcontext* gl, int didx, int sidx) nothrow @trusted @nogc {
12853 import core.stdc.string : memset;
12854 assert(didx >= 0 && didx < gl.fbo.length);
12855 assert(sidx >= 0 && sidx < gl.fbo.length);
12856 assert(gl.fboWidth > 0);
12857 assert(gl.fboHeight > 0);
12858 assert(gl.fbo.ptr[didx] != 0);
12859 assert(gl.fbo.ptr[sidx] != 0);
12860 if (didx == sidx) return;
12862 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copy FBO: %d -> %d\n", gl.msp-1, sidx, didx); }
12864 glUseProgram(gl.shaderCopyFBO.prog);
12866 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[didx]);
12867 glDisable(GL_CULL_FACE);
12868 glDisable(GL_BLEND);
12869 glDisable(GL_SCISSOR_TEST);
12870 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[sidx].ptr[0]);
12871 // copy texture by drawing full quad
12872 enum x = 0;
12873 enum y = 0;
12874 immutable int w = gl.fboWidth;
12875 immutable int h = gl.fboHeight;
12876 glBegin(GL_QUADS);
12877 glVertex2i(x, y); // top-left
12878 glVertex2i(w, y); // top-right
12879 glVertex2i(w, h); // bottom-right
12880 glVertex2i(x, h); // bottom-left
12881 glEnd();
12883 // restore state (but don't unbind FBO)
12884 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture);
12885 glEnable(GL_CULL_FACE);
12886 glEnable(GL_BLEND);
12887 glUseProgram(gl.shader.prog);
12890 void glnvg__resetFBOClipTextureCache (GLNVGcontext* gl) nothrow @trusted @nogc {
12891 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): texture cache invalidated (%d)\n", gl.msp-1, gl.lastClipFBO); }
12893 if (gl.lastClipFBO >= 0) {
12894 glActiveTexture(GL_TEXTURE1);
12895 glBindTexture(GL_TEXTURE_2D, 0);
12896 glActiveTexture(GL_TEXTURE0);
12899 gl.lastClipFBO = -666;
12902 void glnvg__setFBOClipTexture (GLNVGcontext* gl, GLNVGfragUniforms* frag) nothrow @trusted @nogc {
12903 //assert(gl.msp > 0 && gl.msp <= gl.maskStack.length);
12904 if (gl.lastClipFBO != -666) {
12905 // cached
12906 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cached (%d)\n", gl.msp-1, gl.lastClipFBO); }
12907 frag.doclip = (gl.lastClipFBO >= 0 ? 1 : 0);
12908 return;
12911 // no cache
12912 int fboidx = -1;
12913 mainloop: foreach_reverse (immutable sp, GLMaskState mst; gl.maskStack.ptr[0..gl.msp]/*; reverse*/) {
12914 final switch (mst) {
12915 case GLMaskState.DontMask: fboidx = -1; break mainloop;
12916 case GLMaskState.Uninitialized: break;
12917 case GLMaskState.Initialized: fboidx = cast(int)sp; break mainloop;
12918 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__setFBOClipTexture()` internal error");
12922 if (fboidx < 0) {
12923 // don't mask
12924 gl.lastClipFBO = -1;
12925 frag.doclip = 0;
12926 } else {
12927 // do masking
12928 assert(gl.fbo.ptr[fboidx] != 0);
12929 gl.lastClipFBO = fboidx;
12930 frag.doclip = 1;
12933 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): new cache (new:%d)\n", gl.msp-1, gl.lastClipFBO); }
12935 if (gl.lastClipFBO >= 0) {
12936 assert(gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
12937 glActiveTexture(GL_TEXTURE1);
12938 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
12939 glActiveTexture(GL_TEXTURE0);
12943 // returns index in `gl.fbo`, or -1 for "don't mask"
12944 int glnvg__generateFBOClipTexture (GLNVGcontext* gl) nothrow @trusted @nogc {
12945 assert(gl.msp > 0 && gl.msp <= gl.maskStack.length);
12946 // we need initialized FBO, even for "don't mask" case
12947 // for this, look back in stack, and either copy initialized FBO,
12948 // or stop at first uninitialized one, and clear it
12949 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.Initialized) {
12950 // shortcut
12951 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); }
12952 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[gl.msp-1]);
12953 return gl.msp-1;
12955 foreach_reverse (immutable sp; 0..gl.msp/*; reverse*/) {
12956 final switch (gl.maskStack.ptr[sp]) {
12957 case GLMaskState.DontMask:
12958 // clear it
12959 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture\n", gl.msp-1); }
12960 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1);
12961 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared;
12962 return gl.msp-1;
12963 case GLMaskState.Uninitialized: break; // do nothing
12964 case GLMaskState.Initialized:
12965 // i found her! copy to TOS
12966 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copying texture from %d\n", gl.msp-1, cast(int)sp); }
12967 glnvg__allocFBO(gl, gl.msp-1, false);
12968 glnvg__copyFBOToFrom(gl, gl.msp-1, sp);
12969 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized;
12970 return gl.msp-1;
12971 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__generateFBOClipTexture()` internal error");
12974 // nothing was initialized, lol
12975 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture (first one)\n", gl.msp-1); }
12976 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1);
12977 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared;
12978 return gl.msp-1;
12981 void glnvg__renderPushClip (void* uptr) nothrow @trusted @nogc {
12982 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
12983 GLNVGcall* call = glnvg__allocCall(gl);
12984 if (call is null) return;
12985 call.type = GLNVG_PUSHCLIP;
12988 void glnvg__renderPopClip (void* uptr) nothrow @trusted @nogc {
12989 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
12990 GLNVGcall* call = glnvg__allocCall(gl);
12991 if (call is null) return;
12992 call.type = GLNVG_POPCLIP;
12995 void glnvg__renderResetClip (void* uptr) nothrow @trusted @nogc {
12996 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
12997 GLNVGcall* call = glnvg__allocCall(gl);
12998 if (call is null) return;
12999 call.type = GLNVG_RESETCLIP;
13002 void glnvg__clipDebugDump (void* uptr, bool doit) nothrow @trusted @nogc {
13003 version(nanovega_debug_clipping) {
13004 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13005 GLNVGcall* call = glnvg__allocCall(gl);
13006 call.type = (doit ? GLNVG_CLIP_DDUMP_ON : GLNVG_CLIP_DDUMP_OFF);
13010 bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc {
13011 import core.stdc.stdio : snprintf;
13013 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13014 enum align_ = 4;
13016 char[64] shaderHeader = void;
13017 //enum shaderHeader = "#define UNIFORM_ARRAY_SIZE 12\n";
13018 snprintf(shaderHeader.ptr, shaderHeader.length, "#define UNIFORM_ARRAY_SIZE %u\n", cast(uint)GLNVGfragUniforms.UNIFORM_ARRAY_SIZE);
13020 enum fillVertShader = q{
13021 uniform vec2 viewSize;
13022 attribute vec2 vertex;
13023 attribute vec2 tcoord;
13024 varying vec2 ftcoord;
13025 varying vec2 fpos;
13026 uniform vec4 tmat; /* abcd of affine matrix: xyzw */
13027 uniform vec2 ttr; /* tx and ty of affine matrix */
13028 void main (void) {
13029 /* affine transformation */
13030 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x;
13031 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y;
13032 ftcoord = tcoord;
13033 fpos = vec2(nx, ny);
13034 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1);
13038 enum fillFragShader = q{
13039 uniform vec4 frag[UNIFORM_ARRAY_SIZE];
13040 uniform sampler2D tex;
13041 uniform sampler2D clipTex;
13042 uniform vec2 viewSize;
13043 varying vec2 ftcoord;
13044 varying vec2 fpos;
13045 #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz)
13046 #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz)
13047 #define innerCol frag[6]
13048 #define middleCol frag[7]
13049 #define outerCol frag[7+1]
13050 #define scissorExt frag[8+1].xy
13051 #define scissorScale frag[8+1].zw
13052 #define extent frag[9+1].xy
13053 #define radius frag[9+1].z
13054 #define feather frag[9+1].w
13055 #define strokeMult frag[10+1].x
13056 #define strokeThr frag[10+1].y
13057 #define texType int(frag[10+1].z)
13058 #define type int(frag[10+1].w)
13059 #define doclip int(frag[11+1].x)
13060 #define midp frag[11+1].y
13062 float sdroundrect (in vec2 pt, in vec2 ext, in float rad) {
13063 vec2 ext2 = ext-vec2(rad, rad);
13064 vec2 d = abs(pt)-ext2;
13065 return min(max(d.x, d.y), 0.0)+length(max(d, 0.0))-rad;
13068 // Scissoring
13069 float scissorMask (in vec2 p) {
13070 vec2 sc = (abs((scissorMat*vec3(p, 1.0)).xy)-scissorExt);
13071 sc = vec2(0.5, 0.5)-sc*scissorScale;
13072 return clamp(sc.x, 0.0, 1.0)*clamp(sc.y, 0.0, 1.0);
13075 #ifdef EDGE_AA
13076 // Stroke - from [0..1] to clipped pyramid, where the slope is 1px.
13077 float strokeMask () {
13078 return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult)*min(1.0, ftcoord.y);
13080 #endif
13082 void main (void) {
13083 // clipping
13084 if (doclip != 0) {
13085 /*vec4 clr = texelFetch(clipTex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);*/
13086 vec4 clr = texture2D(clipTex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y));
13087 if (clr.r == 0.0) discard;
13089 float scissor = scissorMask(fpos);
13090 if (scissor <= 0.0) discard; //k8: is it really faster?
13091 #ifdef EDGE_AA
13092 float strokeAlpha = strokeMask();
13093 if (strokeAlpha < strokeThr) discard;
13094 #else
13095 float strokeAlpha = 1.0;
13096 #endif
13097 // rendering
13098 vec4 color;
13099 if (type == 0) { /* NSVG_SHADER_FILLCOLOR */
13100 color = innerCol;
13101 // Combine alpha
13102 color *= strokeAlpha*scissor;
13103 } else if (type == 1) { /* NSVG_SHADER_FILLGRAD */
13104 // Gradient
13105 // Calculate gradient color using box gradient
13106 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy;
13107 float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0);
13108 if (midp <= 0.0) {
13109 color = mix(innerCol, outerCol, d);
13110 } else {
13111 float gdst = min(midp, 1.0);
13112 if (d < gdst) {
13113 color = mix(innerCol, middleCol, d/gdst);
13114 } else {
13115 color = mix(middleCol, outerCol, (d-gdst)/gdst);
13118 // Combine alpha
13119 color *= strokeAlpha*scissor;
13120 } else if (type == 2) { /* NSVG_SHADER_FILLIMG */
13121 // Image
13122 // Calculate color from texture
13123 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy/extent;
13124 color = texture2D(tex, pt);
13125 if (texType == 1) color = vec4(color.xyz*color.w, color.w);
13126 if (texType == 2) color = vec4(color.x);
13127 // Apply color tint and alpha
13128 color *= innerCol;
13129 // Combine alpha
13130 color *= strokeAlpha*scissor;
13131 } else if (type == 3) { /* NSVG_SHADER_SIMPLE */
13132 // Stencil fill
13133 color = vec4(1, 1, 1, 1);
13134 } else if (type == 4) { /* NSVG_SHADER_IMG */
13135 // Textured tris
13136 color = texture2D(tex, ftcoord);
13137 if (texType == 1) color = vec4(color.xyz*color.w, color.w);
13138 if (texType == 2) color = vec4(color.x);
13139 color *= scissor;
13140 color *= innerCol; // Apply color tint
13142 gl_FragColor = color;
13146 enum clipVertShaderFill = q{
13147 uniform vec2 viewSize;
13148 attribute vec2 vertex;
13149 uniform vec4 tmat; /* abcd of affine matrix: xyzw */
13150 uniform vec2 ttr; /* tx and ty of affine matrix */
13151 void main (void) {
13152 /* affine transformation */
13153 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x;
13154 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y;
13155 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1);
13159 enum clipFragShaderFill = q{
13160 uniform vec2 viewSize;
13161 void main (void) {
13162 gl_FragColor = vec4(1, 1, 1, 1);
13166 enum clipVertShaderCopy = q{
13167 uniform vec2 viewSize;
13168 attribute vec2 vertex;
13169 void main (void) {
13170 gl_Position = vec4(2.0*vertex.x/viewSize.x-1.0, 1.0-2.0*vertex.y/viewSize.y, 0, 1);
13174 enum clipFragShaderCopy = q{
13175 uniform sampler2D tex;
13176 uniform vec2 viewSize;
13177 void main (void) {
13178 //gl_FragColor = texelFetch(tex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);
13179 gl_FragColor = texture2D(tex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y));
13183 glnvg__checkError(gl, "init");
13185 string defines = (gl.flags&NVGContextFlag.Antialias ? "#define EDGE_AA 1\n" : null);
13186 if (!glnvg__createShader(&gl.shader, "shader", shaderHeader.ptr, defines.ptr, fillVertShader, fillFragShader)) return false;
13187 if (!glnvg__createShader(&gl.shaderFillFBO, "shaderFillFBO", shaderHeader.ptr, defines.ptr, clipVertShaderFill, clipFragShaderFill)) return false;
13188 if (!glnvg__createShader(&gl.shaderCopyFBO, "shaderCopyFBO", shaderHeader.ptr, defines.ptr, clipVertShaderCopy, clipFragShaderCopy)) return false;
13190 glnvg__checkError(gl, "uniform locations");
13191 glnvg__getUniforms(&gl.shader);
13192 glnvg__getUniforms(&gl.shaderFillFBO);
13193 glnvg__getUniforms(&gl.shaderCopyFBO);
13195 // Create dynamic vertex array
13196 glGenBuffers(1, &gl.vertBuf);
13198 gl.fragSize = GLNVGfragUniforms.sizeof+align_-GLNVGfragUniforms.sizeof%align_;
13200 glnvg__checkError(gl, "create done");
13202 glFinish();
13204 return true;
13207 int glnvg__renderCreateTexture (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc {
13208 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13209 GLNVGtexture* tex = glnvg__allocTexture(gl);
13211 if (tex is null) return 0;
13213 glGenTextures(1, &tex.tex);
13214 tex.width = w;
13215 tex.height = h;
13216 tex.type = type;
13217 tex.flags = imageFlags;
13218 glnvg__bindTexture(gl, tex.tex);
13220 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("created texture with id %d; glid=%u\n", tex.id, tex.tex); }}
13222 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
13223 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width);
13224 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13225 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13227 // GL 1.4 and later has support for generating mipmaps using a tex parameter.
13228 if ((imageFlags&(NVGImageFlag.GenerateMipmaps|NVGImageFlag.NoFiltering)) == NVGImageFlag.GenerateMipmaps) glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
13230 immutable ttype = (type == NVGtexture.RGBA ? GL_RGBA : GL_RED);
13231 glTexImage2D(GL_TEXTURE_2D, 0, ttype, w, h, 0, ttype, GL_UNSIGNED_BYTE, data);
13233 immutable tfmin =
13234 (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST :
13235 imageFlags&NVGImageFlag.GenerateMipmaps ? GL_LINEAR_MIPMAP_LINEAR :
13236 GL_LINEAR);
13237 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tfmin);
13238 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST : GL_LINEAR));
13240 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, (imageFlags&NVGImageFlag.RepeatX ? GL_REPEAT : GL_CLAMP_TO_EDGE));
13241 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, (imageFlags&NVGImageFlag.RepeatY ? GL_REPEAT : GL_CLAMP_TO_EDGE));
13243 glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
13244 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
13245 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13246 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13248 glnvg__checkError(gl, "create tex");
13249 glnvg__bindTexture(gl, 0);
13251 return tex.id;
13254 bool glnvg__renderDeleteTexture (void* uptr, int image) nothrow @trusted @nogc {
13255 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13256 return glnvg__deleteTexture(gl, image);
13259 bool glnvg__renderTextureIncRef (void* uptr, int image) nothrow @trusted @nogc {
13260 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13261 GLNVGtexture* tex = glnvg__findTexture(gl, image);
13262 if (tex is null) {
13263 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT incref texture with id %d\n", image); }}
13264 return false;
13266 import core.atomic : atomicOp;
13267 atomicOp!"+="(tex.rc, 1);
13268 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("texture #%d: incref; newref=%d\n", image, tex.rc); }}
13269 return true;
13272 bool glnvg__renderUpdateTexture (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc {
13273 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13274 GLNVGtexture* tex = glnvg__findTexture(gl, image);
13276 if (tex is null) {
13277 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT update texture with id %d\n", image); }}
13278 return false;
13281 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("updated texture with id %d; glid=%u\n", tex.id, image, tex.tex); }}
13283 glnvg__bindTexture(gl, tex.tex);
13285 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
13286 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width);
13287 glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
13288 glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
13290 immutable ttype = (tex.type == NVGtexture.RGBA ? GL_RGBA : GL_RED);
13291 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, ttype, GL_UNSIGNED_BYTE, data);
13293 glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
13294 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
13295 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13296 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13298 glnvg__bindTexture(gl, 0);
13300 return true;
13303 bool glnvg__renderGetTextureSize (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc {
13304 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13305 GLNVGtexture* tex = glnvg__findTexture(gl, image);
13306 if (tex is null) {
13307 if (w !is null) *w = 0;
13308 if (h !is null) *h = 0;
13309 return false;
13310 } else {
13311 if (w !is null) *w = tex.width;
13312 if (h !is null) *h = tex.height;
13313 return true;
13317 void glnvg__xformToMat3x4 (float[] m3, const(float)[] t) nothrow @trusted @nogc {
13318 assert(t.length >= 6);
13319 assert(m3.length >= 12);
13320 m3.ptr[0] = t.ptr[0];
13321 m3.ptr[1] = t.ptr[1];
13322 m3.ptr[2] = 0.0f;
13323 m3.ptr[3] = 0.0f;
13324 m3.ptr[4] = t.ptr[2];
13325 m3.ptr[5] = t.ptr[3];
13326 m3.ptr[6] = 0.0f;
13327 m3.ptr[7] = 0.0f;
13328 m3.ptr[8] = t.ptr[4];
13329 m3.ptr[9] = t.ptr[5];
13330 m3.ptr[10] = 1.0f;
13331 m3.ptr[11] = 0.0f;
13334 NVGColor glnvg__premulColor() (in auto ref NVGColor c) nothrow @trusted @nogc {
13335 //pragma(inline, true);
13336 NVGColor res = void;
13337 res.r = c.r*c.a;
13338 res.g = c.g*c.a;
13339 res.b = c.b*c.a;
13340 res.a = c.a;
13341 return res;
13344 bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* paint, NVGscissor* scissor, float width, float fringe, float strokeThr) nothrow @trusted @nogc {
13345 import core.stdc.math : sqrtf;
13346 GLNVGtexture* tex = null;
13347 NVGMatrix invxform = void;
13349 memset(frag, 0, (*frag).sizeof);
13351 frag.innerCol = glnvg__premulColor(paint.innerColor);
13352 frag.middleCol = glnvg__premulColor(paint.middleColor);
13353 frag.outerCol = glnvg__premulColor(paint.outerColor);
13354 frag.midp = paint.midp;
13356 if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) {
13357 memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof);
13358 frag.scissorExt.ptr[0] = 1.0f;
13359 frag.scissorExt.ptr[1] = 1.0f;
13360 frag.scissorScale.ptr[0] = 1.0f;
13361 frag.scissorScale.ptr[1] = 1.0f;
13362 } else {
13363 //nvgTransformInverse(invxform[], scissor.xform[]);
13364 invxform = scissor.xform.inverted;
13365 glnvg__xformToMat3x4(frag.scissorMat[], invxform.mat[]);
13366 frag.scissorExt.ptr[0] = scissor.extent.ptr[0];
13367 frag.scissorExt.ptr[1] = scissor.extent.ptr[1];
13368 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;
13369 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;
13372 memcpy(frag.extent.ptr, paint.extent.ptr, frag.extent.sizeof);
13373 frag.strokeMult = (width*0.5f+fringe*0.5f)/fringe;
13374 frag.strokeThr = strokeThr;
13376 if (paint.image.valid) {
13377 tex = glnvg__findTexture(gl, paint.image.id);
13378 if (tex is null) return false;
13379 if ((tex.flags&NVGImageFlag.FlipY) != 0) {
13381 NVGMatrix flipped;
13382 nvgTransformScale(flipped[], 1.0f, -1.0f);
13383 nvgTransformMultiply(flipped[], paint.xform[]);
13384 nvgTransformInverse(invxform[], flipped[]);
13387 NVGMatrix m1 = void, m2 = void;
13388 nvgTransformTranslate(m1[], 0.0f, frag.extent.ptr[1]*0.5f);
13389 nvgTransformMultiply(m1[], paint.xform[]);
13390 nvgTransformScale(m2[], 1.0f, -1.0f);
13391 nvgTransformMultiply(m2[], m1[]);
13392 nvgTransformTranslate(m1[], 0.0f, -frag.extent.ptr[1]*0.5f);
13393 nvgTransformMultiply(m1[], m2[]);
13394 nvgTransformInverse(invxform[], m1[]);
13396 NVGMatrix m1 = NVGMatrix.Translated(0.0f, frag.extent.ptr[1]*0.5f);
13397 m1.mul(paint.xform);
13398 NVGMatrix m2 = NVGMatrix.Scaled(1.0f, -1.0f);
13399 m2.mul(m1);
13400 m1 = NVGMatrix.Translated(0.0f, -frag.extent.ptr[1]*0.5f);
13401 m1.mul(m2);
13402 invxform = m1.inverted;
13403 } else {
13404 //nvgTransformInverse(invxform[], paint.xform[]);
13405 invxform = paint.xform.inverted;
13407 frag.type = NSVG_SHADER_FILLIMG;
13409 if (tex.type == NVGtexture.RGBA) {
13410 frag.texType = (tex.flags&NVGImageFlag.Premultiplied ? 0 : 1);
13411 } else {
13412 frag.texType = 2;
13414 //printf("frag.texType = %d\n", frag.texType);
13415 } else {
13416 frag.type = (paint.simpleColor ? NSVG_SHADER_FILLCOLOR : NSVG_SHADER_FILLGRAD);
13417 frag.radius = paint.radius;
13418 frag.feather = paint.feather;
13419 //nvgTransformInverse(invxform[], paint.xform[]);
13420 invxform = paint.xform.inverted;
13423 glnvg__xformToMat3x4(frag.paintMat[], invxform.mat[]);
13425 return true;
13428 void glnvg__setUniforms (GLNVGcontext* gl, int uniformOffset, int image) nothrow @trusted @nogc {
13429 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset);
13430 glnvg__setFBOClipTexture(gl, frag);
13431 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.Frag], frag.UNIFORM_ARRAY_SIZE, &(frag.uniformArray.ptr[0].ptr[0]));
13432 glnvg__checkError(gl, "glnvg__setUniforms");
13433 if (image != 0) {
13434 GLNVGtexture* tex = glnvg__findTexture(gl, image);
13435 glnvg__bindTexture(gl, (tex !is null ? tex.tex : 0));
13436 glnvg__checkError(gl, "tex paint tex");
13437 } else {
13438 glnvg__bindTexture(gl, 0);
13442 void glnvg__finishClip (GLNVGcontext* gl, NVGClipMode clipmode) nothrow @trusted @nogc {
13443 assert(clipmode != NVGClipMode.None);
13445 // fill FBO, clear stencil buffer
13446 //TODO: optimize with bounds?
13447 version(all) {
13448 //glnvg__resetAffine(gl);
13449 //glUseProgram(gl.shaderFillFBO.prog);
13450 glDisable(GL_CULL_FACE);
13451 glDisable(GL_BLEND);
13452 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13453 glEnable(GL_STENCIL_TEST);
13454 if (gl.doClipUnion) {
13455 // for "and" we should clear everything that is NOT stencil-masked
13456 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13457 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13458 } else {
13459 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x00, 0xff);
13460 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13462 glBegin(GL_QUADS);
13463 glVertex2i(0, 0);
13464 glVertex2i(0, gl.fboHeight);
13465 glVertex2i(gl.fboWidth, gl.fboHeight);
13466 glVertex2i(gl.fboWidth, 0);
13467 glEnd();
13468 //glnvg__restoreAffine(gl);
13471 glBindFramebuffer(GL_FRAMEBUFFER, gl.mainFBO);
13472 glDisable(GL_COLOR_LOGIC_OP);
13473 //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // done above
13474 glEnable(GL_BLEND);
13475 glDisable(GL_STENCIL_TEST);
13476 glEnable(GL_CULL_FACE);
13477 glUseProgram(gl.shader.prog);
13479 // set current FBO as used one
13480 assert(gl.msp > 0 && gl.fbo.ptr[gl.msp-1] > 0 && gl.fboTex.ptr[gl.msp-1].ptr[0] > 0);
13481 if (gl.lastClipFBO != gl.msp-1) {
13482 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); }
13483 gl.lastClipFBO = gl.msp-1;
13484 glActiveTexture(GL_TEXTURE1);
13485 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
13486 glActiveTexture(GL_TEXTURE0);
13490 void glnvg__setClipUniforms (GLNVGcontext* gl, int uniformOffset, NVGClipMode clipmode) nothrow @trusted @nogc {
13491 assert(clipmode != NVGClipMode.None);
13492 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset);
13493 // save uniform offset for `glnvg__finishClip()`
13494 gl.lastClipUniOfs = uniformOffset;
13495 // get FBO index, bind this FBO
13496 immutable int clipTexId = glnvg__generateFBOClipTexture(gl);
13497 assert(clipTexId >= 0);
13498 glUseProgram(gl.shaderFillFBO.prog);
13499 glnvg__checkError(gl, "use");
13500 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[clipTexId]);
13501 // set logic op for clip
13502 gl.doClipUnion = false;
13503 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.JustCleared) {
13504 // it is cleared to zero, we can just draw a path
13505 glDisable(GL_COLOR_LOGIC_OP);
13506 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized;
13507 } else {
13508 glEnable(GL_COLOR_LOGIC_OP);
13509 final switch (clipmode) {
13510 case NVGClipMode.None: assert(0, "wtf?!");
13511 case NVGClipMode.Union: glLogicOp(GL_CLEAR); gl.doClipUnion = true; break; // use `GL_CLEAR` to avoid adding another shader mode
13512 case NVGClipMode.Or: glLogicOp(GL_COPY); break; // GL_OR
13513 case NVGClipMode.Xor: glLogicOp(GL_XOR); break;
13514 case NVGClipMode.Sub: glLogicOp(GL_CLEAR); break;
13515 case NVGClipMode.Replace: glLogicOp(GL_COPY); break;
13518 // set affine matrix
13519 glUniform4fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TMat], 1, gl.lastAffine.mat.ptr);
13520 glnvg__checkError(gl, "affine 0");
13521 glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TTr], 1, gl.lastAffine.mat.ptr+4);
13522 glnvg__checkError(gl, "affine 1");
13523 // setup common OpenGL parameters
13524 glDisable(GL_BLEND);
13525 glDisable(GL_CULL_FACE);
13526 glEnable(GL_STENCIL_TEST);
13527 glnvg__stencilMask(gl, 0xff);
13528 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13529 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
13530 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13533 void glnvg__renderViewport (void* uptr, int width, int height) nothrow @trusted @nogc {
13534 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13535 gl.inFrame = true;
13536 gl.view.ptr[0] = cast(float)width;
13537 gl.view.ptr[1] = cast(float)height;
13538 // kill FBOs if we need to create new ones (flushing will recreate 'em if necessary)
13539 if (width != gl.fboWidth || height != gl.fboHeight) {
13540 glnvg__killFBOs(gl);
13541 gl.fboWidth = width;
13542 gl.fboHeight = height;
13544 gl.msp = 1;
13545 gl.maskStack.ptr[0] = GLMaskState.DontMask;
13546 // texture cleanup
13547 import core.atomic : atomicLoad;
13548 if (atomicLoad(gl.mustCleanTextures)) {
13549 try {
13550 import core.thread : Thread;
13551 static if (__VERSION__ < 2076) {
13552 DGNoThrowNoGC(() {
13553 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13554 })();
13555 } else {
13556 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13558 synchronized(GLNVGTextureLocker.classinfo) {
13559 gl.mustCleanTextures = false;
13560 foreach (immutable tidx, ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) {
13561 // no need to use atomic ops here, as we're locked
13562 if (tex.rc == 0 && tex.tex != 0 && tex.id == 0) {
13563 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** cleaned up texture with glid=%u\n", tex.tex); }}
13564 import core.stdc.string : memset;
13565 if ((tex.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tex.tex);
13566 memset(&tex, 0, tex.sizeof);
13567 tex.nextfree = gl.freetexid;
13568 gl.freetexid = cast(int)tidx;
13572 } catch (Exception e) {}
13576 void glnvg__fill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13577 GLNVGpath* paths = &gl.paths[call.pathOffset];
13578 int npaths = call.pathCount;
13580 if (call.clipmode == NVGClipMode.None) {
13581 // Draw shapes
13582 glEnable(GL_STENCIL_TEST);
13583 glnvg__stencilMask(gl, 0xffU);
13584 glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xffU);
13586 glnvg__setUniforms(gl, call.uniformOffset, 0);
13587 glnvg__checkError(gl, "fill simple");
13589 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13590 if (call.evenOdd) {
13591 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT);
13592 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT);
13593 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
13594 } else {
13595 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
13596 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
13598 glDisable(GL_CULL_FACE);
13599 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13600 glEnable(GL_CULL_FACE);
13602 // Draw anti-aliased pixels
13603 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13604 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image);
13605 glnvg__checkError(gl, "fill fill");
13607 if (gl.flags&NVGContextFlag.Antialias) {
13608 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xffU);
13609 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13610 // Draw fringes
13611 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13614 // Draw fill
13615 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xffU);
13616 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13617 if (call.evenOdd) {
13618 glDisable(GL_CULL_FACE);
13619 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount);
13620 //foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13621 glEnable(GL_CULL_FACE);
13622 } else {
13623 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount);
13626 glDisable(GL_STENCIL_TEST);
13627 } else {
13628 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); // this activates our FBO
13629 glnvg__checkError(gl, "fillclip simple");
13630 glnvg__stencilFunc(gl, GL_ALWAYS, 0x00, 0xffU);
13631 if (call.evenOdd) {
13632 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT);
13633 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT);
13634 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
13635 } else {
13636 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
13637 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
13639 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13640 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13644 void glnvg__convexFill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13645 GLNVGpath* paths = &gl.paths[call.pathOffset];
13646 int npaths = call.pathCount;
13648 if (call.clipmode == NVGClipMode.None) {
13649 glnvg__setUniforms(gl, call.uniformOffset, call.image);
13650 glnvg__checkError(gl, "convex fill");
13651 if (call.evenOdd) glDisable(GL_CULL_FACE);
13652 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13653 if (gl.flags&NVGContextFlag.Antialias) {
13654 // Draw fringes
13655 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13657 if (call.evenOdd) glEnable(GL_CULL_FACE);
13658 } else {
13659 glnvg__setClipUniforms(gl, call.uniformOffset, call.clipmode); // this activates our FBO
13660 glnvg__checkError(gl, "clip convex fill");
13661 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13662 if (gl.flags&NVGContextFlag.Antialias) {
13663 // Draw fringes
13664 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13666 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13670 void glnvg__stroke (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13671 GLNVGpath* paths = &gl.paths[call.pathOffset];
13672 int npaths = call.pathCount;
13674 if (call.clipmode == NVGClipMode.None) {
13675 if (gl.flags&NVGContextFlag.StencilStrokes) {
13676 glEnable(GL_STENCIL_TEST);
13677 glnvg__stencilMask(gl, 0xff);
13679 // Fill the stroke base without overlap
13680 glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff);
13681 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
13682 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image);
13683 glnvg__checkError(gl, "stroke fill 0");
13684 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13686 // Draw anti-aliased pixels.
13687 glnvg__setUniforms(gl, call.uniformOffset, call.image);
13688 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13689 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13690 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13692 // Clear stencil buffer.
13693 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13694 glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff);
13695 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13696 glnvg__checkError(gl, "stroke fill 1");
13697 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13698 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13700 glDisable(GL_STENCIL_TEST);
13702 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f);
13703 } else {
13704 glnvg__setUniforms(gl, call.uniformOffset, call.image);
13705 glnvg__checkError(gl, "stroke fill");
13706 // Draw Strokes
13707 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13709 } else {
13710 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode);
13711 glnvg__checkError(gl, "stroke fill 0");
13712 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13713 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13717 void glnvg__triangles (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13718 if (call.clipmode == NVGClipMode.None) {
13719 glnvg__setUniforms(gl, call.uniformOffset, call.image);
13720 glnvg__checkError(gl, "triangles fill");
13721 glDrawArrays(GL_TRIANGLES, call.triangleOffset, call.triangleCount);
13722 } else {
13723 //TODO(?): use texture as mask?
13727 void glnvg__affine (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13728 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, call.affine.mat.ptr);
13729 glnvg__checkError(gl, "affine");
13730 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, call.affine.mat.ptr+4);
13731 glnvg__checkError(gl, "affine");
13732 //glnvg__setUniforms(gl, call.uniformOffset, call.image);
13735 void glnvg__renderCancelInternal (GLNVGcontext* gl, bool clearTextures) nothrow @trusted @nogc {
13736 scope(exit) gl.inFrame = false;
13737 if (clearTextures && gl.inFrame) {
13738 try {
13739 import core.thread : Thread;
13740 static if (__VERSION__ < 2076) {
13741 DGNoThrowNoGC(() {
13742 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13743 })();
13744 } else {
13745 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13747 } catch (Exception e) {}
13748 foreach (ref GLNVGcall c; gl.calls[0..gl.ncalls]) if (c.image > 0) glnvg__deleteTexture(gl, c.image);
13750 gl.nverts = 0;
13751 gl.npaths = 0;
13752 gl.ncalls = 0;
13753 gl.nuniforms = 0;
13754 gl.msp = 1;
13755 gl.maskStack.ptr[0] = GLMaskState.DontMask;
13758 void glnvg__renderCancel (void* uptr) nothrow @trusted @nogc {
13759 glnvg__renderCancelInternal(cast(GLNVGcontext*)uptr, true);
13762 GLenum glnvg_convertBlendFuncFactor (NVGBlendFactor factor) pure nothrow @trusted @nogc {
13763 if (factor == NVGBlendFactor.Zero) return GL_ZERO;
13764 if (factor == NVGBlendFactor.One) return GL_ONE;
13765 if (factor == NVGBlendFactor.SrcColor) return GL_SRC_COLOR;
13766 if (factor == NVGBlendFactor.OneMinusSrcColor) return GL_ONE_MINUS_SRC_COLOR;
13767 if (factor == NVGBlendFactor.DstColor) return GL_DST_COLOR;
13768 if (factor == NVGBlendFactor.OneMinusDstColor) return GL_ONE_MINUS_DST_COLOR;
13769 if (factor == NVGBlendFactor.SrcAlpha) return GL_SRC_ALPHA;
13770 if (factor == NVGBlendFactor.OneMinusSrcAlpha) return GL_ONE_MINUS_SRC_ALPHA;
13771 if (factor == NVGBlendFactor.DstAlpha) return GL_DST_ALPHA;
13772 if (factor == NVGBlendFactor.OneMinusDstAlpha) return GL_ONE_MINUS_DST_ALPHA;
13773 if (factor == NVGBlendFactor.SrcAlphaSaturate) return GL_SRC_ALPHA_SATURATE;
13774 return GL_INVALID_ENUM;
13777 GLNVGblend glnvg__buildBlendFunc (NVGCompositeOperationState op) pure nothrow @trusted @nogc {
13778 GLNVGblend res;
13779 res.simple = op.simple;
13780 res.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB);
13781 res.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB);
13782 res.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha);
13783 res.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha);
13784 if (res.simple) {
13785 if (res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) {
13786 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM;
13788 } else {
13789 if (res.srcRGB == GL_INVALID_ENUM || res.dstRGB == GL_INVALID_ENUM || res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) {
13790 res.simple = true;
13791 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM;
13794 return res;
13797 void glnvg__blendCompositeOperation() (GLNVGcontext* gl, in auto ref GLNVGblend op) nothrow @trusted @nogc {
13798 //glBlendFuncSeparate(glnvg_convertBlendFuncFactor(op.srcRGB), glnvg_convertBlendFuncFactor(op.dstRGB), glnvg_convertBlendFuncFactor(op.srcAlpha), glnvg_convertBlendFuncFactor(op.dstAlpha));
13799 static if (NANOVG_GL_USE_STATE_FILTER) {
13800 if (gl.blendFunc.simple == op.simple) {
13801 if (op.simple) {
13802 if (gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return;
13803 } else {
13804 if (gl.blendFunc.srcRGB == op.srcRGB && gl.blendFunc.dstRGB == op.dstRGB && gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return;
13807 gl.blendFunc = op;
13809 if (op.simple) {
13810 if (op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) {
13811 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13812 } else {
13813 glBlendFunc(op.srcAlpha, op.dstAlpha);
13815 } else {
13816 if (op.srcRGB == GL_INVALID_ENUM || op.dstRGB == GL_INVALID_ENUM || op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) {
13817 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13818 } else {
13819 glBlendFuncSeparate(op.srcRGB, op.dstRGB, op.srcAlpha, op.dstAlpha);
13824 void glnvg__renderSetAffine (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc {
13825 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13826 GLNVGcall* call;
13827 // if last operation was GLNVG_AFFINE, simply replace the matrix
13828 if (gl.ncalls > 0 && gl.calls[gl.ncalls-1].type == GLNVG_AFFINE) {
13829 call = &gl.calls[gl.ncalls-1];
13830 } else {
13831 call = glnvg__allocCall(gl);
13832 if (call is null) return;
13833 call.type = GLNVG_AFFINE;
13835 call.affine.mat.ptr[0..6] = mat.mat.ptr[0..6];
13838 version(nanovega_debug_clipping) public __gshared bool nanovegaClipDebugDump = false;
13840 void glnvg__renderFlush (void* uptr) nothrow @trusted @nogc {
13841 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13842 if (!gl.inFrame) assert(0, "NanoVega: internal driver error");
13843 try {
13844 import core.thread : Thread;
13845 static if (__VERSION__ < 2076) {
13846 DGNoThrowNoGC(() {
13847 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13848 })();
13849 } else {
13850 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13852 } catch (Exception e) {}
13853 scope(exit) gl.inFrame = false;
13855 glnvg__resetError!true(gl);
13857 int vv = 0;
13858 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &vv);
13859 if (glGetError() || vv < 0) vv = 0;
13860 gl.mainFBO = cast(uint)vv;
13863 enum ShaderType { None, Fill, Clip }
13864 auto lastShader = ShaderType.None;
13865 if (gl.ncalls > 0) {
13866 gl.msp = 1;
13867 gl.maskStack.ptr[0] = GLMaskState.DontMask;
13869 // Setup require GL state.
13870 glUseProgram(gl.shader.prog);
13872 glActiveTexture(GL_TEXTURE1);
13873 glBindTexture(GL_TEXTURE_2D, 0);
13874 glActiveTexture(GL_TEXTURE0);
13875 glnvg__resetFBOClipTextureCache(gl);
13877 //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13878 static if (NANOVG_GL_USE_STATE_FILTER) {
13879 gl.blendFunc.simple = true;
13880 gl.blendFunc.srcRGB = gl.blendFunc.dstRGB = gl.blendFunc.srcAlpha = gl.blendFunc.dstAlpha = GL_INVALID_ENUM;
13882 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // just in case
13883 glEnable(GL_CULL_FACE);
13884 glCullFace(GL_BACK);
13885 glFrontFace(GL_CCW);
13886 glEnable(GL_BLEND);
13887 glDisable(GL_DEPTH_TEST);
13888 glDisable(GL_SCISSOR_TEST);
13889 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13890 glStencilMask(0xffffffff);
13891 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13892 glStencilFunc(GL_ALWAYS, 0, 0xffffffff);
13893 glActiveTexture(GL_TEXTURE0);
13894 glBindTexture(GL_TEXTURE_2D, 0);
13895 static if (NANOVG_GL_USE_STATE_FILTER) {
13896 gl.boundTexture = 0;
13897 gl.stencilMask = 0xffffffff;
13898 gl.stencilFunc = GL_ALWAYS;
13899 gl.stencilFuncRef = 0;
13900 gl.stencilFuncMask = 0xffffffff;
13902 glnvg__checkError(gl, "OpenGL setup");
13904 // Upload vertex data
13905 glBindBuffer(GL_ARRAY_BUFFER, gl.vertBuf);
13906 glBufferData(GL_ARRAY_BUFFER, gl.nverts*NVGVertex.sizeof, gl.verts, GL_STREAM_DRAW);
13907 glEnableVertexAttribArray(0);
13908 glEnableVertexAttribArray(1);
13909 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)cast(usize)0);
13910 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)(0+2*float.sizeof));
13911 glnvg__checkError(gl, "vertex data uploading");
13913 // Set view and texture just once per frame.
13914 glUniform1i(gl.shader.loc[GLNVGuniformLoc.Tex], 0);
13915 if (gl.shader.loc[GLNVGuniformLoc.ClipTex] != -1) {
13916 //{ import core.stdc.stdio; printf("%d\n", gl.shader.loc[GLNVGuniformLoc.ClipTex]); }
13917 glUniform1i(gl.shader.loc[GLNVGuniformLoc.ClipTex], 1);
13919 if (gl.shader.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shader.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
13920 glnvg__checkError(gl, "render shader setup");
13922 // Reset affine transformations.
13923 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, NVGMatrix.IdentityMat.ptr);
13924 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, NVGMatrix.IdentityMat.ptr+4);
13925 glnvg__checkError(gl, "affine setup");
13927 // set clip shaders params
13928 // fill
13929 glUseProgram(gl.shaderFillFBO.prog);
13930 glnvg__checkError(gl, "clip shaders setup (fill 0)");
13931 if (gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
13932 glnvg__checkError(gl, "clip shaders setup (fill 1)");
13933 // copy
13934 glUseProgram(gl.shaderCopyFBO.prog);
13935 glnvg__checkError(gl, "clip shaders setup (copy 0)");
13936 if (gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
13937 glnvg__checkError(gl, "clip shaders setup (copy 1)");
13938 //glUniform1i(gl.shaderFillFBO.loc[GLNVGuniformLoc.Tex], 0);
13939 glUniform1i(gl.shaderCopyFBO.loc[GLNVGuniformLoc.Tex], 0);
13940 glnvg__checkError(gl, "clip shaders setup (copy 2)");
13941 // restore render shader
13942 glUseProgram(gl.shader.prog);
13944 //{ 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]); }
13946 gl.lastAffine.identity;
13948 foreach (int i; 0..gl.ncalls) {
13949 GLNVGcall* call = &gl.calls[i];
13950 switch (call.type) {
13951 case GLNVG_FILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__fill(gl, call); break;
13952 case GLNVG_CONVEXFILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__convexFill(gl, call); break;
13953 case GLNVG_STROKE: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__stroke(gl, call); break;
13954 case GLNVG_TRIANGLES: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__triangles(gl, call); break;
13955 case GLNVG_AFFINE: gl.lastAffine = call.affine; glnvg__affine(gl, call); break;
13956 // clip region management
13957 case GLNVG_PUSHCLIP:
13958 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]); }
13959 if (gl.msp >= gl.maskStack.length) assert(0, "NanoVega: mask stack overflow in OpenGL backend");
13960 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.DontMask) {
13961 gl.maskStack.ptr[gl.msp++] = GLMaskState.DontMask;
13962 } else {
13963 gl.maskStack.ptr[gl.msp++] = GLMaskState.Uninitialized;
13965 // no need to reset FBO cache here, as nothing was changed
13966 break;
13967 case GLNVG_POPCLIP:
13968 if (gl.msp <= 1) assert(0, "NanoVega: mask stack underflow in OpenGL backend");
13969 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]); }
13970 --gl.msp;
13971 assert(gl.msp > 0);
13972 //{ import core.stdc.stdio; printf("popped; new msp is %d; state is %d\n", gl.msp, gl.maskStack.ptr[gl.msp]); }
13973 // check popped item
13974 final switch (gl.maskStack.ptr[gl.msp]) {
13975 case GLMaskState.DontMask:
13976 // if last FBO was "don't mask", reset cache if current is not "don't mask"
13977 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) {
13978 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); }
13979 glnvg__resetFBOClipTextureCache(gl);
13981 break;
13982 case GLMaskState.Uninitialized:
13983 // if last FBO texture was uninitialized, it means that nothing was changed,
13984 // so we can keep using cached FBO
13985 break;
13986 case GLMaskState.Initialized:
13987 // if last FBO was initialized, it means that something was definitely changed
13988 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); }
13989 glnvg__resetFBOClipTextureCache(gl);
13990 break;
13991 case GLMaskState.JustCleared: assert(0, "NanoVega: internal FBO stack error");
13993 break;
13994 case GLNVG_RESETCLIP:
13995 // mark current mask as "don't mask"
13996 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]); }
13997 if (gl.msp > 0) {
13998 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) {
13999 gl.maskStack.ptr[gl.msp-1] = GLMaskState.DontMask;
14000 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); }
14001 glnvg__resetFBOClipTextureCache(gl);
14004 break;
14005 case GLNVG_CLIP_DDUMP_ON:
14006 version(nanovega_debug_clipping) nanovegaClipDebugDump = true;
14007 break;
14008 case GLNVG_CLIP_DDUMP_OFF:
14009 version(nanovega_debug_clipping) nanovegaClipDebugDump = false;
14010 break;
14011 case GLNVG_NONE: break;
14012 default:
14014 import core.stdc.stdio; stderr.fprintf("NanoVega FATAL: invalid command in OpenGL backend: %d\n", call.type);
14016 assert(0, "NanoVega: invalid command in OpenGL backend (fatal internal error)");
14018 // and free texture, why not
14019 glnvg__deleteTexture(gl, call.image);
14022 glDisableVertexAttribArray(0);
14023 glDisableVertexAttribArray(1);
14024 glDisable(GL_CULL_FACE);
14025 glBindBuffer(GL_ARRAY_BUFFER, 0);
14026 glUseProgram(0);
14027 glnvg__bindTexture(gl, 0);
14030 // this will do all necessary cleanup
14031 glnvg__renderCancelInternal(gl, false); // no need to clear textures
14034 int glnvg__maxVertCount (const(NVGpath)* paths, int npaths) nothrow @trusted @nogc {
14035 int count = 0;
14036 foreach (int i; 0..npaths) {
14037 count += paths[i].nfill;
14038 count += paths[i].nstroke;
14040 return count;
14043 GLNVGcall* glnvg__allocCall (GLNVGcontext* gl) nothrow @trusted @nogc {
14044 GLNVGcall* ret = null;
14045 if (gl.ncalls+1 > gl.ccalls) {
14046 GLNVGcall* calls;
14047 int ccalls = glnvg__maxi(gl.ncalls+1, 128)+gl.ccalls/2; // 1.5x Overallocate
14048 calls = cast(GLNVGcall*)realloc(gl.calls, GLNVGcall.sizeof*ccalls);
14049 if (calls is null) return null;
14050 gl.calls = calls;
14051 gl.ccalls = ccalls;
14053 ret = &gl.calls[gl.ncalls++];
14054 memset(ret, 0, GLNVGcall.sizeof);
14055 return ret;
14058 int glnvg__allocPaths (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14059 int ret = 0;
14060 if (gl.npaths+n > gl.cpaths) {
14061 GLNVGpath* paths;
14062 int cpaths = glnvg__maxi(gl.npaths+n, 128)+gl.cpaths/2; // 1.5x Overallocate
14063 paths = cast(GLNVGpath*)realloc(gl.paths, GLNVGpath.sizeof*cpaths);
14064 if (paths is null) return -1;
14065 gl.paths = paths;
14066 gl.cpaths = cpaths;
14068 ret = gl.npaths;
14069 gl.npaths += n;
14070 return ret;
14073 int glnvg__allocVerts (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14074 int ret = 0;
14075 if (gl.nverts+n > gl.cverts) {
14076 NVGVertex* verts;
14077 int cverts = glnvg__maxi(gl.nverts+n, 4096)+gl.cverts/2; // 1.5x Overallocate
14078 verts = cast(NVGVertex*)realloc(gl.verts, NVGVertex.sizeof*cverts);
14079 if (verts is null) return -1;
14080 gl.verts = verts;
14081 gl.cverts = cverts;
14083 ret = gl.nverts;
14084 gl.nverts += n;
14085 return ret;
14088 int glnvg__allocFragUniforms (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14089 int ret = 0, structSize = gl.fragSize;
14090 if (gl.nuniforms+n > gl.cuniforms) {
14091 ubyte* uniforms;
14092 int cuniforms = glnvg__maxi(gl.nuniforms+n, 128)+gl.cuniforms/2; // 1.5x Overallocate
14093 uniforms = cast(ubyte*)realloc(gl.uniforms, structSize*cuniforms);
14094 if (uniforms is null) return -1;
14095 gl.uniforms = uniforms;
14096 gl.cuniforms = cuniforms;
14098 ret = gl.nuniforms*structSize;
14099 gl.nuniforms += n;
14100 return ret;
14103 GLNVGfragUniforms* nvg__fragUniformPtr (GLNVGcontext* gl, int i) nothrow @trusted @nogc {
14104 return cast(GLNVGfragUniforms*)&gl.uniforms[i];
14107 void glnvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc {
14108 vtx.x = x;
14109 vtx.y = y;
14110 vtx.u = u;
14111 vtx.v = v;
14114 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 {
14115 if (npaths < 1) return;
14117 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14118 GLNVGcall* call = glnvg__allocCall(gl);
14119 NVGVertex* quad;
14120 GLNVGfragUniforms* frag;
14121 int maxverts, offset;
14123 if (call is null) return;
14125 call.type = GLNVG_FILL;
14126 call.evenOdd = evenOdd;
14127 call.clipmode = clipmode;
14128 //if (clipmode != NVGClipMode.None) { import core.stdc.stdio; printf("CLIP!\n"); }
14129 call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14130 call.triangleCount = 4;
14131 call.pathOffset = glnvg__allocPaths(gl, npaths);
14132 if (call.pathOffset == -1) goto error;
14133 call.pathCount = npaths;
14134 call.image = paint.image.id;
14135 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14137 if (npaths == 1 && paths[0].convex) {
14138 call.type = GLNVG_CONVEXFILL;
14139 call.triangleCount = 0; // Bounding box fill quad not needed for convex fill
14142 // Allocate vertices for all the paths.
14143 maxverts = glnvg__maxVertCount(paths, npaths)+call.triangleCount;
14144 offset = glnvg__allocVerts(gl, maxverts);
14145 if (offset == -1) goto error;
14147 foreach (int i; 0..npaths) {
14148 GLNVGpath* copy = &gl.paths[call.pathOffset+i];
14149 const(NVGpath)* path = &paths[i];
14150 memset(copy, 0, GLNVGpath.sizeof);
14151 if (path.nfill > 0) {
14152 copy.fillOffset = offset;
14153 copy.fillCount = path.nfill;
14154 memcpy(&gl.verts[offset], path.fill, NVGVertex.sizeof*path.nfill);
14155 offset += path.nfill;
14157 if (path.nstroke > 0) {
14158 copy.strokeOffset = offset;
14159 copy.strokeCount = path.nstroke;
14160 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke);
14161 offset += path.nstroke;
14165 // Setup uniforms for draw calls
14166 if (call.type == GLNVG_FILL) {
14167 import core.stdc.string : memcpy;
14168 // Quad
14169 call.triangleOffset = offset;
14170 quad = &gl.verts[call.triangleOffset];
14171 glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f);
14172 glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f);
14173 glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f);
14174 glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f);
14175 // Get uniform
14176 call.uniformOffset = glnvg__allocFragUniforms(gl, 2);
14177 if (call.uniformOffset == -1) goto error;
14178 // Simple shader for stencil
14179 frag = nvg__fragUniformPtr(gl, call.uniformOffset);
14180 memset(frag, 0, (*frag).sizeof);
14181 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f);
14182 memcpy(nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), frag, (*frag).sizeof);
14183 frag.strokeThr = -1.0f;
14184 frag.type = NSVG_SHADER_SIMPLE;
14185 // Fill shader
14186 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, fringe, fringe, -1.0f);
14187 } else {
14188 call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14189 if (call.uniformOffset == -1) goto error;
14190 // Fill shader
14191 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f);
14194 return;
14196 error:
14197 // We get here if call alloc was ok, but something else is not.
14198 // Roll back the last call to prevent drawing it.
14199 if (gl.ncalls > 0) --gl.ncalls;
14202 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 {
14203 if (npaths < 1) return;
14205 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14206 GLNVGcall* call = glnvg__allocCall(gl);
14207 int maxverts, offset;
14209 if (call is null) return;
14211 call.type = GLNVG_STROKE;
14212 call.clipmode = clipmode;
14213 call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14214 call.pathOffset = glnvg__allocPaths(gl, npaths);
14215 if (call.pathOffset == -1) goto error;
14216 call.pathCount = npaths;
14217 call.image = paint.image.id;
14218 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14220 // Allocate vertices for all the paths.
14221 maxverts = glnvg__maxVertCount(paths, npaths);
14222 offset = glnvg__allocVerts(gl, maxverts);
14223 if (offset == -1) goto error;
14225 foreach (int i; 0..npaths) {
14226 GLNVGpath* copy = &gl.paths[call.pathOffset+i];
14227 const(NVGpath)* path = &paths[i];
14228 memset(copy, 0, GLNVGpath.sizeof);
14229 if (path.nstroke) {
14230 copy.strokeOffset = offset;
14231 copy.strokeCount = path.nstroke;
14232 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke);
14233 offset += path.nstroke;
14237 if (gl.flags&NVGContextFlag.StencilStrokes) {
14238 // Fill shader
14239 call.uniformOffset = glnvg__allocFragUniforms(gl, 2);
14240 if (call.uniformOffset == -1) goto error;
14241 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
14242 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f);
14243 } else {
14244 // Fill shader
14245 call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14246 if (call.uniformOffset == -1) goto error;
14247 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
14250 return;
14252 error:
14253 // We get here if call alloc was ok, but something else is not.
14254 // Roll back the last call to prevent drawing it.
14255 if (gl.ncalls > 0) --gl.ncalls;
14258 void glnvg__renderTriangles (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc {
14259 if (nverts < 1) return;
14261 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14262 GLNVGcall* call = glnvg__allocCall(gl);
14263 GLNVGfragUniforms* frag;
14265 if (call is null) return;
14267 call.type = GLNVG_TRIANGLES;
14268 call.clipmode = clipmode;
14269 call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14270 call.image = paint.image.id;
14271 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14273 // Allocate vertices for all the paths.
14274 call.triangleOffset = glnvg__allocVerts(gl, nverts);
14275 if (call.triangleOffset == -1) goto error;
14276 call.triangleCount = nverts;
14278 memcpy(&gl.verts[call.triangleOffset], verts, NVGVertex.sizeof*nverts);
14280 // Fill shader
14281 call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14282 if (call.uniformOffset == -1) goto error;
14283 frag = nvg__fragUniformPtr(gl, call.uniformOffset);
14284 glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f);
14285 frag.type = NSVG_SHADER_IMG;
14287 return;
14289 error:
14290 // We get here if call alloc was ok, but something else is not.
14291 // Roll back the last call to prevent drawing it.
14292 if (gl.ncalls > 0) --gl.ncalls;
14295 void glnvg__renderDelete (void* uptr) nothrow @trusted @nogc {
14296 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14297 if (gl is null) return;
14299 glnvg__killFBOs(gl);
14300 glnvg__deleteShader(&gl.shader);
14301 glnvg__deleteShader(&gl.shaderFillFBO);
14302 glnvg__deleteShader(&gl.shaderCopyFBO);
14304 if (gl.vertBuf != 0) glDeleteBuffers(1, &gl.vertBuf);
14306 foreach (ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) {
14307 if (tex.id != 0 && (tex.flags&NVGImageFlag.NoDelete) == 0) {
14308 assert(tex.tex != 0);
14309 glDeleteTextures(1, &tex.tex);
14312 free(gl.textures);
14314 free(gl.paths);
14315 free(gl.verts);
14316 free(gl.uniforms);
14317 free(gl.calls);
14319 free(gl);
14323 /** Creates NanoVega contexts for OpenGL2+.
14325 * Specify creation flags as additional arguments, like this:
14326 * `nvgCreateContext(NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes);`
14328 * If you won't specify any flags, defaults will be used:
14329 * `[NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes]`.
14331 * Group: context_management
14333 public NVGContext nvgCreateContext (const(NVGContextFlag)[] flagList...) nothrow @trusted @nogc {
14334 version(aliced) {
14335 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA;
14336 } else {
14337 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes;
14339 uint flags = 0;
14340 if (flagList.length != 0) {
14341 foreach (immutable flg; flagList) flags |= (flg != NVGContextFlag.Default ? flg : DefaultFlags);
14342 } else {
14343 flags = DefaultFlags;
14345 NVGparams params = void;
14346 NVGContext ctx = null;
14347 version(nanovg_builtin_opengl_bindings) nanovgInitOpenGL(); // why not?
14348 GLNVGcontext* gl = cast(GLNVGcontext*)malloc(GLNVGcontext.sizeof);
14349 if (gl is null) goto error;
14350 memset(gl, 0, GLNVGcontext.sizeof);
14352 memset(&params, 0, params.sizeof);
14353 params.renderCreate = &glnvg__renderCreate;
14354 params.renderCreateTexture = &glnvg__renderCreateTexture;
14355 params.renderTextureIncRef = &glnvg__renderTextureIncRef;
14356 params.renderDeleteTexture = &glnvg__renderDeleteTexture;
14357 params.renderUpdateTexture = &glnvg__renderUpdateTexture;
14358 params.renderGetTextureSize = &glnvg__renderGetTextureSize;
14359 params.renderViewport = &glnvg__renderViewport;
14360 params.renderCancel = &glnvg__renderCancel;
14361 params.renderFlush = &glnvg__renderFlush;
14362 params.renderPushClip = &glnvg__renderPushClip;
14363 params.renderPopClip = &glnvg__renderPopClip;
14364 params.renderResetClip = &glnvg__renderResetClip;
14365 params.renderFill = &glnvg__renderFill;
14366 params.renderStroke = &glnvg__renderStroke;
14367 params.renderTriangles = &glnvg__renderTriangles;
14368 params.renderSetAffine = &glnvg__renderSetAffine;
14369 params.renderDelete = &glnvg__renderDelete;
14370 params.userPtr = gl;
14371 params.edgeAntiAlias = (flags&NVGContextFlag.Antialias ? true : false);
14372 if (flags&(NVGContextFlag.FontAA|NVGContextFlag.FontNoAA)) {
14373 params.fontAA = (flags&NVGContextFlag.FontNoAA ? NVG_INVERT_FONT_AA : !NVG_INVERT_FONT_AA);
14374 } else {
14375 params.fontAA = NVG_INVERT_FONT_AA;
14378 gl.flags = flags;
14379 gl.freetexid = -1;
14381 ctx = createInternal(&params);
14382 if (ctx is null) goto error;
14384 static if (__VERSION__ < 2076) {
14385 DGNoThrowNoGC(() { import core.thread; gl.mainTID = Thread.getThis.id; })();
14386 } else {
14387 try { import core.thread; gl.mainTID = Thread.getThis.id; } catch (Exception e) {}
14390 return ctx;
14392 error:
14393 // 'gl' is freed by nvgDeleteInternal.
14394 if (ctx !is null) ctx.deleteInternal();
14395 return null;
14398 /// Create NanoVega OpenGL image from texture id.
14399 /// Group: images
14400 public int glCreateImageFromHandleGL2 (NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc {
14401 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr;
14402 GLNVGtexture* tex = glnvg__allocTexture(gl);
14404 if (tex is null) return 0;
14406 tex.type = NVGtexture.RGBA;
14407 tex.tex = textureId;
14408 tex.flags = imageFlags;
14409 tex.width = w;
14410 tex.height = h;
14412 return tex.id;
14415 /// Returns OpenGL texture id for NanoVega image.
14416 /// Group: images
14417 public GLuint glImageHandleGL2 (NVGContext ctx, int image) nothrow @trusted @nogc {
14418 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr;
14419 GLNVGtexture* tex = glnvg__findTexture(gl, image);
14420 return tex.tex;
14424 // ////////////////////////////////////////////////////////////////////////// //
14425 private:
14427 static if (NanoVegaHasFontConfig) {
14428 version(nanovg_builtin_fontconfig_bindings) {
14429 pragma(lib, "fontconfig");
14431 private extern(C) nothrow @trusted @nogc {
14432 enum FC_FILE = "file"; /* String */
14433 alias FcBool = int;
14434 alias FcChar8 = char;
14435 struct FcConfig;
14436 struct FcPattern;
14437 alias FcMatchKind = int;
14438 enum : FcMatchKind {
14439 FcMatchPattern,
14440 FcMatchFont,
14441 FcMatchScan
14443 alias FcResult = int;
14444 enum : FcResult {
14445 FcResultMatch,
14446 FcResultNoMatch,
14447 FcResultTypeMismatch,
14448 FcResultNoId,
14449 FcResultOutOfMemory
14451 FcBool FcInit ();
14452 FcBool FcConfigSubstituteWithPat (FcConfig* config, FcPattern* p, FcPattern* p_pat, FcMatchKind kind);
14453 void FcDefaultSubstitute (FcPattern* pattern);
14454 FcBool FcConfigSubstitute (FcConfig* config, FcPattern* p, FcMatchKind kind);
14455 FcPattern* FcFontMatch (FcConfig* config, FcPattern* p, FcResult* result);
14456 FcPattern* FcNameParse (const(FcChar8)* name);
14457 void FcPatternDestroy (FcPattern* p);
14458 FcResult FcPatternGetString (const(FcPattern)* p, const(char)* object, int n, FcChar8** s);
14462 __gshared bool fontconfigAvailable = false;
14463 // initialize fontconfig
14464 shared static this () {
14465 if (FcInit()) {
14466 fontconfigAvailable = true;
14467 } else {
14468 import core.stdc.stdio : stderr, fprintf;
14469 stderr.fprintf("***NanoVega WARNING: cannot init fontconfig!\n");
14475 // ////////////////////////////////////////////////////////////////////////// //
14476 public enum BaphometDims = 512.0f; // baphomet icon is 512x512 ([0..511])
14478 private static immutable ubyte[7641] baphometPath = [
14479 0x01,0x04,0x06,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x08,0xa0,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,
14480 0x00,0x80,0xff,0x43,0xa2,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x08,0x00,0x80,0xff,
14481 0x43,0x7a,0x89,0xe5,0x42,0xa0,0x1d,0xc6,0x43,0x00,0x00,0x00,0x00,0x30,0x89,0x7f,0x43,0x00,0x00,0x00,
14482 0x00,0x08,0x7a,0x89,0xe5,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7a,0x89,0xe5,0x42,0x00,0x00,
14483 0x00,0x00,0x30,0x89,0x7f,0x43,0x08,0x00,0x00,0x00,0x00,0xa2,0x1d,0xc6,0x43,0x7a,0x89,0xe5,0x42,0x00,
14484 0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x09,0x06,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd,
14485 0x43,0x08,0x16,0x68,0xb3,0x43,0x72,0x87,0xdd,0x43,0x71,0x87,0xdd,0x43,0x17,0x68,0xb3,0x43,0x71,0x87,
14486 0xdd,0x43,0x30,0x89,0x7f,0x43,0x08,0x71,0x87,0xdd,0x43,0xd2,0x2f,0x18,0x43,0x16,0x68,0xb3,0x43,0x35,
14487 0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x35,0xe2,0x87,0x42,0x08,0xd1,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,
14488 0x35,0xe2,0x87,0x42,0xd2,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x08,0x35,0xe2,0x87,
14489 0x42,0x17,0x68,0xb3,0x43,0xd1,0x2f,0x18,0x43,0x72,0x87,0xdd,0x43,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd,
14490 0x43,0x09,0x06,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x07,0xa4,0x3f,0x7f,0x43,0x0b,0x86,0xdc,0x43,
14491 0x07,0x6c,0xb9,0xb2,0x43,0xe8,0xd1,0xca,0x42,0x07,0x6e,0x4d,0xa0,0x42,0xa9,0x10,0x9c,0x43,0x07,0xb7,
14492 0x40,0xd7,0x43,0xa9,0x10,0x9c,0x43,0x07,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x09,0x06,0x98,0x42,
14493 0x74,0x43,0xb1,0x8d,0x68,0x43,0x08,0xd7,0x24,0x79,0x43,0xba,0x83,0x6e,0x43,0xa9,0x16,0x7c,0x43,0x56,
14494 0xa1,0x76,0x43,0x74,0x2a,0x7d,0x43,0x44,0x73,0x80,0x43,0x08,0x55,0xd1,0x7e,0x43,0xe3,0xea,0x76,0x43,
14495 0xbc,0x18,0x81,0x43,0x7f,0xa8,0x6e,0x43,0x8f,0x0a,0x84,0x43,0x02,0xfc,0x68,0x43,0x09,0x06,0x92,0x29,
14496 0x8d,0x43,0x73,0xc3,0x67,0x43,0x08,0xa4,0xd9,0x8e,0x43,0xf2,0xa6,0x7a,0x43,0x8f,0x22,0x88,0x43,0x75,
14497 0x2a,0x7d,0x43,0x42,0x7f,0x82,0x43,0x08,0xc8,0x88,0x43,0x09,0x06,0xc1,0x79,0x74,0x43,0x50,0x64,0x89,
14498 0x43,0x08,0x68,0x2d,0x72,0x43,0xee,0x21,0x81,0x43,0xcd,0x97,0x55,0x43,0xe6,0xf1,0x7b,0x43,0x91,0xec,
14499 0x5d,0x43,0xa8,0xc7,0x6a,0x43,0x09,0x06,0xfa,0xa5,0x52,0x43,0x60,0x97,0x7c,0x43,0x08,0x19,0xff,0x50,
14500 0x43,0xe9,0x6e,0x8a,0x43,0xb0,0xbd,0x70,0x43,0x4c,0x51,0x82,0x43,0x04,0xeb,0x69,0x43,0x66,0x0f,0x8e,
14501 0x43,0x09,0x06,0x17,0xbf,0x71,0x43,0x2c,0x58,0x94,0x43,0x08,0x1c,0x96,0x6e,0x43,0x61,0x68,0x99,0x43,
14502 0x2d,0x3a,0x6e,0x43,0xc8,0x81,0x9e,0x43,0xb7,0x9b,0x72,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0x30,0xdb,
14503 0x82,0x43,0xdb,0xe9,0x93,0x43,0x08,0x11,0x82,0x84,0x43,0x61,0x68,0x99,0x43,0xe8,0x4a,0x84,0x43,0x8e,
14504 0xa6,0x9e,0x43,0x42,0x7f,0x82,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0xc4,0x02,0x85,0x43,0xd1,0x0b,0x92,
14505 0x43,0x08,0xd6,0xb2,0x86,0x43,0x34,0x1e,0x92,0x43,0x4f,0x58,0x87,0x43,0xa4,0xf1,0x92,0x43,0x03,0xd9,
14506 0x87,0x43,0x7b,0xc6,0x94,0x43,0x09,0x06,0x87,0x3e,0x64,0x43,0x31,0x3b,0x93,0x43,0x08,0x3b,0xbf,0x64,
14507 0x43,0x6f,0xf9,0x91,0x43,0x96,0x0b,0x67,0x43,0xc5,0x4a,0x91,0x43,0xcf,0xfe,0x6a,0x43,0x31,0x2f,0x91,
14508 0x43,0x09,0x06,0x16,0x74,0xb5,0x43,0x08,0xec,0x8e,0x43,0x08,0x1b,0x4b,0xb2,0x43,0xee,0x5d,0x8b,0x43,
14509 0x48,0x4d,0xad,0x43,0x12,0xa6,0x8a,0x43,0xf3,0xd7,0xa7,0x43,0x74,0xb8,0x8a,0x43,0x08,0x8c,0xb2,0xa0,
14510 0x43,0xcd,0xf8,0x8a,0x43,0x68,0x46,0x9b,0x43,0x79,0x8f,0x87,0x43,0x49,0xc9,0x96,0x43,0xe9,0x3e,0x82,
14511 0x43,0x08,0x60,0x5c,0x97,0x43,0xa1,0xde,0x8b,0x43,0x4e,0xa0,0x93,0x43,0x31,0x3b,0x93,0x43,0x9f,0xea,
14512 0x8d,0x43,0x27,0x8d,0x99,0x43,0x08,0x07,0xe0,0x8c,0x43,0x06,0x34,0x9b,0x43,0x38,0xe9,0x8c,0x43,0x46,
14513 0x0a,0x9e,0x43,0x3d,0xcc,0x8b,0x43,0xb2,0x06,0xa2,0x43,0x08,0xf1,0x40,0x8a,0x43,0xb0,0x12,0xa4,0x43,
14514 0x39,0xd1,0x88,0x43,0x76,0x43,0xa6,0x43,0xfa,0x06,0x88,0x43,0xa4,0x75,0xa9,0x43,0x08,0x19,0x6c,0x88,
14515 0x43,0x9f,0x9e,0xac,0x43,0x66,0xeb,0x87,0x43,0x44,0x76,0xb0,0x43,0x6b,0xce,0x86,0x43,0x3b,0xbc,0xb4,
14516 0x43,0x08,0xa9,0x8c,0x85,0x43,0x06,0xd0,0xb5,0x43,0xfa,0xee,0x83,0x43,0x74,0xa3,0xb6,0x43,0x3d,0x90,
14517 0x81,0x43,0x31,0xf6,0xb6,0x43,0x08,0x9d,0x61,0x7d,0x43,0xee,0x48,0xb7,0x43,0x3b,0x1f,0x75,0x43,0xcf,
14518 0xe3,0xb6,0x43,0xee,0x6f,0x6d,0x43,0x68,0xe2,0xb5,0x43,0x08,0xd4,0xed,0x6b,0x43,0x87,0x2f,0xb2,0x43,
14519 0x0e,0xc9,0x6b,0x43,0xa7,0x7c,0xae,0x43,0x98,0xfa,0x67,0x43,0xab,0x53,0xab,0x43,0x08,0x25,0x2c,0x64,
14520 0x43,0x33,0xa2,0xa8,0x43,0x40,0x96,0x61,0x43,0xc3,0xc2,0xa5,0x43,0x64,0xde,0x60,0x43,0xfa,0xa2,0xa2,
14521 0x43,0x08,0xb0,0x5d,0x60,0x43,0x06,0x4c,0x9f,0x43,0x9a,0xca,0x5f,0x43,0x38,0x3d,0x9b,0x43,0x3b,0x8f,
14522 0x5c,0x43,0x85,0xb0,0x98,0x43,0x08,0x42,0x36,0x51,0x43,0x3d,0xf0,0x91,0x43,0xcd,0x4f,0x49,0x43,0xdb,
14523 0xb9,0x8b,0x43,0xe0,0xdb,0x44,0x43,0x42,0x8b,0x84,0x43,0x08,0x7e,0xc9,0x44,0x43,0x8a,0x57,0x8d,0x43,
14524 0xbc,0x6c,0x0f,0x43,0x23,0x62,0x8e,0x43,0xf5,0x17,0x07,0x43,0xc5,0x3e,0x8f,0x43,0x09,0x06,0xe0,0xea,
14525 0x76,0x43,0xab,0xef,0xc5,0x43,0x08,0x12,0x00,0x79,0x43,0xab,0xcb,0xbf,0x43,0x79,0xb9,0x6d,0x43,0x7e,
14526 0x8d,0xba,0x43,0xee,0x6f,0x6d,0x43,0x98,0xeb,0xb5,0x43,0x08,0xe0,0x02,0x7b,0x43,0x5f,0x1c,0xb8,0x43,
14527 0x85,0x2c,0x82,0x43,0xe9,0x65,0xb8,0x43,0xd6,0xb2,0x86,0x43,0xc6,0x05,0xb5,0x43,0x08,0x03,0xcd,0x85,
14528 0x43,0x5a,0x39,0xb9,0x43,0xe4,0x4f,0x81,0x43,0xdb,0xd4,0xbf,0x43,0xdf,0x6c,0x82,0x43,0xbc,0x93,0xc5,
14529 0x43,0x09,0x06,0xf0,0xd0,0x22,0x43,0x5d,0x19,0x08,0x43,0x08,0xbc,0xab,0x49,0x43,0x4a,0x35,0x29,0x43,
14530 0xcb,0xf7,0x65,0x43,0xce,0x37,0x45,0x43,0x0e,0x99,0x63,0x43,0x67,0xc6,0x5c,0x43,0x09,0x06,0x05,0x94,
14531 0xab,0x43,0xc2,0x13,0x04,0x43,0x08,0x9f,0x26,0x98,0x43,0x11,0x42,0x25,0x43,0x97,0x00,0x8a,0x43,0x32,
14532 0x32,0x41,0x43,0xf5,0x2f,0x8b,0x43,0xc7,0xc0,0x58,0x43,0x09,0x06,0x8f,0x85,0x48,0x43,0xe0,0xa8,0x8c,
14533 0x43,0x08,0x55,0xaa,0x48,0x43,0xe0,0xa8,0x8c,0x43,0x6b,0x3d,0x49,0x43,0xc1,0x43,0x8c,0x43,0x31,0x62,
14534 0x49,0x43,0xc1,0x43,0x8c,0x43,0x08,0x2f,0xe3,0x2f,0x43,0xad,0xe7,0x98,0x43,0xff,0x0d,0x0d,0x43,0xad,
14535 0xf3,0x9a,0x43,0xf0,0xaf,0xcc,0x42,0x74,0x00,0x97,0x43,0x08,0xbb,0xa2,0xf7,0x42,0x93,0x4d,0x93,0x43,
14536 0x5e,0x19,0x08,0x43,0x5a,0x2a,0x87,0x43,0x23,0x6e,0x10,0x43,0x42,0x97,0x86,0x43,0x08,0xca,0xe8,0x33,
14537 0x43,0x1b,0x3c,0x80,0x43,0x80,0xe8,0x4d,0x43,0xda,0xf4,0x70,0x43,0xae,0x0e,0x4f,0x43,0x2b,0x1b,0x65,
14538 0x43,0x08,0x66,0x96,0x54,0x43,0xa3,0xe1,0x3b,0x43,0x4e,0xc4,0x19,0x43,0xa0,0x1a,0x16,0x43,0x10,0xe2,
14539 0x14,0x43,0x26,0x14,0xe0,0x42,0x08,0x5c,0x91,0x1c,0x43,0xcb,0x27,0xee,0x42,0xa9,0x40,0x24,0x43,0x71,
14540 0x3b,0xfc,0x42,0xf3,0xef,0x2b,0x43,0x8b,0x27,0x05,0x43,0x08,0xe2,0x4b,0x2c,0x43,0x48,0x86,0x07,0x43,
14541 0x79,0x62,0x2f,0x43,0x05,0xe5,0x09,0x43,0x55,0x32,0x34,0x43,0xa0,0xd2,0x09,0x43,0x08,0x74,0xa3,0x36,
14542 0x43,0x3a,0xd1,0x08,0x43,0x7e,0x81,0x38,0x43,0x09,0xd4,0x0a,0x43,0x0d,0xba,0x39,0x43,0xa0,0xea,0x0d,
14543 0x43,0x08,0x6f,0xe4,0x3d,0x43,0x43,0xc7,0x0e,0x43,0xd6,0xe5,0x3e,0x43,0xc4,0x4a,0x11,0x43,0x55,0x7a,
14544 0x40,0x43,0x59,0x72,0x13,0x43,0x08,0x55,0x92,0x44,0x43,0xbf,0x73,0x14,0x43,0x23,0x95,0x46,0x43,0xa5,
14545 0x09,0x17,0x43,0xe0,0xf3,0x48,0x43,0xfe,0x55,0x19,0x43,0x08,0xcd,0x4f,0x49,0x43,0xaa,0x10,0x1c,0x43,
14546 0x61,0x77,0x4b,0x43,0xfe,0x6d,0x1d,0x43,0x80,0xe8,0x4d,0x43,0x2b,0x94,0x1e,0x43,0x08,0x58,0xc9,0x51,
14547 0x43,0x41,0x27,0x1f,0x43,0x9b,0x82,0x53,0x43,0x35,0x72,0x20,0x43,0x53,0xf2,0x54,0x43,0x88,0xcf,0x21,
14548 0x43,0x08,0x7b,0x29,0x55,0x43,0xe8,0x0a,0x25,0x43,0xb2,0x2d,0x58,0x43,0xef,0xe8,0x26,0x43,0x9b,0xb2,
14549 0x5b,0x43,0xd0,0x8f,0x28,0x43,0x08,0x5f,0xef,0x5f,0x43,0xeb,0x11,0x2a,0x43,0xfd,0xdc,0x5f,0x43,0x6e,
14550 0x95,0x2c,0x43,0x3b,0xa7,0x60,0x43,0x2b,0xf4,0x2e,0x43,0x08,0x06,0xbb,0x61,0x43,0xfd,0xe5,0x31,0x43,
14551 0xe7,0x61,0x63,0x43,0xef,0x30,0x33,0x43,0x53,0x52,0x65,0x43,0xa3,0xb1,0x33,0x43,0x08,0x12,0xa0,0x68,
14552 0x43,0x7f,0x69,0x34,0x43,0x40,0xc6,0x69,0x43,0x64,0xff,0x36,0x43,0x7e,0x90,0x6a,0x43,0x71,0xcc,0x39,
14553 0x43,0x08,0xbc,0x5a,0x6b,0x43,0x51,0x73,0x3b,0x43,0xc1,0x49,0x6c,0x43,0xa5,0xd0,0x3c,0x43,0xe0,0xba,
14554 0x6e,0x43,0xb8,0x74,0x3c,0x43,0x08,0x6b,0x1c,0x73,0x43,0x13,0xc1,0x3e,0x43,0x40,0xf6,0x71,0x43,0xce,
14555 0x1f,0x41,0x43,0x55,0x89,0x72,0x43,0x8d,0x7e,0x43,0x43,0x08,0x68,0x2d,0x72,0x43,0x89,0xae,0x4b,0x43,
14556 0xc1,0x79,0x74,0x43,0xcb,0x78,0x4c,0x43,0x55,0xa1,0x76,0x43,0x5b,0xb1,0x4d,0x43,0x08,0xa2,0x38,0x7a,
14557 0x43,0xd1,0x56,0x4e,0x43,0x85,0xb6,0x78,0x43,0xb1,0x15,0x54,0x43,0x83,0xc7,0x77,0x43,0x89,0x0e,0x5c,
14558 0x43,0x08,0xcf,0x46,0x77,0x43,0x0f,0x81,0x5f,0x43,0x1a,0xde,0x7a,0x43,0xce,0xc7,0x5d,0x43,0x42,0x73,
14559 0x80,0x43,0x99,0xc3,0x5a,0x43,0x08,0x85,0x2c,0x82,0x43,0xf6,0xe6,0x59,0x43,0x81,0x3d,0x81,0x43,0x16,
14560 0x10,0x50,0x43,0xd6,0x8e,0x80,0x43,0x5b,0x99,0x49,0x43,0x08,0xc4,0xea,0x80,0x43,0x22,0x95,0x46,0x43,
14561 0xfa,0xe2,0x81,0x43,0xda,0xec,0x43,0x43,0x78,0x77,0x83,0x43,0xe4,0xb2,0x41,0x43,0x08,0x8a,0x27,0x85,
14562 0x43,0x86,0x77,0x3e,0x43,0x0c,0x9f,0x85,0x43,0x07,0xf4,0x3b,0x43,0x8f,0x16,0x86,0x43,0xe6,0x82,0x39,
14563 0x43,0x08,0x85,0x44,0x86,0x43,0x37,0xd9,0x35,0x43,0x1e,0x4f,0x87,0x43,0xe1,0x7b,0x34,0x43,0xdf,0x90,
14564 0x88,0x43,0xb6,0x55,0x33,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0xe5,0x31,0x43,0xfa,0x12,0x8a,0x43,0xbf,
14565 0x03,0x2d,0x43,0x19,0x78,0x8a,0x43,0x45,0x5e,0x2c,0x43,0x08,0x03,0xf1,0x8b,0x43,0xac,0x47,0x29,0x43,
14566 0x2f,0x17,0x8d,0x43,0x45,0x46,0x28,0x43,0xc8,0x21,0x8e,0x43,0x30,0xb3,0x27,0x43,0x08,0xa9,0xc8,0x8f,
14567 0x43,0xef,0xe8,0x26,0x43,0xbf,0x5b,0x90,0x43,0x5b,0xc1,0x24,0x43,0x10,0xca,0x90,0x43,0xa0,0x62,0x22,
14568 0x43,0x08,0x26,0x5d,0x91,0x43,0xbb,0xcc,0x1f,0x43,0xf0,0x70,0x92,0x43,0x78,0x13,0x1e,0x43,0x77,0xd7,
14569 0x93,0x43,0x73,0x24,0x1d,0x43,0x08,0x65,0x3f,0x96,0x43,0xce,0x58,0x1b,0x43,0xbe,0x7f,0x96,0x43,0xbf,
14570 0x8b,0x18,0x43,0x60,0x5c,0x97,0x43,0xb6,0xad,0x16,0x43,0x08,0xba,0xa8,0x99,0x43,0x78,0xcb,0x11,0x43,
14571 0x49,0xe1,0x9a,0x43,0x78,0xcb,0x11,0x43,0x01,0x51,0x9c,0x43,0x73,0xdc,0x10,0x43,0x08,0x72,0x24,0x9d,
14572 0x43,0xd2,0xff,0x0f,0x43,0x1c,0xd3,0x9d,0x43,0x07,0xec,0x0e,0x43,0xeb,0xc9,0x9d,0x43,0xe8,0x7a,0x0c,
14573 0x43,0x08,0x60,0x80,0x9d,0x43,0xd7,0xbe,0x08,0x43,0x4d,0xe8,0x9f,0x43,0x86,0x50,0x08,0x43,0x25,0xbd,
14574 0xa1,0x43,0x5b,0x2a,0x07,0x43,0x08,0x99,0x7f,0xa3,0x43,0xc9,0xf1,0x05,0x43,0x48,0x1d,0xa5,0x43,0x86,
14575 0x38,0x04,0x43,0x6c,0x71,0xa6,0x43,0x18,0x59,0x01,0x43,0x08,0x32,0x96,0xa6,0x43,0x6e,0x64,0xff,0x42,
14576 0x48,0x29,0xa7,0x43,0xed,0xcf,0xfd,0x42,0x5f,0xbc,0xa7,0x43,0x71,0x3b,0xfc,0x42,0x08,0xf3,0xe3,0xa9,
14577 0x43,0xf7,0x7d,0xf7,0x42,0xd8,0x6d,0xaa,0x43,0x45,0xe5,0xf2,0x42,0x48,0x41,0xab,0x43,0xcb,0x27,0xee,
14578 0x42,0x08,0x24,0xf9,0xab,0x43,0x52,0x6a,0xe9,0x42,0xee,0x0c,0xad,0x43,0x4c,0x8c,0xe7,0x42,0x1b,0x33,
14579 0xae,0x43,0xcc,0xf7,0xe5,0x42,0x08,0xaa,0x6b,0xaf,0x43,0xe8,0x61,0xe3,0x42,0x90,0xf5,0xaf,0x43,0xc9,
14580 0xf0,0xe0,0x42,0xe0,0x63,0xb0,0x43,0xe5,0x5a,0xde,0x42,0x08,0xaa,0x83,0xb3,0x43,0x29,0x2d,0x09,0x43,
14581 0x6a,0xfe,0x8e,0x43,0xb8,0x74,0x3c,0x43,0xd5,0x06,0x95,0x43,0xe6,0x79,0x67,0x43,0x08,0x2f,0x53,0x97,
14582 0x43,0xe9,0xb0,0x74,0x43,0xa8,0x28,0xa0,0x43,0x43,0xfd,0x76,0x43,0x83,0x28,0xad,0x43,0x17,0x59,0x81,
14583 0x43,0x08,0x3d,0xe7,0xbf,0x43,0x4b,0x8d,0x8c,0x43,0xae,0x96,0xba,0x43,0x66,0x27,0x92,0x43,0x15,0xe0,
14584 0xc7,0x43,0x6f,0x11,0x96,0x43,0x08,0x7e,0x5d,0xb2,0x43,0xdb,0x01,0x98,0x43,0x9e,0x56,0xa0,0x43,0x80,
14585 0xc1,0x97,0x43,0x69,0x2e,0x97,0x43,0x31,0x17,0x8d,0x43,0x09,0x06,0xab,0xa7,0x39,0x43,0x67,0x0f,0x0e,
14586 0x43,0x08,0xdb,0xbc,0x3b,0x43,0xe8,0x92,0x10,0x43,0xb5,0x85,0x3b,0x43,0x97,0x3c,0x14,0x43,0xab,0xa7,
14587 0x39,0x43,0x0c,0x0b,0x18,0x43,0x09,0x06,0xca,0x30,0x40,0x43,0x30,0x3b,0x13,0x43,0x08,0x17,0xc8,0x43,
14588 0x43,0xa5,0x09,0x17,0x43,0x7e,0xc9,0x44,0x43,0x1a,0xd8,0x1a,0x43,0x9d,0x22,0x43,0x43,0x8d,0xa6,0x1e,
14589 0x43,0x09,0x06,0xc8,0x78,0x4c,0x43,0xed,0xc9,0x1d,0x43,0x08,0x0b,0x32,0x4e,0x43,0x22,0xce,0x20,0x43,
14590 0x23,0xc5,0x4e,0x43,0x58,0xd2,0x23,0x43,0x0b,0x32,0x4e,0x43,0x2b,0xc4,0x26,0x43,0x09,0x06,0xec,0x08,
14591 0x58,0x43,0xc7,0xb1,0x26,0x43,0x08,0x02,0x9c,0x58,0x43,0xef,0x00,0x2b,0x43,0xd9,0x64,0x58,0x43,0x02,
14592 0xbd,0x2e,0x43,0x10,0x51,0x57,0x43,0x37,0xc1,0x31,0x43,0x09,0x06,0xcb,0xdf,0x61,0x43,0x4a,0x65,0x31,
14593 0x43,0x08,0xbe,0x2a,0x63,0x43,0xbd,0x33,0x35,0x43,0x32,0xe1,0x62,0x43,0x56,0x4a,0x38,0x43,0xde,0x83,
14594 0x61,0x43,0x3c,0xe0,0x3a,0x43,0x09,0x06,0x1c,0x7e,0x6a,0x43,0x5b,0x39,0x39,0x43,0x08,0x31,0x11,0x6b,
14595 0x43,0x0c,0xd2,0x3d,0x43,0x1c,0x7e,0x6a,0x43,0x13,0xd9,0x42,0x43,0xd9,0xc4,0x68,0x43,0xcb,0x60,0x48,
14596 0x43,0x09,0x06,0xe5,0xc1,0x73,0x43,0x16,0xf8,0x4b,0x43,0x08,0xa6,0xf7,0x72,0x43,0xb1,0xfd,0x4f,0x43,
14597 0x3b,0x07,0x71,0x43,0x4a,0x14,0x53,0x43,0xa2,0xf0,0x6d,0x43,0x7c,0x29,0x55,0x43,0x09,0x06,0x00,0x8d,
14598 0xa6,0x43,0xef,0x21,0x01,0x43,0x08,0x52,0xfb,0xa6,0x43,0xce,0xc8,0x02,0x43,0xe6,0x16,0xa7,0x43,0x51,
14599 0x4c,0x05,0x43,0x3b,0x68,0xa6,0x43,0x4c,0x75,0x08,0x43,0x09,0x06,0xde,0x20,0xa1,0x43,0x86,0x50,0x08,
14600 0x43,0x08,0xd4,0x4e,0xa1,0x43,0xd3,0xe7,0x0b,0x43,0xb5,0xe9,0xa0,0x43,0x59,0x5a,0x0f,0x43,0xba,0xcc,
14601 0x9f,0x43,0x54,0x83,0x12,0x43,0x09,0x06,0x77,0xfb,0x99,0x43,0x6c,0x16,0x13,0x43,0x08,0xde,0xfc,0x9a,
14602 0x43,0x4a,0xbd,0x14,0x43,0x06,0x34,0x9b,0x43,0xfe,0x55,0x19,0x43,0x13,0xe9,0x99,0x43,0x41,0x27,0x1f,
14603 0x43,0x09,0x06,0x46,0xce,0x93,0x43,0x26,0xa5,0x1d,0x43,0x08,0xe7,0xaa,0x94,0x43,0xbb,0xcc,0x1f,0x43,
14604 0x18,0xb4,0x94,0x43,0xa8,0x40,0x24,0x43,0xe2,0xbb,0x93,0x43,0x21,0xfe,0x28,0x43,0x09,0x06,0xb1,0x8e,
14605 0x8d,0x43,0xa8,0x58,0x28,0x43,0x08,0x19,0x90,0x8e,0x43,0x54,0x13,0x2b,0x43,0xa4,0xd9,0x8e,0x43,0x84,
14606 0x40,0x31,0x43,0x46,0xaa,0x8d,0x43,0x29,0x24,0x37,0x43,0x09,0x06,0xd6,0xbe,0x88,0x43,0xef,0x30,0x33,
14607 0x43,0x08,0x0c,0xb7,0x89,0x43,0x0e,0xa2,0x35,0x43,0xc0,0x37,0x8a,0x43,0x7a,0xaa,0x3b,0x43,0xbb,0x48,
14608 0x89,0x43,0xbb,0x7b,0x41,0x43,0x09,0x06,0x3a,0xad,0x82,0x43,0xc4,0x59,0x43,0x43,0x08,0xd2,0xb7,0x83,
14609 0x43,0x2b,0x5b,0x44,0x43,0x35,0xd6,0x85,0x43,0x48,0xf5,0x49,0x43,0x42,0x97,0x86,0x43,0xc4,0xa1,0x4f,
14610 0x43,0x09,0x06,0x9c,0xb3,0x80,0x43,0x48,0x55,0x5a,0x43,0x08,0xff,0xc5,0x80,0x43,0x09,0x73,0x55,0x43,
14611 0x93,0xe1,0x80,0x43,0x0f,0x39,0x53,0x43,0xf1,0xbe,0x7e,0x43,0x18,0xe7,0x4c,0x43,0x09,0x06,0xe0,0x02,
14612 0x7b,0x43,0x92,0xec,0x5d,0x43,0x08,0x09,0x3a,0x7b,0x43,0xf0,0xf7,0x58,0x43,0x09,0x3a,0x7b,0x43,0xe6,
14613 0x31,0x5b,0x43,0xe0,0x02,0x7b,0x43,0xa8,0x4f,0x56,0x43,0x09,0x06,0x39,0x4f,0x7d,0x43,0x3e,0x8f,0x5c,
14614 0x43,0x08,0xe9,0xe0,0x7c,0x43,0x03,0x9c,0x58,0x43,0x1e,0x2b,0x81,0x43,0x7f,0x30,0x5a,0x43,0xff,0x73,
14615 0x7d,0x43,0xf6,0xb6,0x51,0x43,0x09,0x06,0x5c,0xb8,0x52,0x43,0x28,0x21,0x87,0x43,0x08,0xae,0x3e,0x57,
14616 0x43,0x12,0x9a,0x88,0x43,0x23,0xf5,0x56,0x43,0x04,0xf1,0x8b,0x43,0x25,0xfc,0x5b,0x43,0x85,0x74,0x8e,
14617 0x43,0x08,0x2f,0xf2,0x61,0x43,0x8e,0x52,0x90,0x43,0xd9,0xdc,0x6c,0x43,0x85,0x74,0x8e,0x43,0xc6,0x20,
14618 0x69,0x43,0x3d,0xd8,0x8d,0x43,0x08,0x6d,0x8c,0x5a,0x43,0xf5,0x3b,0x8d,0x43,0x3d,0x77,0x58,0x43,0xa1,
14619 0xc6,0x87,0x43,0xf8,0xed,0x5e,0x43,0x5e,0x0d,0x86,0x43,0x09,0x06,0xde,0xcc,0x92,0x43,0xf7,0x17,0x87,
14620 0x43,0x08,0xb6,0x89,0x90,0x43,0xae,0x87,0x88,0x43,0x4a,0xa5,0x90,0x43,0xa1,0xde,0x8b,0x43,0xf9,0x2a,
14621 0x8e,0x43,0x23,0x62,0x8e,0x43,0x08,0xf5,0x2f,0x8b,0x43,0x5c,0x49,0x90,0x43,0x35,0xd6,0x85,0x43,0x8e,
14622 0x46,0x8e,0x43,0x3d,0xb4,0x87,0x43,0x47,0xaa,0x8d,0x43,0x08,0x6a,0xfe,0x8e,0x43,0xff,0x0d,0x8d,0x43,
14623 0xbb,0x6c,0x8f,0x43,0xf7,0x17,0x87,0x43,0x5c,0x31,0x8c,0x43,0xb2,0x5e,0x85,0x43,0x09,0x06,0x60,0x38,
14624 0x91,0x43,0x69,0x5d,0x7a,0x43,0x08,0x34,0x1e,0x92,0x43,0x1e,0x5b,0x89,0x43,0x04,0x63,0x7e,0x43,0x5e,
14625 0x01,0x84,0x43,0x59,0x2a,0x87,0x43,0x0d,0xcf,0x8d,0x43,0x09,0x03,0x04,0x06,0x5a,0x18,0x63,0x43,0x82,
14626 0x79,0x8b,0x43,0x08,0x25,0x2c,0x64,0x43,0x82,0x79,0x8b,0x43,0x2a,0x1b,0x65,0x43,0x9d,0xef,0x8a,0x43,
14627 0x2a,0x1b,0x65,0x43,0xc1,0x37,0x8a,0x43,0x08,0x2a,0x1b,0x65,0x43,0x17,0x89,0x89,0x43,0x25,0x2c,0x64,
14628 0x43,0x31,0xff,0x88,0x43,0x5a,0x18,0x63,0x43,0x31,0xff,0x88,0x43,0x08,0xf3,0x16,0x62,0x43,0x31,0xff,
14629 0x88,0x43,0xee,0x27,0x61,0x43,0x17,0x89,0x89,0x43,0xee,0x27,0x61,0x43,0xc1,0x37,0x8a,0x43,0x08,0xee,
14630 0x27,0x61,0x43,0x9d,0xef,0x8a,0x43,0xf3,0x16,0x62,0x43,0x82,0x79,0x8b,0x43,0x5a,0x18,0x63,0x43,0x82,
14631 0x79,0x8b,0x43,0x09,0x06,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x08,0x34,0xee,0x89,0x43,0x82,0x79,
14632 0x8b,0x43,0x85,0x5c,0x8a,0x43,0x9d,0xef,0x8a,0x43,0x85,0x5c,0x8a,0x43,0xc1,0x37,0x8a,0x43,0x08,0x85,
14633 0x5c,0x8a,0x43,0x17,0x89,0x89,0x43,0x34,0xee,0x89,0x43,0x31,0xff,0x88,0x43,0x4f,0x64,0x89,0x43,0x31,
14634 0xff,0x88,0x43,0x08,0x9c,0xe3,0x88,0x43,0x31,0xff,0x88,0x43,0x19,0x6c,0x88,0x43,0x17,0x89,0x89,0x43,
14635 0x19,0x6c,0x88,0x43,0xc1,0x37,0x8a,0x43,0x08,0x19,0x6c,0x88,0x43,0x9d,0xef,0x8a,0x43,0x9c,0xe3,0x88,
14636 0x43,0x82,0x79,0x8b,0x43,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x09,0x02,0x04,0x06,0x19,0x60,0x86,
14637 0x43,0xec,0xed,0xa3,0x43,0x08,0x35,0xd6,0x85,0x43,0x76,0x43,0xa6,0x43,0x93,0xe1,0x80,0x43,0x57,0x02,
14638 0xac,0x43,0x61,0xd8,0x80,0x43,0x87,0x17,0xae,0x43,0x08,0xa5,0x85,0x80,0x43,0xc3,0xfe,0xaf,0x43,0xce,
14639 0xbc,0x80,0x43,0x83,0x40,0xb1,0x43,0xa5,0x91,0x82,0x43,0x79,0x6e,0xb1,0x43,0x08,0x23,0x26,0x84,0x43,
14640 0x40,0x93,0xb1,0x43,0x30,0xe7,0x84,0x43,0xbe,0x1b,0xb1,0x43,0x11,0x82,0x84,0x43,0xab,0x6b,0xaf,0x43,
14641 0x08,0xb7,0x41,0x84,0x43,0x3b,0x98,0xae,0x43,0xb7,0x41,0x84,0x43,0xc3,0xf2,0xad,0x43,0xa1,0xae,0x83,
14642 0x43,0x83,0x28,0xad,0x43,0x08,0xb2,0x52,0x83,0x43,0x80,0x39,0xac,0x43,0x81,0x49,0x83,0x43,0xf0,0x00,
14643 0xab,0x43,0xe4,0x67,0x85,0x43,0x76,0x4f,0xa8,0x43,0x08,0x9c,0xd7,0x86,0x43,0xd1,0x83,0xa6,0x43,0xec,
14644 0x45,0x87,0x43,0x01,0x75,0xa2,0x43,0x19,0x60,0x86,0x43,0xec,0xed,0xa3,0x43,0x09,0x06,0xd9,0xdc,0x6c,
14645 0x43,0x14,0x25,0xa4,0x43,0x08,0xa2,0xf0,0x6d,0x43,0x9f,0x7a,0xa6,0x43,0x47,0xec,0x77,0x43,0x80,0x39,
14646 0xac,0x43,0xa9,0xfe,0x77,0x43,0xb0,0x4e,0xae,0x43,0x08,0x23,0xa4,0x78,0x43,0xea,0x35,0xb0,0x43,0xd2,
14647 0x35,0x78,0x43,0xab,0x77,0xb1,0x43,0xc1,0x79,0x74,0x43,0xa2,0xa5,0xb1,0x43,0x08,0xc6,0x50,0x71,0x43,
14648 0x68,0xca,0xb1,0x43,0xab,0xce,0x6f,0x43,0xe7,0x52,0xb1,0x43,0xea,0x98,0x70,0x43,0xd4,0xa2,0xaf,0x43,
14649 0x08,0x9d,0x19,0x71,0x43,0x96,0xd8,0xae,0x43,0x9d,0x19,0x71,0x43,0xec,0x29,0xae,0x43,0xca,0x3f,0x72,
14650 0x43,0xab,0x5f,0xad,0x43,0x08,0xa6,0xf7,0x72,0x43,0xa7,0x70,0xac,0x43,0x09,0x0a,0x73,0x43,0x17,0x38,
14651 0xab,0x43,0x44,0xcd,0x6e,0x43,0x9f,0x86,0xa8,0x43,0x08,0xd4,0xed,0x6b,0x43,0xf8,0xba,0xa6,0x43,0x31,
14652 0x11,0x6b,0x43,0x2a,0xac,0xa2,0x43,0xd9,0xdc,0x6c,0x43,0x14,0x25,0xa4,0x43,0x09,0x01,0x05,0x06,0x66,
14653 0x5d,0x7a,0x43,0x74,0xeb,0xc2,0x43,0x08,0x09,0x22,0x77,0x43,0x50,0xbb,0xc7,0x43,0xe9,0xe0,0x7c,0x43,
14654 0xf5,0x86,0xc9,0x43,0x8f,0x94,0x7a,0x43,0xc5,0x95,0xcd,0x43,0x09,0x06,0x08,0x98,0x80,0x43,0x6b,0x19,
14655 0xc3,0x43,0x08,0xb7,0x35,0x82,0x43,0x79,0xf2,0xc7,0x43,0xf1,0xbe,0x7e,0x43,0x1e,0xbe,0xc9,0x43,0x73,
14656 0x7c,0x80,0x43,0xec,0xcc,0xcd,0x43,0x09,0x06,0x28,0xab,0x7d,0x43,0xae,0xde,0xc6,0x43,0x08,0x1e,0xcd,
14657 0x7b,0x43,0x8a,0xa2,0xc9,0x43,0x30,0x89,0x7f,0x43,0x5c,0x94,0xcc,0x43,0x28,0xab,0x7d,0x43,0x42,0x2a,
14658 0xcf,0x43,0x09,0x01,0x05,0x06,0x24,0x14,0xe0,0x42,0xf5,0x77,0x97,0x43,0x08,0xf7,0x1d,0xe7,0x42,0x74,
14659 0x00,0x97,0x43,0x4d,0x93,0xec,0x42,0xdb,0xf5,0x95,0x43,0x29,0x4b,0xed,0x42,0xcd,0x34,0x95,0x43,0x09,
14660 0x06,0x29,0x7b,0xf5,0x42,0x6f,0x1d,0x98,0x43,0x08,0xe4,0xf1,0xfb,0x42,0x61,0x5c,0x97,0x43,0xdb,0x7d,
14661 0x01,0x43,0xb2,0xbe,0x95,0x43,0x55,0x23,0x02,0x43,0xe7,0xaa,0x94,0x43,0x09,0x06,0x98,0xdc,0x03,0x43,
14662 0xbe,0x8b,0x98,0x43,0x08,0x66,0xdf,0x05,0x43,0x47,0xe6,0x97,0x43,0xae,0x87,0x08,0x43,0x98,0x48,0x96,
14663 0x43,0x61,0x08,0x09,0x43,0xd6,0x06,0x95,0x43,0x09,0x06,0x31,0x0b,0x0b,0x43,0x8e,0x82,0x98,0x43,0x08,
14664 0xdb,0xc5,0x0d,0x43,0x80,0xc1,0x97,0x43,0xd6,0xee,0x10,0x43,0xa9,0xec,0x95,0x43,0x79,0xcb,0x11,0x43,
14665 0x55,0x8f,0x94,0x43,0x09,0x06,0xd1,0x2f,0x18,0x43,0xdb,0x01,0x98,0x43,0x08,0xad,0xe7,0x18,0x43,0x38,
14666 0x25,0x97,0x43,0x8a,0x9f,0x19,0x43,0x80,0xb5,0x95,0x43,0xd6,0x1e,0x19,0x43,0xe0,0xd8,0x94,0x43,0x09,
14667 0x06,0x9a,0x5b,0x1d,0x43,0x58,0x8a,0x97,0x43,0x08,0x01,0x5d,0x1e,0x43,0xf1,0x88,0x96,0x43,0x2f,0x83,
14668 0x1f,0x43,0x19,0xb4,0x94,0x43,0x19,0xf0,0x1e,0x43,0x6f,0x05,0x94,0x43,0x09,0x06,0x0b,0x53,0x24,0x43,
14669 0xae,0xdb,0x96,0x43,0x08,0x25,0xd5,0x25,0x43,0x50,0xac,0x95,0x43,0x53,0xfb,0x26,0x43,0x8a,0x7b,0x93,
14670 0x43,0x76,0x43,0x26,0x43,0xb7,0x95,0x92,0x43,0x09,0x06,0x76,0x5b,0x2a,0x43,0x47,0xda,0x95,0x43,0x08,
14671 0xf3,0xef,0x2b,0x43,0x10,0xe2,0x94,0x43,0x6d,0x95,0x2c,0x43,0xae,0xc3,0x92,0x43,0x68,0xa6,0x2b,0x43,
14672 0x47,0xc2,0x91,0x43,0x09,0x06,0x36,0xc1,0x31,0x43,0x2c,0x58,0x94,0x43,0x08,0x8c,0x1e,0x33,0x43,0x31,
14673 0x3b,0x93,0x43,0x79,0x7a,0x33,0x43,0xff,0x25,0x91,0x43,0xd9,0x9d,0x32,0x43,0xc1,0x5b,0x90,0x43,0x09,
14674 0x06,0x25,0x35,0x36,0x43,0x31,0x3b,0x93,0x43,0x08,0x3f,0xb7,0x37,0x43,0xc1,0x67,0x92,0x43,0xe0,0x93,
14675 0x38,0x43,0xae,0xb7,0x90,0x43,0x7e,0x81,0x38,0x43,0x0d,0xdb,0x8f,0x43,0x09,0x06,0xb5,0x85,0x3b,0x43,
14676 0xe4,0xaf,0x91,0x43,0x08,0xcf,0x07,0x3d,0x43,0x9d,0x13,0x91,0x43,0xbc,0x63,0x3d,0x43,0x47,0xb6,0x8f,
14677 0x43,0xe5,0x9a,0x3d,0x43,0x74,0xd0,0x8e,0x43,0x09,0x06,0xae,0xc6,0x42,0x43,0xa4,0xd9,0x8e,0x43,0x08,
14678 0xca,0x48,0x44,0x43,0xfa,0x2a,0x8e,0x43,0xa2,0x11,0x44,0x43,0x9d,0xfb,0x8c,0x43,0x55,0x92,0x44,0x43,
14679 0x0d,0xc3,0x8b,0x43,0x09,0x06,0x39,0x10,0xc3,0x43,0x34,0x36,0x96,0x43,0x08,0x92,0x44,0xc1,0x43,0xe4,
14680 0xc7,0x95,0x43,0x6f,0xf0,0xbf,0x43,0x4b,0xbd,0x94,0x43,0x47,0xb9,0xbf,0x43,0x0b,0xf3,0x93,0x43,0x09,
14681 0x06,0x8f,0x49,0xbe,0x43,0xb7,0xad,0x96,0x43,0x08,0x11,0xb5,0xbc,0x43,0x77,0xe3,0x95,0x43,0x9c,0xf2,
14682 0xba,0x43,0xfa,0x4e,0x94,0x43,0xae,0x96,0xba,0x43,0x31,0x3b,0x93,0x43,0x09,0x06,0xdb,0xb0,0xb9,0x43,
14683 0x10,0xee,0x96,0x43,0x08,0x42,0xa6,0xb8,0x43,0xc8,0x51,0x96,0x43,0x50,0x5b,0xb7,0x43,0x19,0xb4,0x94,
14684 0x43,0xf7,0x1a,0xb7,0x43,0x58,0x72,0x93,0x43,0x09,0x06,0xf2,0x2b,0xb6,0x43,0x10,0xee,0x96,0x43,0x08,
14685 0x9d,0xce,0xb4,0x43,0x04,0x2d,0x96,0x43,0xed,0x30,0xb3,0x43,0x2c,0x58,0x94,0x43,0xce,0xcb,0xb2,0x43,
14686 0xd6,0xfa,0x92,0x43,0x09,0x06,0x5a,0x09,0xb1,0x43,0x19,0xc0,0x96,0x43,0x08,0x6c,0xad,0xb0,0x43,0x77,
14687 0xe3,0x95,0x43,0x7e,0x51,0xb0,0x43,0xc0,0x73,0x94,0x43,0xd8,0x91,0xb0,0x43,0x1e,0x97,0x93,0x43,0x09,
14688 0x06,0x48,0x4d,0xad,0x43,0xbe,0x7f,0x96,0x43,0x08,0x95,0xcc,0xac,0x43,0x58,0x7e,0x95,0x43,0x4d,0x30,
14689 0xac,0x43,0x80,0xa9,0x93,0x43,0xd8,0x79,0xac,0x43,0xd6,0xfa,0x92,0x43,0x09,0x06,0x90,0xd1,0xa9,0x43,
14690 0x14,0xd1,0x95,0x43,0x08,0x83,0x10,0xa9,0x43,0xb7,0xa1,0x94,0x43,0x3b,0x74,0xa8,0x43,0xf1,0x70,0x92,
14691 0x43,0x29,0xd0,0xa8,0x43,0x1e,0x8b,0x91,0x43,0x09,0x06,0x5a,0xcd,0xa6,0x43,0x8a,0x87,0x95,0x43,0x08,
14692 0x1c,0x03,0xa6,0x43,0x23,0x86,0x94,0x43,0x5f,0xb0,0xa5,0x43,0xc1,0x67,0x92,0x43,0xe1,0x27,0xa6,0x43,
14693 0x8a,0x6f,0x91,0x43,0x09,0x06,0xd4,0x5a,0xa3,0x43,0x2c,0x58,0x94,0x43,0x08,0x29,0xac,0xa2,0x43,0x31,
14694 0x3b,0x93,0x43,0x32,0x7e,0xa2,0x43,0xff,0x25,0x91,0x43,0x83,0xec,0xa2,0x43,0x8e,0x52,0x90,0x43,0x09,
14695 0x06,0xf8,0x96,0xa0,0x43,0x1e,0x97,0x93,0x43,0x08,0xeb,0xd5,0x9f,0x43,0x7b,0xba,0x92,0x43,0x99,0x67,
14696 0x9f,0x43,0x9d,0x13,0x91,0x43,0x99,0x67,0x9f,0x43,0xfa,0x36,0x90,0x43,0x09,0x06,0xeb,0xc9,0x9d,0x43,
14697 0xc8,0x39,0x92,0x43,0x08,0xde,0x08,0x9d,0x43,0xb2,0xa6,0x91,0x43,0xe6,0xda,0x9c,0x43,0x2c,0x40,0x90,
14698 0x43,0x52,0xbf,0x9c,0x43,0x5a,0x5a,0x8f,0x43,0x09,0x06,0x37,0x3d,0x9b,0x43,0x85,0x80,0x90,0x43,0x08,
14699 0x2a,0x7c,0x9a,0x43,0xdb,0xd1,0x8f,0x43,0xf0,0xa0,0x9a,0x43,0x7d,0xa2,0x8e,0x43,0x65,0x57,0x9a,0x43,
14700 0xee,0x69,0x8d,0x43,0x09,0x02,0x04,0x06,0x2a,0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x08,0x0d,0x8a,0x31,
14701 0x42,0x9f,0x0e,0x94,0x43,0xf3,0x1f,0x34,0x42,0x3d,0xfc,0x93,0x43,0x63,0xff,0x36,0x42,0xa9,0xe0,0x93,
14702 0x43,0x08,0xb5,0x34,0x5d,0x42,0x0b,0xf3,0x93,0x43,0x6d,0xa4,0x5e,0x42,0x03,0x39,0x98,0x43,0xe7,0x31,
14703 0x5b,0x42,0x93,0x89,0x9d,0x43,0x08,0x02,0x9c,0x58,0x42,0xd4,0x5a,0xa3,0x43,0x38,0x70,0x53,0x42,0x14,
14704 0x49,0xaa,0x43,0xf8,0xed,0x5e,0x42,0x83,0x28,0xad,0x43,0x08,0xea,0x68,0x68,0x42,0x20,0x22,0xaf,0x43,
14705 0x12,0xb8,0x6c,0x42,0xb5,0x49,0xb1,0x43,0x2a,0x4b,0x6d,0x42,0x0d,0x96,0xb3,0x43,0x07,0x2a,0x4b,0x6d,
14706 0x42,0xc6,0x05,0xb5,0x43,0x08,0x87,0x6e,0x6c,0x42,0x68,0xee,0xb7,0x43,0x1c,0x66,0x66,0x42,0x31,0x0e,
14707 0xbb,0x43,0x57,0x11,0x5e,0x42,0x8f,0x49,0xbe,0x43,0x08,0x66,0x96,0x54,0x42,0xb9,0x5c,0xb8,0x43,0x2c,
14708 0x2b,0x3c,0x42,0x68,0xd6,0xb3,0x43,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x07,0x2a,0xf4,0x2e,0x42,
14709 0x61,0xa4,0xa3,0x43,0x08,0x55,0x1a,0x30,0x42,0xf0,0xd0,0xa2,0x43,0xf8,0xf6,0x30,0x42,0xb2,0x06,0xa2,
14710 0x43,0x98,0xd3,0x31,0x42,0xd6,0x4e,0xa1,0x43,0x08,0x1c,0x6f,0x38,0x42,0x2a,0x94,0x9e,0x43,0xc1,0x22,
14711 0x36,0x42,0xf5,0x9b,0x9d,0x43,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x57,
14712 0xa2,0x9b,0x43,0x08,0xab,0x8f,0x35,0x42,0x8a,0xab,0x9b,0x43,0xe9,0x71,0x3a,0x42,0xb2,0xe2,0x9b,0x43,
14713 0xb7,0x74,0x3c,0x42,0x34,0x5a,0x9c,0x43,0x08,0x23,0x7d,0x42,0x42,0x0b,0x2f,0x9e,0x43,0xe5,0x9a,0x3d,
14714 0x42,0x38,0x6d,0xa3,0x43,0x36,0xd9,0x35,0x42,0xf3,0xd7,0xa7,0x43,0x08,0x12,0x61,0x2e,0x42,0xb0,0x42,
14715 0xac,0x43,0x63,0xff,0x36,0x42,0xdd,0x74,0xaf,0x43,0x1e,0xa6,0x45,0x42,0x44,0x82,0xb2,0x43,0x08,0x74,
14716 0x1b,0x4b,0x42,0x79,0x7a,0xb3,0x43,0x10,0x21,0x4f,0x42,0x2a,0x18,0xb5,0x43,0xdb,0x4c,0x54,0x42,0x91,
14717 0x19,0xb6,0x43,0x08,0xee,0x3f,0x65,0x42,0x5f,0x28,0xba,0x43,0xa7,0xaf,0x66,0x42,0xb9,0x50,0xb6,0x43,
14718 0x14,0x58,0x5c,0x42,0xca,0xdc,0xb1,0x43,0x08,0x2c,0x8b,0x4c,0x42,0x4e,0x30,0xac,0x43,0x19,0xcf,0x48,
14719 0x42,0x2a,0xd0,0xa8,0x43,0xbc,0xab,0x49,0x42,0xa9,0x4c,0xa6,0x43,0x08,0x61,0x5f,0x47,0x42,0xfa,0xa2,
14720 0xa2,0x43,0xa7,0xaf,0x66,0x42,0x85,0x98,0x94,0x43,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x07,0x2a,
14721 0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x09,0x06,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x08,0xdc,0xe3,
14722 0xf1,0x41,0xe9,0x9e,0x92,0x43,0xd2,0xe7,0x0b,0x42,0xd6,0x06,0x95,0x43,0x2a,0xf4,0x2e,0x42,0x04,0x21,
14723 0x94,0x43,0x07,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x08,0x87,0x17,0x2e,0x42,0xc3,0x62,0x95,0x43,
14724 0xe7,0x3a,0x2d,0x42,0xf5,0x6b,0x95,0x43,0x44,0x5e,0x2c,0x42,0xf5,0x6b,0x95,0x43,0x08,0xd1,0x47,0x1c,
14725 0x42,0x19,0xc0,0x96,0x43,0x66,0xdf,0x05,0x42,0x38,0x19,0x95,0x43,0x12,0x6a,0x00,0x42,0xb2,0xbe,0x95,
14726 0x43,0x08,0xbb,0x6b,0xea,0x41,0xd6,0x12,0x97,0x43,0x2d,0x82,0xfa,0x41,0x61,0x74,0x9b,0x43,0x7e,0x72,
14727 0x06,0x42,0x8a,0xab,0x9b,0x43,0x08,0xc8,0x39,0x12,0x42,0x4e,0xd0,0x9b,0x43,0x53,0xe3,0x22,0x42,0xc3,
14728 0x86,0x9b,0x43,0x2a,0xf4,0x2e,0x42,0x57,0xa2,0x9b,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,
14729 0x08,0x01,0xa5,0x2a,0x42,0xa4,0x2d,0x9d,0x43,0x96,0x9c,0x24,0x42,0x06,0x40,0x9d,0x43,0x8a,0xb7,0x1d,
14730 0x42,0x9a,0x5b,0x9d,0x43,0x08,0x6b,0x16,0x13,0x42,0xcd,0x64,0x9d,0x43,0x42,0xc7,0x0e,0x42,0x9a,0x5b,
14731 0x9d,0x43,0x23,0x26,0x04,0x42,0xcd,0x64,0x9d,0x43,0x08,0xe6,0x91,0xeb,0x41,0x38,0x49,0x9d,0x43,0x73,
14732 0x7b,0xdb,0x41,0xf5,0x83,0x99,0x43,0x7f,0x60,0xe2,0x41,0x0b,0x0b,0x98,0x43,0x08,0x7f,0x60,0xe2,0x41,
14733 0xec,0x99,0x95,0x43,0xe3,0x5a,0xde,0x41,0xbe,0x7f,0x96,0x43,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,
14734 0x07,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x09,0x06,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x08,
14735 0xd4,0x7e,0x29,0x42,0xab,0x6b,0xaf,0x43,0x4e,0x0c,0x26,0x42,0x44,0x6a,0xae,0x43,0x38,0x79,0x25,0x42,
14736 0xd4,0x96,0xad,0x43,0x08,0x25,0xbd,0x21,0x42,0xe2,0x4b,0xac,0x43,0x49,0x35,0x29,0x42,0x9a,0x97,0xa7,
14737 0x43,0x2a,0xf4,0x2e,0x42,0x61,0xa4,0xa3,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x09,0x06,
14738 0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x08,0x86,0x20,0x80,0x43,0x57,0x41,0xe6,0x43,0x7d,0x4e,0x80,
14739 0x43,0x25,0x38,0xe6,0x43,0xa5,0x85,0x80,0x43,0xf3,0x2e,0xe6,0x43,0x08,0x35,0xca,0x83,0x43,0xd4,0xc9,
14740 0xe5,0x43,0x9c,0xd7,0x86,0x43,0x44,0x91,0xe4,0x43,0xd5,0xca,0x8a,0x43,0x91,0x1c,0xe6,0x43,0x08,0x53,
14741 0x5f,0x8c,0x43,0xf8,0x1d,0xe7,0x43,0x2f,0x17,0x8d,0x43,0x4e,0x7b,0xe8,0x43,0x92,0x29,0x8d,0x43,0x2f,
14742 0x22,0xea,0x43,0x07,0x92,0x29,0x8d,0x43,0x44,0xb5,0xea,0x43,0x08,0xfe,0x0d,0x8d,0x43,0x2a,0x4b,0xed,
14743 0x43,0xe3,0x8b,0x8b,0x43,0x55,0x7d,0xf0,0x43,0xec,0x51,0x89,0x43,0x72,0x0b,0xf4,0x43,0x08,0xcd,0xd4,
14744 0x84,0x43,0x9d,0x55,0xfb,0x43,0xc9,0xe5,0x83,0x43,0x74,0x1e,0xfb,0x43,0x73,0x94,0x84,0x43,0x5a,0x90,
14745 0xf7,0x43,0x08,0xe8,0x62,0x88,0x43,0xfd,0x30,0xee,0x43,0x39,0xc5,0x86,0x43,0xdd,0xbf,0xeb,0x43,0x35,
14746 0xbe,0x81,0x43,0x40,0xde,0xed,0x43,0x08,0x4f,0x34,0x81,0x43,0x36,0x0c,0xee,0x43,0x08,0x98,0x80,0x43,
14747 0xfd,0x30,0xee,0x43,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x40,0xec,
14748 0x43,0x08,0x35,0xbe,0x81,0x43,0x06,0xf7,0xeb,0x43,0x15,0x65,0x83,0x43,0x49,0xa4,0xeb,0x43,0x1e,0x43,
14749 0x85,0x43,0xbe,0x5a,0xeb,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0x18,0xea,0x43,0x42,0x97,0x86,0x43,0x5f,
14750 0x67,0xf4,0x43,0xa9,0x98,0x87,0x43,0xd4,0x1d,0xf4,0x43,0x08,0x5c,0x25,0x8a,0x43,0xcf,0x16,0xef,0x43,
14751 0x46,0xaa,0x8d,0x43,0x5a,0x3c,0xe9,0x43,0x19,0x6c,0x88,0x43,0x53,0x5e,0xe7,0x43,0x08,0xc4,0x02,0x85,
14752 0x43,0x96,0x0b,0xe7,0x43,0x85,0x2c,0x82,0x43,0x83,0x67,0xe7,0x43,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,
14753 0x43,0x07,0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x09,0x06,0xfd,0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,
14754 0x08,0xfa,0x6c,0x78,0x43,0xd1,0xc2,0xe0,0x43,0x25,0x5c,0x6c,0x43,0x25,0x44,0xe8,0x43,0x1d,0xe5,0x7f,
14755 0x43,0x87,0x4a,0xe6,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,0x43,0x08,0xa6,0x27,0x7b,0x43,0x91,
14756 0x28,0xe8,0x43,0xbc,0xa2,0x77,0x43,0xb0,0x8d,0xe8,0x43,0xc6,0x68,0x75,0x43,0x57,0x4d,0xe8,0x43,0x08,
14757 0xe0,0xd2,0x72,0x43,0xab,0x9e,0xe7,0x43,0x50,0x9a,0x71,0x43,0x2a,0x27,0xe7,0x43,0xea,0x98,0x70,0x43,
14758 0x57,0x35,0xe4,0x43,0x08,0x94,0x3b,0x6f,0x43,0x14,0x7c,0xe2,0x43,0xff,0x13,0x6d,0x43,0x06,0xbb,0xe1,
14759 0x43,0xcf,0xfe,0x6a,0x43,0x06,0xbb,0xe1,0x43,0x08,0x44,0x9d,0x66,0x43,0x77,0x8e,0xe2,0x43,0x3b,0xef,
14760 0x6c,0x43,0x91,0x10,0xe4,0x43,0xfd,0x24,0x6c,0x43,0xb0,0x81,0xe6,0x43,0x08,0x96,0x23,0x6b,0x43,0xee,
14761 0x57,0xe9,0x43,0xca,0x0f,0x6a,0x43,0x5f,0x37,0xec,0x43,0x55,0x71,0x6e,0x43,0x9f,0x01,0xed,0x43,0x08,
14762 0xdb,0xfb,0x75,0x43,0x3b,0xef,0xec,0x43,0x09,0x3a,0x7b,0x43,0xb0,0xa5,0xec,0x43,0x1d,0xe5,0x7f,0x43,
14763 0x91,0x40,0xec,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x08,0xa9,0x16,0x7c,0x43,0xb0,0xb1,
14764 0xee,0x43,0x47,0xec,0x77,0x43,0xd9,0xe8,0xee,0x43,0x1e,0x9d,0x73,0x43,0xcf,0x16,0xef,0x43,0x08,0x0e,
14765 0xc9,0x6b,0x43,0xee,0x7b,0xef,0x43,0x7e,0x90,0x6a,0x43,0xfd,0x30,0xee,0x43,0x01,0xfc,0x68,0x43,0x4e,
14766 0x93,0xec,0x43,0x08,0x31,0xf9,0x66,0x43,0x4e,0x87,0xea,0x43,0x31,0x11,0x6b,0x43,0xd4,0xd5,0xe7,0x43,
14767 0xd9,0xc4,0x68,0x43,0xd4,0xc9,0xe5,0x43,0x08,0xe5,0x79,0x67,0x43,0x77,0x9a,0xe4,0x43,0x44,0x9d,0x66,
14768 0x43,0xab,0x86,0xe3,0x43,0x7e,0x78,0x66,0x43,0x0b,0xaa,0xe2,0x43,0x07,0x7e,0x78,0x66,0x43,0x57,0x29,
14769 0xe2,0x43,0x08,0xa7,0xaf,0x66,0x43,0xbe,0x1e,0xe1,0x43,0x87,0x56,0x68,0x43,0x77,0x82,0xe0,0x43,0xfd,
14770 0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,0x09,0x06,0xc4,0x41,0xbf,0x43,0x85,0xc0,0x72,0x42,0x08,0x73,0xdf,
14771 0xc0,0x43,0xf4,0x76,0x72,0x42,0x97,0x33,0xc2,0x43,0x85,0xc0,0x72,0x42,0xb2,0xb5,0xc3,0x43,0x64,0x56,
14772 0x75,0x42,0x08,0x03,0x24,0xc4,0x43,0x5e,0x7f,0x78,0x42,0xfa,0x51,0xc4,0x43,0x01,0x85,0x7c,0x42,0x5c,
14773 0x64,0xc4,0x43,0xa0,0xb3,0x80,0x42,0x07,0x5c,0x64,0xc4,0x43,0x10,0x93,0x83,0x42,0x08,0xc8,0x48,0xc4,
14774 0x43,0x1c,0x78,0x8a,0x42,0x27,0x6c,0xc3,0x43,0xaf,0xcf,0x94,0x42,0x23,0x7d,0xc2,0x43,0x99,0x9c,0xa4,
14775 0x42,0x08,0x3d,0xe7,0xbf,0x43,0xfb,0xfd,0xb5,0x42,0xb3,0x9d,0xbf,0x43,0x88,0x17,0xae,0x42,0xc4,0x41,
14776 0xbf,0x43,0x69,0x76,0xa3,0x42,0x07,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x08,0x4f,0x8b,0xbf,0x43,
14777 0xed,0x81,0x91,0x42,0xe4,0xa6,0xbf,0x43,0x5d,0x61,0x94,0x42,0xfa,0x39,0xc0,0x43,0x3b,0x49,0x9d,0x42,
14778 0x08,0x2b,0x43,0xc0,0x43,0x28,0xed,0xa9,0x42,0x61,0x3b,0xc1,0x43,0x00,0x9e,0xa5,0x42,0xe4,0xb2,0xc1,
14779 0x43,0x5d,0x91,0x9c,0x42,0x08,0x78,0xce,0xc1,0x43,0xfd,0x36,0x90,0x42,0x22,0x89,0xc4,0x43,0x81,0x72,
14780 0x86,0x42,0xae,0xc6,0xc2,0x43,0xa0,0xb3,0x80,0x42,0x08,0x54,0x86,0xc2,0x43,0x58,0xd1,0x7e,0x42,0x30,
14781 0x32,0xc1,0x43,0xce,0x5e,0x7b,0x42,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x07,0xc4,0x41,0xbf,0x43,
14782 0x85,0xc0,0x72,0x42,0x09,0x06,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x08,0x35,0xfd,0xbb,0x43,0xa4,
14783 0xa1,0x5c,0x42,0x5e,0x34,0xbc,0x43,0x9d,0x2a,0x70,0x42,0x5e,0x40,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,
14784 0x4c,0x9c,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,0xef,0xbe,0x43,0x0e,0x0a,0x73,0x42,0xc4,0x41,0xbf,0x43,
14785 0x85,0xc0,0x72,0x42,0x07,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x08,0xcd,0x13,0xbf,0x43,0xe8,0xf1,
14786 0x7b,0x42,0xd6,0xe5,0xbe,0x43,0x71,0x3b,0x7c,0x42,0xdf,0xb7,0xbe,0x43,0x71,0x3b,0x7c,0x42,0x08,0x08,
14787 0xe3,0xbc,0x43,0xa4,0x61,0x7d,0x42,0x28,0x3c,0xbb,0x43,0x91,0x45,0x69,0x42,0x28,0x3c,0xbb,0x43,0x58,
14788 0x71,0x6e,0x42,0x08,0xce,0xfb,0xba,0x43,0xd5,0x35,0x78,0x42,0x59,0x45,0xbb,0x43,0x58,0x23,0x82,0x42,
14789 0xa1,0xe1,0xbb,0x43,0xd7,0xbe,0x88,0x42,0x08,0xc9,0x18,0xbc,0x43,0xaf,0x9f,0x8c,0x42,0x1e,0x76,0xbd,
14790 0x43,0x51,0x7c,0x8d,0x42,0xd6,0xe5,0xbe,0x43,0xf4,0x58,0x8e,0x42,0x08,0x9c,0x0a,0xbf,0x43,0x45,0xc7,
14791 0x8e,0x42,0x30,0x26,0xbf,0x43,0x96,0x35,0x8f,0x42,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x07,0xc4,
14792 0x41,0xbf,0x43,0x69,0x76,0xa3,0x42,0x08,0x08,0xef,0xbe,0x43,0xb1,0xd6,0x99,0x42,0xe8,0x89,0xbe,0x43,
14793 0xde,0xc5,0x8d,0x42,0xc0,0x46,0xbc,0x43,0xc2,0x5b,0x90,0x42,0x08,0x9c,0xf2,0xba,0x43,0x86,0x80,0x90,
14794 0x42,0xf2,0x43,0xba,0x43,0xe8,0x73,0x87,0x42,0x8f,0x31,0xba,0x43,0xb6,0xf4,0x7d,0x42,0x07,0x8f,0x31,
14795 0xba,0x43,0x21,0xc6,0x76,0x42,0x08,0xc0,0x3a,0xba,0x43,0x5f,0x48,0x6b,0x42,0xae,0x96,0xba,0x43,0xe3,
14796 0x83,0x61,0x42,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x09,0x06,0xea,0x74,0xea,0x43,0x61,0x44,0x93,
14797 0x43,0x08,0x24,0x5c,0xec,0x43,0x31,0x3b,0x93,0x43,0xfb,0x30,0xee,0x43,0x93,0x4d,0x93,0x43,0x0d,0xe1,
14798 0xef,0x43,0x80,0xa9,0x93,0x43,0x08,0x8f,0x58,0xf0,0x43,0xd1,0x17,0x94,0x43,0xb7,0x8f,0xf0,0x43,0x10,
14799 0xe2,0x94,0x43,0xea,0x98,0xf0,0x43,0xa9,0xec,0x95,0x43,0x07,0xea,0x98,0xf0,0x43,0x38,0x25,0x97,0x43,
14800 0x08,0x23,0x74,0xf0,0x43,0x9f,0x32,0x9a,0x43,0x5a,0x60,0xef,0x43,0x53,0xcb,0x9e,0x43,0x2d,0x3a,0xee,
14801 0x43,0xfd,0x91,0xa3,0x43,0x08,0xa2,0xf0,0xed,0x43,0xdd,0x38,0xa5,0x43,0x17,0xa7,0xed,0x43,0xbe,0xdf,
14802 0xa6,0x43,0x5a,0x54,0xed,0x43,0x9f,0x86,0xa8,0x43,0x08,0xfc,0x24,0xec,0x43,0xca,0xc4,0xad,0x43,0x48,
14803 0xa4,0xeb,0x43,0x40,0x6f,0xab,0x43,0x28,0x3f,0xeb,0x43,0x1c,0x0f,0xa8,0x43,0x08,0x1f,0x6d,0xeb,0x43,
14804 0x72,0x48,0xa3,0x43,0x67,0x09,0xec,0x43,0xd1,0x53,0x9e,0x43,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,0x43,
14805 0x07,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x08,0x7e,0x90,0xea,0x43,0x8a,0x9f,0x99,0x43,0x12,0xac,
14806 0xea,0x43,0xbc,0xa8,0x99,0x43,0xa7,0xc7,0xea,0x43,0xbc,0xa8,0x99,0x43,0x08,0x51,0x76,0xeb,0x43,0x9f,
14807 0x32,0x9a,0x43,0x5e,0x37,0xec,0x43,0x49,0xed,0x9c,0x43,0xb0,0xa5,0xec,0x43,0x2a,0xa0,0xa0,0x43,0x08,
14808 0x09,0xe6,0xec,0x43,0xd1,0x77,0xa4,0x43,0x28,0x4b,0xed,0x43,0x61,0xa4,0xa3,0x43,0xab,0xc2,0xed,0x43,
14809 0x8e,0xb2,0xa0,0x43,0x08,0x70,0xe7,0xed,0x43,0xde,0x08,0x9d,0x43,0x87,0x86,0xf0,0x43,0x2f,0x53,0x97,
14810 0x43,0x87,0x7a,0xee,0x43,0xec,0x99,0x95,0x43,0x08,0xca,0x27,0xee,0x43,0xff,0x3d,0x95,0x43,0x74,0xca,
14811 0xec,0x43,0x55,0x8f,0x94,0x43,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x07,0xea,0x74,0xea,0x43,0x61,
14812 0x44,0x93,0x43,0x09,0x06,0x05,0xd3,0xe5,0x43,0x19,0x9c,0x90,0x43,0x08,0x09,0xc2,0xe6,0x43,0xd1,0xff,
14813 0x8f,0x43,0x4d,0x6f,0xe6,0x43,0x74,0xe8,0x92,0x43,0x3b,0xd7,0xe8,0x43,0xc3,0x56,0x93,0x43,0x08,0x1f,
14814 0x61,0xe9,0x43,0x93,0x4d,0x93,0x43,0x05,0xeb,0xe9,0x43,0x93,0x4d,0x93,0x43,0xea,0x74,0xea,0x43,0x61,
14815 0x44,0x93,0x43,0x07,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x08,0x24,0x50,0xea,0x43,0xe7,0xaa,0x94,
14816 0x43,0x2d,0x22,0xea,0x43,0xe7,0xaa,0x94,0x43,0x36,0xf4,0xe9,0x43,0xe7,0xaa,0x94,0x43,0x08,0xa2,0xcc,
14817 0xe7,0x43,0xe0,0xd8,0x94,0x43,0xd4,0xc9,0xe5,0x43,0x19,0xa8,0x92,0x43,0xd4,0xc9,0xe5,0x43,0x27,0x69,
14818 0x93,0x43,0x08,0x17,0x77,0xe5,0x43,0xe0,0xd8,0x94,0x43,0x67,0xe5,0xe5,0x43,0x47,0xda,0x95,0x43,0x43,
14819 0x9d,0xe6,0x43,0xe2,0xd3,0x97,0x43,0x08,0x9d,0xdd,0xe6,0x43,0xad,0xe7,0x98,0x43,0x09,0xce,0xe8,0x43,
14820 0xff,0x55,0x99,0x43,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x07,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,
14821 0x43,0x08,0x71,0xcf,0xe9,0x43,0x53,0xb3,0x9a,0x43,0xa7,0xbb,0xe8,0x43,0xdb,0x0d,0x9a,0x43,0xc6,0x14,
14822 0xe7,0x43,0xdb,0x0d,0x9a,0x43,0x08,0x48,0x80,0xe5,0x43,0xdb,0x0d,0x9a,0x43,0x0a,0xb6,0xe4,0x43,0xc3,
14823 0x6e,0x97,0x43,0x76,0x9a,0xe4,0x43,0x74,0xf4,0x94,0x43,0x07,0x76,0x9a,0xe4,0x43,0x79,0xd7,0x93,0x43,
14824 0x08,0xd8,0xac,0xe4,0x43,0x66,0x27,0x92,0x43,0x29,0x1b,0xe5,0x43,0xe0,0xc0,0x90,0x43,0x05,0xd3,0xe5,
14825 0x43,0x19,0x9c,0x90,0x43,0x09,0x06,0x1b,0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x08,0x71,0x0b,0xf4,0x42,
14826 0x00,0x0e,0x8d,0x42,0x8c,0x0f,0x01,0x43,0x3e,0xc0,0x89,0x42,0xf3,0x28,0x06,0x43,0x48,0x9e,0x8b,0x42,
14827 0x08,0x15,0x89,0x09,0x43,0x00,0x0e,0x8d,0x42,0xe0,0x9c,0x0a,0x43,0xc1,0x8b,0x98,0x42,0xa6,0xc1,0x0a,
14828 0x43,0x02,0xa5,0xaa,0x42,0x07,0xa6,0xc1,0x0a,0x43,0xf9,0xf6,0xb0,0x42,0x08,0xa6,0xc1,0x0a,0x43,0x47,
14829 0x8e,0xb4,0x42,0x42,0xaf,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0xe0,0x9c,0x0a,0x43,0xba,0x74,0xbc,0x42,0x08,
14830 0xa1,0xd2,0x09,0x43,0x40,0x47,0xd0,0x42,0x0d,0xab,0x07,0x43,0x91,0xb5,0xd0,0x42,0x3b,0xb9,0x04,0x43,
14831 0xec,0x71,0xba,0x42,0x08,0xe5,0x5b,0x03,0x43,0xe3,0x33,0xa8,0x42,0x63,0xd8,0x00,0x43,0xce,0x70,0x9f,
14832 0x42,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x07,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,0x42,0x08,0xed,
14833 0x6f,0xed,0x42,0x73,0x24,0x9d,0x42,0xd8,0x0c,0xf5,0x42,0x99,0x6c,0x9c,0x42,0x27,0xab,0xfd,0x42,0xea,
14834 0xda,0x9c,0x42,0x08,0x36,0xca,0x03,0x43,0x2b,0x94,0x9e,0x42,0x68,0xc7,0x01,0x43,0x8f,0xbe,0xa2,0x42,
14835 0xfa,0x06,0x08,0x43,0x73,0xb4,0xb5,0x42,0x08,0x8e,0x2e,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0x9d,0xe3,0x08,
14836 0x43,0xd7,0x1e,0x99,0x42,0x28,0x15,0x05,0x43,0x32,0x3b,0x93,0x42,0x08,0x63,0xf0,0x04,0x43,0x70,0xed,
14837 0x8f,0x42,0x71,0x0b,0xf4,0x42,0x32,0x3b,0x93,0x42,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x07,0x1b,
14838 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x09,0x06,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,0x08,0x8e,0x55,
14839 0xc0,0x42,0xb8,0x4d,0x86,0x42,0x60,0xbf,0xd7,0x42,0x3e,0xf0,0x91,0x42,0x63,0xf6,0xe4,0x42,0x70,0xed,
14840 0x8f,0x42,0x08,0x7a,0x89,0xe5,0x42,0xac,0xc8,0x8f,0x42,0xcc,0xf7,0xe5,0x42,0xac,0xc8,0x8f,0x42,0x1b,
14841 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x07,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x08,0x63,0xf6,0xe4,
14842 0x42,0x3b,0x19,0x95,0x42,0xe6,0x61,0xe3,0x42,0x00,0x3e,0x95,0x42,0xf4,0x16,0xe2,0x42,0xc4,0x62,0x95,
14843 0x42,0x08,0x6e,0x74,0xd6,0x42,0x15,0xd1,0x95,0x42,0x97,0x63,0xca,0x42,0xaf,0xcf,0x94,0x42,0xfb,0x2d,
14844 0xbe,0x42,0x86,0x80,0x90,0x42,0x08,0x97,0x03,0xba,0x42,0xce,0x10,0x8f,0x42,0x5e,0x28,0xba,0x42,0x3e,
14845 0xf0,0x91,0x42,0xf2,0x4f,0xbc,0x42,0x45,0xf7,0x96,0x42,0x08,0x27,0x54,0xbf,0x42,0x73,0x24,0x9d,0x42,
14846 0xa5,0xe8,0xc0,0x42,0x86,0xe0,0xa0,0x42,0xe4,0xca,0xc5,0x42,0xed,0x11,0xaa,0x42,0x08,0x54,0xaa,0xc8,
14847 0x42,0x86,0x40,0xb1,0x42,0x59,0x81,0xc5,0x42,0xa1,0x11,0xc4,0x42,0x3e,0xe7,0xbf,0x42,0xfb,0x8d,0xce,
14848 0x42,0x08,0xb4,0x6d,0xb7,0x42,0x30,0xc2,0xd9,0x42,0x46,0xf5,0xc9,0x42,0xdf,0x53,0xd9,0x42,0x38,0x40,
14849 0xcb,0x42,0x62,0x8f,0xcf,0x42,0x08,0x7d,0xf9,0xcc,0x42,0xec,0xa1,0xc2,0x42,0x07,0x43,0xcd,0x42,0x6c,
14850 0xdd,0xb8,0x42,0x2b,0x8b,0xcc,0x42,0x92,0xf5,0xaf,0x42,0x08,0xf9,0x8d,0xce,0x42,0x41,0x57,0xa7,0x42,
14851 0x5b,0xb8,0xd2,0x42,0xae,0x2f,0xa5,0x42,0x18,0x2f,0xd9,0x42,0x13,0x2a,0xa1,0x42,0x08,0x41,0x7e,0xdd,
14852 0x42,0xe3,0x03,0xa0,0x42,0x2e,0xf2,0xe1,0x42,0x7c,0x02,0x9f,0x42,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,
14853 0x42,0x07,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x08,0x4d,0x63,0xe4,0x42,0x00,0x9e,0xa5,0x42,0xf4,
14854 0x16,0xe2,0x42,0x15,0x31,0xa6,0x42,0x99,0xca,0xdf,0x42,0x2b,0xc4,0xa6,0x42,0x08,0xc0,0x82,0xc6,0x42,
14855 0xc4,0xc2,0xa5,0x42,0x57,0xe1,0xd5,0x42,0x91,0xb5,0xd0,0x42,0x54,0xda,0xd0,0x42,0x97,0x93,0xd2,0x42,
14856 0x08,0x9c,0x3a,0xc7,0x42,0x17,0x58,0xdc,0x42,0x9c,0x0a,0xbf,0x42,0x6e,0xa4,0xde,0x42,0x90,0x25,0xb8,
14857 0x42,0xdf,0x53,0xd9,0x42,0x08,0x59,0x21,0xb5,0x42,0xf2,0xdf,0xd4,0x42,0x51,0x43,0xb3,0x42,0x91,0xb5,
14858 0xd0,0x42,0xc5,0x29,0xbb,0x42,0x0e,0x1a,0xca,0x42,0x08,0x65,0x36,0xc4,0x42,0xd0,0x07,0xbd,0x42,0x3e,
14859 0xe7,0xbf,0x42,0x37,0x09,0xbe,0x42,0x0c,0xea,0xc1,0x42,0xcd,0xd0,0xaf,0x42,0x08,0x2b,0x5b,0xc4,0x42,
14860 0x18,0x08,0xa3,0x42,0x67,0xa6,0xab,0x42,0x99,0x3c,0x94,0x42,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,
14861 0x09,];
14863 private struct ThePath {
14864 public:
14865 enum Command {
14866 Bounds, // always first, has 4 args (x0, y0, x1, y1)
14867 StrokeMode,
14868 FillMode,
14869 StrokeFillMode,
14870 NormalStroke,
14871 ThinStroke,
14872 MoveTo,
14873 LineTo,
14874 CubicTo, // cubic bezier
14875 EndPath,
14878 public:
14879 const(ubyte)[] path;
14880 uint ppos;
14882 public:
14883 this (const(void)[] apath) pure nothrow @trusted @nogc {
14884 path = cast(const(ubyte)[])apath;
14887 @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (ppos >= path.length); }
14889 Command getCommand () nothrow @trusted @nogc {
14890 pragma(inline, true);
14891 if (ppos >= cast(uint)path.length) assert(0, "invalid path");
14892 return cast(Command)(path.ptr[ppos++]);
14895 // number of (x,y) pairs for this command
14896 static int argCount (in Command cmd) nothrow @safe @nogc {
14897 version(aliced) pragma(inline, true);
14898 if (cmd == Command.Bounds) return 2;
14899 else if (cmd == Command.MoveTo || cmd == Command.LineTo) return 1;
14900 else if (cmd == Command.CubicTo) return 3;
14901 else return 0;
14904 void skipArgs (int argc) nothrow @trusted @nogc {
14905 pragma(inline, true);
14906 ppos += cast(uint)(float.sizeof*2*argc);
14909 float getFloat () nothrow @trusted @nogc {
14910 pragma(inline, true);
14911 if (ppos >= cast(uint)path.length || cast(uint)path.length-ppos < float.sizeof) assert(0, "invalid path");
14912 version(LittleEndian) {
14913 float res = *cast(const(float)*)(&path.ptr[ppos]);
14914 ppos += cast(uint)float.sizeof;
14915 return res;
14916 } else {
14917 static assert(float.sizeof == 4);
14918 uint xp = path.ptr[ppos]|(path.ptr[ppos+1]<<8)|(path.ptr[ppos+2]<<16)|(path.ptr[ppos+3]<<24);
14919 ppos += cast(uint)float.sizeof;
14920 return *cast(const(float)*)(&xp);
14925 // this will add baphomet's background path to the current NanoVega path, so you can fill it.
14926 public void addBaphometBack (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc {
14927 if (nvg is null) return;
14929 auto path = ThePath(baphometPath);
14931 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
14932 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
14934 bool inPath = false;
14935 while (!path.empty) {
14936 auto cmd = path.getCommand();
14937 switch (cmd) {
14938 case ThePath.Command.MoveTo:
14939 inPath = true;
14940 immutable float ex = getScaledX();
14941 immutable float ey = getScaledY();
14942 nvg.moveTo(ex, ey);
14943 break;
14944 case ThePath.Command.LineTo:
14945 inPath = true;
14946 immutable float ex = getScaledX();
14947 immutable float ey = getScaledY();
14948 nvg.lineTo(ex, ey);
14949 break;
14950 case ThePath.Command.CubicTo: // cubic bezier
14951 inPath = true;
14952 immutable float x1 = getScaledX();
14953 immutable float y1 = getScaledY();
14954 immutable float x2 = getScaledX();
14955 immutable float y2 = getScaledY();
14956 immutable float ex = getScaledX();
14957 immutable float ey = getScaledY();
14958 nvg.bezierTo(x1, y1, x2, y2, ex, ey);
14959 break;
14960 case ThePath.Command.EndPath:
14961 if (inPath) return;
14962 break;
14963 default:
14964 path.skipArgs(path.argCount(cmd));
14965 break;
14970 // this will add baphomet's pupil paths to the current NanoVega path, so you can fill it.
14971 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 {
14972 // pupils starts with "fill-and-stroke" mode
14973 if (nvg is null) return;
14975 auto path = ThePath(baphometPath);
14977 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
14978 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
14980 bool inPath = false;
14981 bool pupLeft = true;
14982 while (!path.empty) {
14983 auto cmd = path.getCommand();
14984 switch (cmd) {
14985 case ThePath.Command.StrokeFillMode: inPath = true; break;
14986 case ThePath.Command.MoveTo:
14987 if (!inPath) goto default;
14988 static if (!left) { if (pupLeft) goto default; }
14989 static if (!right) { if (!pupLeft) goto default; }
14990 immutable float ex = getScaledX();
14991 immutable float ey = getScaledY();
14992 nvg.moveTo(ex, ey);
14993 break;
14994 case ThePath.Command.LineTo:
14995 if (!inPath) goto default;
14996 static if (!left) { if (pupLeft) goto default; }
14997 static if (!right) { if (!pupLeft) goto default; }
14998 immutable float ex = getScaledX();
14999 immutable float ey = getScaledY();
15000 nvg.lineTo(ex, ey);
15001 break;
15002 case ThePath.Command.CubicTo: // cubic bezier
15003 if (!inPath) goto default;
15004 static if (!left) { if (pupLeft) goto default; }
15005 static if (!right) { if (!pupLeft) goto default; }
15006 immutable float x1 = getScaledX();
15007 immutable float y1 = getScaledY();
15008 immutable float x2 = getScaledX();
15009 immutable float y2 = getScaledY();
15010 immutable float ex = getScaledX();
15011 immutable float ey = getScaledY();
15012 nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15013 break;
15014 case ThePath.Command.EndPath:
15015 if (inPath) {
15016 if (pupLeft) pupLeft = false; else return;
15018 break;
15019 default:
15020 path.skipArgs(path.argCount(cmd));
15021 break;
15026 // mode: 'f' to allow fills; 's' to allow strokes; 'w' to allow stroke widths; 'c' to replace fills with strokes
15027 public void renderBaphomet(string mode="fs") (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc {
15028 template hasChar(char ch, string s) {
15029 static if (s.length == 0) enum hasChar = false;
15030 else static if (s[0] == ch) enum hasChar = true;
15031 else enum hasChar = hasChar!(ch, s[1..$]);
15033 enum AllowStroke = hasChar!('s', mode);
15034 enum AllowFill = hasChar!('f', mode);
15035 enum AllowWidth = hasChar!('w', mode);
15036 enum Contour = hasChar!('c', mode);
15037 //static assert(AllowWidth || AllowFill);
15039 if (nvg is null) return;
15041 auto path = ThePath(baphometPath);
15043 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15044 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15046 int mode = 0;
15047 int sw = ThePath.Command.NormalStroke;
15048 nvg.beginPath();
15049 while (!path.empty) {
15050 auto cmd = path.getCommand();
15051 switch (cmd) {
15052 case ThePath.Command.StrokeMode: mode = ThePath.Command.StrokeMode; break;
15053 case ThePath.Command.FillMode: mode = ThePath.Command.FillMode; break;
15054 case ThePath.Command.StrokeFillMode: mode = ThePath.Command.StrokeFillMode; break;
15055 case ThePath.Command.NormalStroke: sw = ThePath.Command.NormalStroke; break;
15056 case ThePath.Command.ThinStroke: sw = ThePath.Command.ThinStroke; break;
15057 case ThePath.Command.MoveTo:
15058 immutable float ex = getScaledX();
15059 immutable float ey = getScaledY();
15060 nvg.moveTo(ex, ey);
15061 break;
15062 case ThePath.Command.LineTo:
15063 immutable float ex = getScaledX();
15064 immutable float ey = getScaledY();
15065 nvg.lineTo(ex, ey);
15066 break;
15067 case ThePath.Command.CubicTo: // cubic bezier
15068 immutable float x1 = getScaledX();
15069 immutable float y1 = getScaledY();
15070 immutable float x2 = getScaledX();
15071 immutable float y2 = getScaledY();
15072 immutable float ex = getScaledX();
15073 immutable float ey = getScaledY();
15074 nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15075 break;
15076 case ThePath.Command.EndPath:
15077 if (mode == ThePath.Command.FillMode || mode == ThePath.Command.StrokeFillMode) {
15078 static if (AllowFill || Contour) {
15079 static if (Contour) {
15080 if (mode == ThePath.Command.FillMode) { nvg.strokeWidth = 1; nvg.stroke(); }
15081 } else {
15082 nvg.fill();
15086 if (mode == ThePath.Command.StrokeMode || mode == ThePath.Command.StrokeFillMode) {
15087 static if (AllowStroke || Contour) {
15088 static if (AllowWidth) {
15089 if (sw == ThePath.Command.NormalStroke) nvg.strokeWidth = 1;
15090 else if (sw == ThePath.Command.ThinStroke) nvg.strokeWidth = 0.5;
15091 else assert(0, "wtf?!");
15093 nvg.stroke();
15096 nvg.newPath();
15097 break;
15098 default:
15099 path.skipArgs(path.argCount(cmd));
15100 break;
15103 nvg.newPath();