dynstring: use unified storage for both metadata and data
[iv.d.git] / nanovega / nanovega.d
blobd0ae7c55c803e78b74f8d9cf524c0d501ccea5d4
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, version 3 of the License ONLY.
27 * This program is distributed in the hope that it will be useful,
28 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 * GNU General Public License for more details.
32 * You should have received a copy of the GNU General Public License
33 * along with this program. If not, see <http://www.gnu.org/licenses/>.
35 /**
36 The NanoVega API is modeled loosely on HTML5 canvas API.
37 If you know canvas, you're up to speed with NanoVega in no time.
40 Creating drawing context
41 ========================
43 The drawing context is created using platform specific constructor function.
45 ---
46 NVGContext vg = nvgCreateContext();
47 ---
49 $(WARNING You must use created context ONLY in that thread where you created it.
50 There is no way to "transfer" context between threads. Trying to do so
51 will lead to UB.)
53 $(WARNING Never issue any commands outside of [beginFrame]/[endFrame]. Trying to
54 do so will lead to UB.)
57 Drawing shapes with NanoVega
58 ============================
60 Drawing a simple shape using NanoVega consists of four steps:
61 $(LIST
62 * begin a new shape,
63 * define the path to draw,
64 * set fill or stroke,
65 * and finally fill or stroke the path.
68 ---
69 vg.beginPath();
70 vg.rect(100, 100, 120, 30);
71 vg.fillColor(nvgRGBA(255, 192, 0, 255));
72 vg.fill();
73 ---
75 Calling [beginPath] will clear any existing paths and start drawing from blank slate.
76 There are number of number of functions to define the path to draw, such as rectangle,
77 rounded rectangle and ellipse, or you can use the common moveTo, lineTo, bezierTo and
78 arcTo API to compose the paths step by step.
81 Understanding Composite Paths
82 =============================
84 Because of the way the rendering backend is built in NanoVega, drawing a composite path,
85 that is path consisting from multiple paths defining holes and fills, is a bit more
86 involved. NanoVega uses non-zero filling rule and by default, and paths are wound in counter
87 clockwise order. Keep that in mind when drawing using the low level draw API. In order to
88 wind one of the predefined shapes as a hole, you should call `pathWinding(NVGSolidity.Hole)`,
89 or `pathWinding(NVGSolidity.Solid)` $(B after) defining the path.
91 ---
92 vg.beginPath();
93 vg.rect(100, 100, 120, 30);
94 vg.circle(120, 120, 5);
95 vg.pathWinding(NVGSolidity.Hole); // mark circle as a hole
96 vg.fillColor(nvgRGBA(255, 192, 0, 255));
97 vg.fill();
98 ---
101 Rendering is wrong, what to do?
102 ===============================
104 $(LIST
105 * make sure you have created NanoVega context using [nvgCreateContext] call
106 * make sure you have initialised OpenGL with $(B stencil buffer)
107 * make sure you have cleared stencil buffer
108 * make sure all rendering calls happen between [beginFrame] and [endFrame]
109 * to enable more checks for OpenGL errors, add `NVGContextFlag.Debug` flag to [nvgCreateContext]
113 OpenGL state touched by the backend
114 ===================================
116 The OpenGL back-end touches following states:
118 When textures are uploaded or updated, the following pixel store is set to defaults:
119 `GL_UNPACK_ALIGNMENT`, `GL_UNPACK_ROW_LENGTH`, `GL_UNPACK_SKIP_PIXELS`, `GL_UNPACK_SKIP_ROWS`.
120 Texture binding is also affected. Texture updates can happen when the user loads images,
121 or when new font glyphs are added. Glyphs are added as needed between calls to [beginFrame]
122 and [endFrame].
124 The data for the whole frame is buffered and flushed in [endFrame].
125 The following code illustrates the OpenGL state touched by the rendering code:
128 glUseProgram(prog);
129 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
130 glEnable(GL_CULL_FACE);
131 glCullFace(GL_BACK);
132 glFrontFace(GL_CCW);
133 glEnable(GL_BLEND);
134 glDisable(GL_DEPTH_TEST);
135 glDisable(GL_SCISSOR_TEST);
136 glDisable(GL_COLOR_LOGIC_OP);
137 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
138 glStencilMask(0xffffffff);
139 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
140 glStencilFunc(GL_ALWAYS, 0, 0xffffffff);
141 glActiveTexture(GL_TEXTURE1);
142 glActiveTexture(GL_TEXTURE0);
143 glBindBuffer(GL_UNIFORM_BUFFER, buf);
144 glBindVertexArray(arr);
145 glBindBuffer(GL_ARRAY_BUFFER, buf);
146 glBindTexture(GL_TEXTURE_2D, tex);
147 glUniformBlockBinding(... , GLNVG_FRAG_BINDING);
150 Symbol_groups:
152 context_management =
153 ## Context Management
155 Functions to create and destory NanoVega context.
157 frame_management =
158 ## Frame Management
160 To start drawing with NanoVega context, you have to "begin frame", and then
161 "end frame" to flush your rendering commands to GPU.
163 composite_operation =
164 ## Composite Operation
166 The composite operations in NanoVega are modeled after HTML Canvas API, and
167 the blend func is based on OpenGL (see corresponding manuals for more info).
168 The colors in the blending state have premultiplied alpha.
170 color_utils =
171 ## Color Utils
173 Colors in NanoVega are stored as ARGB. Zero alpha means "transparent color".
175 matrices =
176 ## Matrices and Transformations
178 The paths, gradients, patterns and scissor region are transformed by an transformation
179 matrix at the time when they are passed to the API.
180 The current transformation matrix is an affine matrix:
182 ----------------------
183 [sx kx tx]
184 [ky sy ty]
185 [ 0 0 1]
186 ----------------------
188 Where: (sx, sy) define scaling, (kx, ky) skewing, and (tx, ty) translation.
189 The last row is assumed to be (0, 0, 1) and is not stored.
191 Apart from [resetTransform], each transformation function first creates
192 specific transformation matrix and pre-multiplies the current transformation by it.
194 Current coordinate system (transformation) can be saved and restored using [save] and [restore].
196 The following functions can be used to make calculations on 2x3 transformation matrices.
197 A 2x3 matrix is represented as float[6].
199 state_handling =
200 ## State Handling
202 NanoVega contains state which represents how paths will be rendered.
203 The state contains transform, fill and stroke styles, text and font styles,
204 and scissor clipping.
206 render_styles =
207 ## Render Styles
209 Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern.
210 Solid color is simply defined as a color value, different kinds of paints can be created
211 using [linearGradient], [boxGradient], [radialGradient] and [imagePattern].
213 Current render style can be saved and restored using [save] and [restore].
215 Note that if you want "almost perfect" pixel rendering, you should set aspect ratio to 1,
216 and use `integerCoord+0.5f` as pixel coordinates.
218 render_transformations =
219 ## Render Transformations
221 Transformation matrix management for the current rendering style. Transformations are applied in
222 backwards order. I.e. if you first translate, and then rotate, your path will be rotated around
223 it's origin, and then translated to the destination point.
225 scissoring =
226 ## Scissoring
228 Scissoring allows you to clip the rendering into a rectangle. This is useful for various
229 user interface cases like rendering a text edit or a timeline.
231 images =
232 ## Images
234 NanoVega allows you to load image files in various formats (if arsd loaders are in place) to be used for rendering.
235 In addition you can upload your own image.
236 The parameter imageFlagsList is a list of flags defined in [NVGImageFlag].
238 If you will use your image as fill pattern, it will be scaled by default. To make it repeat, pass
239 [NVGImageFlag.RepeatX] and [NVGImageFlag.RepeatY] flags to image creation function respectively.
241 paints =
242 ## Paints
244 NanoVega supports four types of paints: linear gradient, box gradient, radial gradient and image pattern.
245 These can be used as paints for strokes and fills.
247 gpu_affine =
248 ## Render-Time Affine Transformations
250 It is possible to set affine transformation matrix for GPU. That matrix will
251 be applied by the shader code. This can be used to quickly translate and rotate
252 saved paths. Call this $(B only) between [beginFrame] and [endFrame].
254 Note that [beginFrame] resets this matrix to identity one.
256 $(WARNING Don't use this for scaling or skewing, or your image will be heavily distorted!)
258 paths =
259 ## Paths
261 Drawing a new shape starts with [beginPath], it clears all the currently defined paths.
262 Then you define one or more paths and sub-paths which describe the shape. The are functions
263 to draw common shapes like rectangles and circles, and lower level step-by-step functions,
264 which allow to define a path curve by curve.
266 NanoVega uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise
267 winding and holes should have counter clockwise order. To specify winding of a path you can
268 call [pathWinding]. This is useful especially for the common shapes, which are drawn CCW.
270 Finally you can fill the path using current fill style by calling [fill], and stroke it
271 with current stroke style by calling [stroke].
273 The curve segments and sub-paths are transformed by the current transform.
275 picking_api =
276 ## Picking API
278 This is picking API that works directly on paths, without rasterizing them first.
280 [beginFrame] resets picking state. Then you can create paths as usual, but
281 there is a possibility to perform hit checks $(B before) rasterizing a path.
282 Call either id assigning functions ([currFillHitId]/[currStrokeHitId]), or
283 immediate hit test functions ([hitTestCurrFill]/[hitTestCurrStroke])
284 before rasterizing (i.e. calling [fill] or [stroke]) to perform hover
285 effects, for example.
287 Also note that picking API is ignoring GPU affine transformation matrix.
288 You can "untransform" picking coordinates before checking with [gpuUntransformPoint].
290 $(WARNING Picking API completely ignores clipping. If you want to check for
291 clip regions, you have to manually register them as fill/stroke paths,
292 and perform the necessary logic. See [hitTestForId] function.)
294 clipping =
295 ## Clipping with paths
297 If scissoring is not enough for you, you can clip rendering with arbitrary path,
298 or with combination of paths. Clip region is saved by [save] and restored by
299 [restore] NanoVega functions. You can combine clip paths with various logic
300 operations, see [NVGClipMode].
302 Note that both [clip] and [clipStroke] are ignoring scissoring (i.e. clip mask
303 is created as if there was no scissor set). Actual rendering is affected by
304 scissors, though.
306 text_api =
307 ## Text
309 NanoVega allows you to load .ttf files and use the font to render text.
310 You have to load some font, and set proper font size before doing anything
311 with text, as there is no "default" font provided by NanoVega. Also, don't
312 forget to check return value of `createFont()`, 'cause NanoVega won't fail
313 if it cannot load font, it will silently try to render nothing.
315 The appearance of the text can be defined by setting the current text style
316 and by specifying the fill color. Common text and font settings such as
317 font size, letter spacing and text align are supported. Font blur allows you
318 to create simple text effects such as drop shadows.
320 At render time the font face can be set based on the font handles or name.
322 Font measure functions return values in local space, the calculations are
323 carried in the same resolution as the final rendering. This is done because
324 the text glyph positions are snapped to the nearest pixels sharp rendering.
326 The local space means that values are not rotated or scale as per the current
327 transformation. For example if you set font size to 12, which would mean that
328 line height is 16, then regardless of the current scaling and rotation, the
329 returned line height is always 16. Some measures may vary because of the scaling
330 since aforementioned pixel snapping.
332 While this may sound a little odd, the setup allows you to always render the
333 same way regardless of scaling. I.e. following works regardless of scaling:
335 ----------------------
336 string txt = "Text me up.";
337 vg.textBounds(x, y, txt, bounds);
338 vg.beginPath();
339 vg.roundedRect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1], 6);
340 vg.fill();
341 ----------------------
343 Note: currently only solid color fill is supported for text.
345 font_stash =
346 ## Low-Level Font Engine (FontStash)
348 FontStash is used to load fonts, to manage font atlases, and to get various text metrics.
349 You don't need any graphics context to use FontStash, so you can do things like text
350 layouting outside of your rendering code. Loaded fonts are refcounted, so it is cheap
351 to create new FontStash, copy fonts from NanoVega context into it, and use that new
352 FontStash to do some UI layouting, for example. Also note that you can get text metrics
353 without creating glyph bitmaps (by using [FONSTextBoundsIterator], for example); this way
354 you don't need to waste CPU and memory resources to render unneeded images into font atlas,
355 and you can layout alot of text very fast.
357 Note that "FontStash" is abbrevated as "FONS". So when you see some API that contains
358 word "fons" in it, this is not a typo, and it should not read "font" intead.
360 TODO for Ketmar: write some nice example code here, and finish documenting FontStash API.
362 module iv.nanovega.nanovega;
363 private:
365 /* this will use "em to pixels" instead of font height to calculate font sizes.
366 it is more corrent (and consistent with that other software does), but font
367 sizes must be reduced, which may ruin your app.
369 //version = nanovg_use_em_font_sizes;
371 version(aliced) {
372 import iv.meta;
373 import iv.vfs;
374 } else {
375 private alias usize = size_t;
376 // i fear phobos!
377 private template Unqual(T) {
378 static if (is(T U == immutable U)) alias Unqual = U;
379 else static if (is(T U == shared inout const U)) alias Unqual = U;
380 else static if (is(T U == shared inout U)) alias Unqual = U;
381 else static if (is(T U == shared const U)) alias Unqual = U;
382 else static if (is(T U == shared U)) alias Unqual = U;
383 else static if (is(T U == inout const U)) alias Unqual = U;
384 else static if (is(T U == inout U)) alias Unqual = U;
385 else static if (is(T U == const U)) alias Unqual = U;
386 else alias Unqual = T;
388 private template isAnyCharType(T, bool unqual=false) {
389 static if (unqual) private alias UT = Unqual!T; else private alias UT = T;
390 enum isAnyCharType = is(UT == char) || is(UT == wchar) || is(UT == dchar);
392 private template isWideCharType(T, bool unqual=false) {
393 static if (unqual) private alias UT = Unqual!T; else private alias UT = T;
394 enum isWideCharType = is(UT == wchar) || is(UT == dchar);
397 version(nanovg_disable_vfs) {
398 enum NanoVegaHasIVVFS = false;
399 } else {
400 static if (is(typeof((){import iv.vfs;}))) {
401 enum NanoVegaHasIVVFS = true;
402 import iv.vfs;
403 } else {
404 enum NanoVegaHasIVVFS = false;
408 // ////////////////////////////////////////////////////////////////////////// //
409 // engine
410 // ////////////////////////////////////////////////////////////////////////// //
411 import core.stdc.stdlib : malloc, realloc, free;
412 import core.stdc.string : memset, memcpy, strlen;
413 import std.math : PI;
415 //version = nanovg_force_stb_ttf;
417 version(Posix) {
418 version = nanovg_use_freetype;
419 } else {
420 version = nanovg_disable_fontconfig;
422 version(aliced) {
423 version = nanovg_default_no_font_aa;
424 version = nanovg_builtin_fontconfig_bindings;
425 version = nanovg_builtin_freetype_bindings;
426 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings
427 } else {
428 version = nanovg_builtin_fontconfig_bindings;
429 version = nanovg_builtin_freetype_bindings;
430 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings
433 version(nanovg_disable_fontconfig) {
434 public enum NanoVegaHasFontConfig = false;
435 } else {
436 public enum NanoVegaHasFontConfig = true;
437 version(nanovg_builtin_fontconfig_bindings) {} else import iv.fontconfig;
440 //version = nanovg_bench_flatten;
443 Annotation to indicate it is compatible with [arsd.script]
445 package(iv) enum scriptable = "arsd_jsvar_compatible";
447 public:
448 alias NVG_PI = PI;
450 enum NanoVegaHasArsdColor = (is(typeof((){ import arsd.color; })));
451 enum NanoVegaHasArsdImage = (is(typeof((){ import arsd.color; import arsd.image; })));
453 static if (NanoVegaHasArsdColor) private import arsd.color;
454 static if (NanoVegaHasArsdImage) {
455 private import arsd.image;
456 } else {
457 void stbi_set_unpremultiply_on_load (int flag_true_if_should_unpremultiply) {}
458 void stbi_convert_iphone_png_to_rgb (int flag_true_if_should_convert) {}
459 ubyte* stbi_load (const(char)* filename, int* x, int* y, int* comp, int req_comp) { return null; }
460 ubyte* stbi_load_from_memory (const(void)* buffer, int len, int* x, int* y, int* comp, int req_comp) { return null; }
461 void stbi_image_free (void* retval_from_stbi_load) {}
464 version(nanovg_default_no_font_aa) {
465 __gshared bool NVG_INVERT_FONT_AA = false;
466 } else {
467 __gshared bool NVG_INVERT_FONT_AA = true;
471 /// this is branchless for ints on x86, and even for longs on x86_64
472 public ubyte nvgClampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) {
473 static if (__VERSION__ > 2067) pragma(inline, true);
474 static if (T.sizeof == 2 || T.sizeof == 4) {
475 static if (__traits(isUnsigned, T)) {
476 return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24)));
477 } else {
478 n &= -cast(int)(n >= 0);
479 return cast(ubyte)(n|((255-cast(int)n)>>31));
481 } else static if (T.sizeof == 1) {
482 static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?");
483 return cast(ubyte)n;
484 } else static if (T.sizeof == 8) {
485 static if (__traits(isUnsigned, T)) {
486 return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56)));
487 } else {
488 n &= -cast(long)(n >= 0);
489 return cast(ubyte)(n|((255-cast(long)n)>>63));
491 } else {
492 static assert(false, "clampToByte: integer too big");
497 /// NanoVega RGBA color
498 /// Group: color_utils
499 public align(1) struct NVGColor {
500 align(1):
501 public:
502 float[4] rgba = 0; /// default color is transparent (a=1 is opaque)
504 public:
505 @property string toString () const @safe { import std.string : format; return "NVGColor(%s,%s,%s,%s)".format(r, g, b, a); }
507 public:
508 enum transparent = NVGColor(0.0f, 0.0f, 0.0f, 0.0f);
509 enum k8orange = NVGColor(1.0f, 0.5f, 0.0f, 1.0f);
511 enum aliceblue = NVGColor(240, 248, 255);
512 enum antiquewhite = NVGColor(250, 235, 215);
513 enum aqua = NVGColor(0, 255, 255);
514 enum aquamarine = NVGColor(127, 255, 212);
515 enum azure = NVGColor(240, 255, 255);
516 enum beige = NVGColor(245, 245, 220);
517 enum bisque = NVGColor(255, 228, 196);
518 enum black = NVGColor(0, 0, 0); // basic color
519 enum blanchedalmond = NVGColor(255, 235, 205);
520 enum blue = NVGColor(0, 0, 255); // basic color
521 enum blueviolet = NVGColor(138, 43, 226);
522 enum brown = NVGColor(165, 42, 42);
523 enum burlywood = NVGColor(222, 184, 135);
524 enum cadetblue = NVGColor(95, 158, 160);
525 enum chartreuse = NVGColor(127, 255, 0);
526 enum chocolate = NVGColor(210, 105, 30);
527 enum coral = NVGColor(255, 127, 80);
528 enum cornflowerblue = NVGColor(100, 149, 237);
529 enum cornsilk = NVGColor(255, 248, 220);
530 enum crimson = NVGColor(220, 20, 60);
531 enum cyan = NVGColor(0, 255, 255); // basic color
532 enum darkblue = NVGColor(0, 0, 139);
533 enum darkcyan = NVGColor(0, 139, 139);
534 enum darkgoldenrod = NVGColor(184, 134, 11);
535 enum darkgray = NVGColor(169, 169, 169);
536 enum darkgreen = NVGColor(0, 100, 0);
537 enum darkgrey = NVGColor(169, 169, 169);
538 enum darkkhaki = NVGColor(189, 183, 107);
539 enum darkmagenta = NVGColor(139, 0, 139);
540 enum darkolivegreen = NVGColor(85, 107, 47);
541 enum darkorange = NVGColor(255, 140, 0);
542 enum darkorchid = NVGColor(153, 50, 204);
543 enum darkred = NVGColor(139, 0, 0);
544 enum darksalmon = NVGColor(233, 150, 122);
545 enum darkseagreen = NVGColor(143, 188, 143);
546 enum darkslateblue = NVGColor(72, 61, 139);
547 enum darkslategray = NVGColor(47, 79, 79);
548 enum darkslategrey = NVGColor(47, 79, 79);
549 enum darkturquoise = NVGColor(0, 206, 209);
550 enum darkviolet = NVGColor(148, 0, 211);
551 enum deeppink = NVGColor(255, 20, 147);
552 enum deepskyblue = NVGColor(0, 191, 255);
553 enum dimgray = NVGColor(105, 105, 105);
554 enum dimgrey = NVGColor(105, 105, 105);
555 enum dodgerblue = NVGColor(30, 144, 255);
556 enum firebrick = NVGColor(178, 34, 34);
557 enum floralwhite = NVGColor(255, 250, 240);
558 enum forestgreen = NVGColor(34, 139, 34);
559 enum fuchsia = NVGColor(255, 0, 255);
560 enum gainsboro = NVGColor(220, 220, 220);
561 enum ghostwhite = NVGColor(248, 248, 255);
562 enum gold = NVGColor(255, 215, 0);
563 enum goldenrod = NVGColor(218, 165, 32);
564 enum gray = NVGColor(128, 128, 128); // basic color
565 enum green = NVGColor(0, 128, 0); // basic color
566 enum greenyellow = NVGColor(173, 255, 47);
567 enum grey = NVGColor(128, 128, 128); // basic color
568 enum honeydew = NVGColor(240, 255, 240);
569 enum hotpink = NVGColor(255, 105, 180);
570 enum indianred = NVGColor(205, 92, 92);
571 enum indigo = NVGColor(75, 0, 130);
572 enum ivory = NVGColor(255, 255, 240);
573 enum khaki = NVGColor(240, 230, 140);
574 enum lavender = NVGColor(230, 230, 250);
575 enum lavenderblush = NVGColor(255, 240, 245);
576 enum lawngreen = NVGColor(124, 252, 0);
577 enum lemonchiffon = NVGColor(255, 250, 205);
578 enum lightblue = NVGColor(173, 216, 230);
579 enum lightcoral = NVGColor(240, 128, 128);
580 enum lightcyan = NVGColor(224, 255, 255);
581 enum lightgoldenrodyellow = NVGColor(250, 250, 210);
582 enum lightgray = NVGColor(211, 211, 211);
583 enum lightgreen = NVGColor(144, 238, 144);
584 enum lightgrey = NVGColor(211, 211, 211);
585 enum lightpink = NVGColor(255, 182, 193);
586 enum lightsalmon = NVGColor(255, 160, 122);
587 enum lightseagreen = NVGColor(32, 178, 170);
588 enum lightskyblue = NVGColor(135, 206, 250);
589 enum lightslategray = NVGColor(119, 136, 153);
590 enum lightslategrey = NVGColor(119, 136, 153);
591 enum lightsteelblue = NVGColor(176, 196, 222);
592 enum lightyellow = NVGColor(255, 255, 224);
593 enum lime = NVGColor(0, 255, 0);
594 enum limegreen = NVGColor(50, 205, 50);
595 enum linen = NVGColor(250, 240, 230);
596 enum magenta = NVGColor(255, 0, 255); // basic color
597 enum maroon = NVGColor(128, 0, 0);
598 enum mediumaquamarine = NVGColor(102, 205, 170);
599 enum mediumblue = NVGColor(0, 0, 205);
600 enum mediumorchid = NVGColor(186, 85, 211);
601 enum mediumpurple = NVGColor(147, 112, 219);
602 enum mediumseagreen = NVGColor(60, 179, 113);
603 enum mediumslateblue = NVGColor(123, 104, 238);
604 enum mediumspringgreen = NVGColor(0, 250, 154);
605 enum mediumturquoise = NVGColor(72, 209, 204);
606 enum mediumvioletred = NVGColor(199, 21, 133);
607 enum midnightblue = NVGColor(25, 25, 112);
608 enum mintcream = NVGColor(245, 255, 250);
609 enum mistyrose = NVGColor(255, 228, 225);
610 enum moccasin = NVGColor(255, 228, 181);
611 enum navajowhite = NVGColor(255, 222, 173);
612 enum navy = NVGColor(0, 0, 128);
613 enum oldlace = NVGColor(253, 245, 230);
614 enum olive = NVGColor(128, 128, 0);
615 enum olivedrab = NVGColor(107, 142, 35);
616 enum orange = NVGColor(255, 165, 0);
617 enum orangered = NVGColor(255, 69, 0);
618 enum orchid = NVGColor(218, 112, 214);
619 enum palegoldenrod = NVGColor(238, 232, 170);
620 enum palegreen = NVGColor(152, 251, 152);
621 enum paleturquoise = NVGColor(175, 238, 238);
622 enum palevioletred = NVGColor(219, 112, 147);
623 enum papayawhip = NVGColor(255, 239, 213);
624 enum peachpuff = NVGColor(255, 218, 185);
625 enum peru = NVGColor(205, 133, 63);
626 enum pink = NVGColor(255, 192, 203);
627 enum plum = NVGColor(221, 160, 221);
628 enum powderblue = NVGColor(176, 224, 230);
629 enum purple = NVGColor(128, 0, 128);
630 enum red = NVGColor(255, 0, 0); // basic color
631 enum rosybrown = NVGColor(188, 143, 143);
632 enum royalblue = NVGColor(65, 105, 225);
633 enum saddlebrown = NVGColor(139, 69, 19);
634 enum salmon = NVGColor(250, 128, 114);
635 enum sandybrown = NVGColor(244, 164, 96);
636 enum seagreen = NVGColor(46, 139, 87);
637 enum seashell = NVGColor(255, 245, 238);
638 enum sienna = NVGColor(160, 82, 45);
639 enum silver = NVGColor(192, 192, 192);
640 enum skyblue = NVGColor(135, 206, 235);
641 enum slateblue = NVGColor(106, 90, 205);
642 enum slategray = NVGColor(112, 128, 144);
643 enum slategrey = NVGColor(112, 128, 144);
644 enum snow = NVGColor(255, 250, 250);
645 enum springgreen = NVGColor(0, 255, 127);
646 enum steelblue = NVGColor(70, 130, 180);
647 enum tan = NVGColor(210, 180, 140);
648 enum teal = NVGColor(0, 128, 128);
649 enum thistle = NVGColor(216, 191, 216);
650 enum tomato = NVGColor(255, 99, 71);
651 enum turquoise = NVGColor(64, 224, 208);
652 enum violet = NVGColor(238, 130, 238);
653 enum wheat = NVGColor(245, 222, 179);
654 enum white = NVGColor(255, 255, 255); // basic color
655 enum whitesmoke = NVGColor(245, 245, 245);
656 enum yellow = NVGColor(255, 255, 0); // basic color
657 enum yellowgreen = NVGColor(154, 205, 50);
659 nothrow @safe @nogc:
660 public:
662 this (ubyte ar, ubyte ag, ubyte ab, ubyte aa=255) pure {
663 pragma(inline, true);
664 r = ar/255.0f;
665 g = ag/255.0f;
666 b = ab/255.0f;
667 a = aa/255.0f;
671 this (float ar, float ag, float ab, float aa=1.0f) pure {
672 pragma(inline, true);
673 r = ar;
674 g = ag;
675 b = ab;
676 a = aa;
679 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
680 this (uint c) pure {
681 pragma(inline, true);
682 r = (c&0xff)/255.0f;
683 g = ((c>>8)&0xff)/255.0f;
684 b = ((c>>16)&0xff)/255.0f;
685 a = ((c>>24)&0xff)/255.0f;
688 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb"
689 this (const(char)[] srgb) {
690 static int c2d (char ch) pure nothrow @safe @nogc {
691 pragma(inline, true);
692 return
693 ch >= '0' && ch <= '9' ? ch-'0' :
694 ch >= 'A' && ch <= 'F' ? ch-'A'+10 :
695 ch >= 'a' && ch <= 'f' ? ch-'a'+10 :
698 int[8] digs;
699 int dc = -1;
700 foreach (immutable char ch; srgb) {
701 if (ch <= ' ') continue;
702 if (ch == '#') {
703 if (dc != -1) { dc = -1; break; }
704 dc = 0;
705 } else {
706 if (dc >= digs.length) { dc = -1; break; }
707 if ((digs[dc++] = c2d(ch)) < 0) { dc = -1; break; }
710 switch (dc) {
711 case 3: // rgb
712 a = 1.0f;
713 r = digs[0]/15.0f;
714 g = digs[1]/15.0f;
715 b = digs[2]/15.0f;
716 break;
717 case 4: // argb
718 a = digs[0]/15.0f;
719 r = digs[1]/15.0f;
720 g = digs[2]/15.0f;
721 b = digs[3]/15.0f;
722 break;
723 case 6: // rrggbb
724 a = 1.0f;
725 r = (digs[0]*16+digs[1])/255.0f;
726 g = (digs[2]*16+digs[3])/255.0f;
727 b = (digs[4]*16+digs[5])/255.0f;
728 break;
729 case 8: // aarrggbb
730 a = (digs[0]*16+digs[1])/255.0f;
731 r = (digs[2]*16+digs[3])/255.0f;
732 g = (digs[4]*16+digs[5])/255.0f;
733 b = (digs[6]*16+digs[7])/255.0f;
734 break;
735 default:
736 break;
740 /// Is this color completely opaque?
741 @property bool isOpaque () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] >= 1.0f); }
742 /// Is this color completely transparent?
743 @property bool isTransparent () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] <= 0.0f); }
745 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
746 @property uint asUint () const pure {
747 pragma(inline, true);
748 return
749 cast(uint)(r*255)|
750 (cast(uint)(g*255)<<8)|
751 (cast(uint)(b*255)<<16)|
752 (cast(uint)(a*255)<<24);
755 alias asUintABGR = asUint; /// Ditto.
757 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
758 static NVGColor fromUint (uint c) pure { pragma(inline, true); return NVGColor(c); }
760 alias fromUintABGR = fromUint; /// Ditto.
762 /// AARRGGBB
763 @property uint asUintARGB () const pure {
764 pragma(inline, true);
765 return
766 cast(uint)(b*255)|
767 (cast(uint)(g*255)<<8)|
768 (cast(uint)(r*255)<<16)|
769 (cast(uint)(a*255)<<24);
772 /// AARRGGBB
773 static NVGColor fromUintARGB (uint c) pure { pragma(inline, true); return NVGColor((c>>16)&0xff, (c>>8)&0xff, c&0xff, (c>>24)&0xff); }
775 @property ref inout(float) r () inout pure @trusted { pragma(inline, true); return rgba.ptr[0]; } ///
776 @property ref inout(float) g () inout pure @trusted { pragma(inline, true); return rgba.ptr[1]; } ///
777 @property ref inout(float) b () inout pure @trusted { pragma(inline, true); return rgba.ptr[2]; } ///
778 @property ref inout(float) a () inout pure @trusted { pragma(inline, true); return rgba.ptr[3]; } ///
780 ref NVGColor applyTint() (in auto ref NVGColor tint) nothrow @trusted @nogc {
781 if (tint.a == 0) return this;
782 foreach (immutable idx, ref float v; rgba[0..4]) {
783 v = nvg__clamp(v*tint.rgba.ptr[idx], 0.0f, 1.0f);
785 return this;
788 NVGHSL asHSL() (bool useWeightedLightness=false) const { pragma(inline, true); return NVGHSL.fromColor(this, useWeightedLightness); } ///
789 static fromHSL() (in auto ref NVGHSL hsl) { pragma(inline, true); return hsl.asColor; } ///
791 static if (NanoVegaHasArsdColor) {
792 Color toArsd () const { pragma(inline, true); return Color(cast(int)(r*255), cast(int)(g*255), cast(int)(b*255), cast(int)(a*255)); } ///
793 static NVGColor fromArsd (in Color c) { pragma(inline, true); return NVGColor(c.r, c.g, c.b, c.a); } ///
795 this (in Color c) {
796 version(aliced) pragma(inline, true);
797 r = c.r/255.0f;
798 g = c.g/255.0f;
799 b = c.b/255.0f;
800 a = c.a/255.0f;
806 /// NanoVega A-HSL color
807 /// Group: color_utils
808 public align(1) struct NVGHSL {
809 align(1):
810 float h=0, s=0, l=1, a=1; ///
812 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)); }
814 nothrow @safe @nogc:
815 public:
817 this (float ah, float as, float al, float aa=1) pure { pragma(inline, true); h = ah; s = as; l = al; a = aa; }
819 NVGColor asColor () const { pragma(inline, true); return nvgHSLA(h, s, l, a); } ///
821 // taken from Adam's arsd.color
822 /** Converts an RGB color into an HSL triplet.
823 * [useWeightedLightness] will try to get a better value for luminosity for the human eye,
824 * which is more sensitive to green than red and more to red than blue.
825 * If it is false, it just does average of the rgb. */
826 static NVGHSL fromColor() (in auto ref NVGColor c, bool useWeightedLightness=false) pure {
827 NVGHSL res;
828 res.a = c.a;
829 float r1 = c.r;
830 float g1 = c.g;
831 float b1 = c.b;
833 float maxColor = r1;
834 if (g1 > maxColor) maxColor = g1;
835 if (b1 > maxColor) maxColor = b1;
836 float minColor = r1;
837 if (g1 < minColor) minColor = g1;
838 if (b1 < minColor) minColor = b1;
840 res.l = (maxColor+minColor)/2;
841 if (useWeightedLightness) {
842 // the colors don't affect the eye equally
843 // this is a little more accurate than plain HSL numbers
844 res.l = 0.2126*r1+0.7152*g1+0.0722*b1;
846 if (maxColor != minColor) {
847 if (res.l < 0.5) {
848 res.s = (maxColor-minColor)/(maxColor+minColor);
849 } else {
850 res.s = (maxColor-minColor)/(2.0-maxColor-minColor);
852 if (r1 == maxColor) {
853 res.h = (g1-b1)/(maxColor-minColor);
854 } else if(g1 == maxColor) {
855 res.h = 2.0+(b1-r1)/(maxColor-minColor);
856 } else {
857 res.h = 4.0+(r1-g1)/(maxColor-minColor);
861 res.h = res.h*60;
862 if (res.h < 0) res.h += 360;
863 res.h /= 360;
865 return res;
870 //version = nanovega_debug_image_manager;
871 //version = nanovega_debug_image_manager_rc;
873 /** NanoVega image handle.
875 * This is refcounted struct, so you don't need to do anything special to free it once it is allocated.
877 * Group: images
879 struct NVGImage {
880 private:
881 NVGContext ctx;
882 int id; // backend image id
884 public:
886 this() (in auto ref NVGImage src) nothrow @trusted @nogc {
887 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); }
888 if (src.id > 0 && src.ctx !is null) {
889 ctx = cast(NVGContext)src.ctx;
890 id = src.id;
891 ctx.nvg__imageIncRef(id);
896 ~this () nothrow @trusted @nogc { version(aliced) pragma(inline, true); clear(); }
899 this (this) nothrow @trusted @nogc {
900 version(aliced) pragma(inline, true);
901 if (id > 0 && ctx !is null) {
902 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p postblit (imgid=%d)\n", &this, id); }
903 ctx.nvg__imageIncRef(id);
908 void opAssign() (in auto ref NVGImage src) nothrow @trusted @nogc {
909 if (src.id <= 0 || src.ctx is null) {
910 clear();
911 } else {
912 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); }
913 if (src.id > 0 && src.ctx !is null) (cast(NVGContext)src.ctx).nvg__imageIncRef(src.id);
914 if (id > 0 && ctx !is null) ctx.nvg__imageDecRef(id);
915 ctx = cast(NVGContext)src.ctx;
916 id = src.id;
920 /// Free this image.
921 void clear () nothrow @trusted @nogc {
922 if (id > 0 && ctx !is null) {
923 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p cleared (imgid=%d)\n", &this, id); }
924 ctx.nvg__imageDecRef(id);
926 id = 0;
927 ctx = null;
930 /// Is this image valid?
931 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (id > 0 && ctx.valid); }
933 /// Is the given image valid and comes from the same context?
934 @property bool isSameContext (const(NVGContext) actx) const pure nothrow @safe @nogc { pragma(inline, true); return (actx !is null && ctx is actx); }
936 /// Returns image width, or zero for invalid image.
937 int width () const nothrow @trusted @nogc {
938 int w = 0;
939 if (valid) {
940 int h = void;
941 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h);
943 return w;
946 /// Returns image height, or zero for invalid image.
947 int height () const nothrow @trusted @nogc {
948 int h = 0;
949 if (valid) {
950 int w = void;
951 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h);
953 return h;
958 /// Paint parameters for various fills. Don't change anything here!
959 /// Group: render_styles
960 public struct NVGPaint {
961 NVGMatrix xform;
962 float[2] extent = 0.0f;
963 float radius = 0.0f;
964 float feather = 0.0f;
965 NVGColor innerColor; /// this can be used to modulate images (fill/font)
966 NVGColor middleColor;
967 NVGColor outerColor;
968 float midp = -1; // middle stop for 3-color gradient
969 NVGImage image;
970 bool simpleColor; /// if `true`, only innerColor is used, and this is solid-color paint
972 this() (in auto ref NVGPaint p) nothrow @trusted @nogc {
973 xform = p.xform;
974 extent[] = p.extent[];
975 radius = p.radius;
976 feather = p.feather;
977 innerColor = p.innerColor;
978 middleColor = p.middleColor;
979 midp = p.midp;
980 outerColor = p.outerColor;
981 image = p.image;
982 simpleColor = p.simpleColor;
985 void opAssign() (in auto ref NVGPaint p) nothrow @trusted @nogc {
986 xform = p.xform;
987 extent[] = p.extent[];
988 radius = p.radius;
989 feather = p.feather;
990 innerColor = p.innerColor;
991 middleColor = p.middleColor;
992 midp = p.midp;
993 outerColor = p.outerColor;
994 image = p.image;
995 simpleColor = p.simpleColor;
998 void clear () nothrow @trusted @nogc {
999 version(aliced) pragma(inline, true);
1000 import core.stdc.string : memset;
1001 image.clear();
1002 memset(&this, 0, this.sizeof);
1003 simpleColor = true;
1007 /// Path winding.
1008 /// Group: paths
1009 public enum NVGWinding {
1010 CCW = 1, /// Winding for solid shapes
1011 CW = 2, /// Winding for holes
1014 /// Path solidity.
1015 /// Group: paths
1016 public enum NVGSolidity {
1017 Solid = 1, /// Solid shape (CCW winding).
1018 Hole = 2, /// Hole (CW winding).
1021 /// Line cap style.
1022 /// Group: render_styles
1023 public enum NVGLineCap {
1024 Butt, ///
1025 Round, ///
1026 Square, ///
1027 Bevel, ///
1028 Miter, ///
1031 /// Text align.
1032 /// Group: text_api
1033 public align(1) struct NVGTextAlign {
1034 align(1):
1035 /// Horizontal align.
1036 enum H : ubyte {
1037 Left = 0, /// Default, align text horizontally to left.
1038 Center = 1, /// Align text horizontally to center.
1039 Right = 2, /// Align text horizontally to right.
1042 /// Vertical align.
1043 enum V : ubyte {
1044 Baseline = 0, /// Default, align text vertically to baseline.
1045 Top = 1, /// Align text vertically to top.
1046 Middle = 2, /// Align text vertically to middle.
1047 Bottom = 3, /// Align text vertically to bottom.
1050 pure nothrow @safe @nogc:
1051 public:
1052 this (H h) { pragma(inline, true); value = h; } ///
1053 this (V v) { pragma(inline, true); value = cast(ubyte)(v<<4); } ///
1054 this (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1055 this (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1056 void reset () { pragma(inline, true); value = 0; } ///
1057 void reset (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1058 void reset (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1059 @property:
1060 bool left () const { pragma(inline, true); return ((value&0x0f) == H.Left); } ///
1061 void left (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Left : 0)); } ///
1062 bool center () const { pragma(inline, true); return ((value&0x0f) == H.Center); } ///
1063 void center (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Center : 0)); } ///
1064 bool right () const { pragma(inline, true); return ((value&0x0f) == H.Right); } ///
1065 void right (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Right : 0)); } ///
1067 bool baseline () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Baseline); } ///
1068 void baseline (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Baseline<<4 : 0)); } ///
1069 bool top () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Top); } ///
1070 void top (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Top<<4 : 0)); } ///
1071 bool middle () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Middle); } ///
1072 void middle (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Middle<<4 : 0)); } ///
1073 bool bottom () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Bottom); } ///
1074 void bottom (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Bottom<<4 : 0)); } ///
1076 H horizontal () const { pragma(inline, true); return cast(H)(value&0x0f); } ///
1077 void horizontal (H v) { pragma(inline, true); value = (value&0xf0)|v; } ///
1079 V vertical () const { pragma(inline, true); return cast(V)((value>>4)&0x0f); } ///
1080 void vertical (V v) { pragma(inline, true); value = (value&0x0f)|cast(ubyte)(v<<4); } ///
1082 private:
1083 ubyte value = 0; // low nibble: horizontal; high nibble: vertical
1086 /// Blending type.
1087 /// Group: composite_operation
1088 public enum NVGBlendFactor {
1089 Zero = 1<<0, ///
1090 One = 1<<1, ///
1091 SrcColor = 1<<2, ///
1092 OneMinusSrcColor = 1<<3, ///
1093 DstColor = 1<<4, ///
1094 OneMinusDstColor = 1<<5, ///
1095 SrcAlpha = 1<<6, ///
1096 OneMinusSrcAlpha = 1<<7, ///
1097 DstAlpha = 1<<8, ///
1098 OneMinusDstAlpha = 1<<9, ///
1099 SrcAlphaSaturate = 1<<10, ///
1102 /// Composite operation (HTML5-alike).
1103 /// Group: composite_operation
1104 public enum NVGCompositeOperation {
1105 SourceOver, ///
1106 SourceIn, ///
1107 SourceOut, ///
1108 SourceAtop, ///
1109 DestinationOver, ///
1110 DestinationIn, ///
1111 DestinationOut, ///
1112 DestinationAtop, ///
1113 Lighter, ///
1114 Copy, ///
1115 Xor, ///
1118 /// Composite operation state.
1119 /// Group: composite_operation
1120 public struct NVGCompositeOperationState {
1121 bool simple; /// `true`: use `glBlendFunc()` instead of `glBlendFuncSeparate()`
1122 NVGBlendFactor srcRGB; ///
1123 NVGBlendFactor dstRGB; ///
1124 NVGBlendFactor srcAlpha; ///
1125 NVGBlendFactor dstAlpha; ///
1128 /// Mask combining more
1129 /// Group: clipping
1130 public enum NVGClipMode {
1131 None, /// normal rendering (i.e. render path instead of modifying clip region)
1132 Union, /// old mask will be masked with the current one; this is the default mode for [clip]
1133 Or, /// new mask will be added to the current one (logical `OR` operation);
1134 Xor, /// new mask will be logically `XOR`ed with the current one
1135 Sub, /// "subtract" current path from mask
1136 Replace, /// replace current mask
1137 Add = Or, /// Synonym
1140 /// Glyph position info.
1141 /// Group: text_api
1142 public struct NVGGlyphPosition {
1143 usize strpos; /// Position of the glyph in the input string.
1144 float x; /// The x-coordinate of the logical glyph position.
1145 float minx, maxx; /// The bounds of the glyph shape.
1148 /// Text row storage.
1149 /// Group: text_api
1150 public struct NVGTextRow(CT) if (isAnyCharType!CT) {
1151 alias CharType = CT;
1152 const(CT)[] s;
1153 int start; /// Index in the input text where the row starts.
1154 int end; /// Index in the input text where the row ends (one past the last character).
1155 float width; /// Logical width of the row.
1156 float minx, maxx; /// Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending.
1157 /// Get rest of the string.
1158 @property const(CT)[] rest () const pure nothrow @trusted @nogc { pragma(inline, true); return (end <= s.length ? s[end..$] : null); }
1159 /// Get current row.
1160 @property const(CT)[] row () const pure nothrow @trusted @nogc { pragma(inline, true); return s[start..end]; }
1161 @property const(CT)[] string () const pure nothrow @trusted @nogc { pragma(inline, true); return s; }
1162 @property void string(CT) (const(CT)[] v) pure nothrow @trusted @nogc { pragma(inline, true); s = v; }
1165 /// Image creation flags.
1166 /// Group: images
1167 public enum NVGImageFlag : uint {
1168 None = 0, /// Nothing special.
1169 GenerateMipmaps = 1<<0, /// Generate mipmaps during creation of the image.
1170 RepeatX = 1<<1, /// Repeat image in X direction.
1171 RepeatY = 1<<2, /// Repeat image in Y direction.
1172 FlipY = 1<<3, /// Flips (inverses) image in Y direction when rendered.
1173 Premultiplied = 1<<4, /// Image data has premultiplied alpha.
1174 NoFiltering = 1<<8, /// use GL_NEAREST instead of GL_LINEAR
1175 Nearest = NoFiltering, /// compatibility with original NanoVG
1176 NoDelete = 1<<16,/// Do not delete GL texture handle.
1179 alias NVGImageFlags = NVGImageFlag; /// Backwards compatibility for [NVGImageFlag].
1182 // ////////////////////////////////////////////////////////////////////////// //
1183 private:
1185 static T* xdup(T) (const(T)* ptr, int count) nothrow @trusted @nogc {
1186 import core.stdc.stdlib : malloc;
1187 import core.stdc.string : memcpy;
1188 if (count == 0) return null;
1189 T* res = cast(T*)malloc(T.sizeof*count);
1190 if (res is null) assert(0, "NanoVega: out of memory");
1191 memcpy(res, ptr, T.sizeof*count);
1192 return res;
1195 // Internal Render API
1196 enum NVGtexture {
1197 Alpha = 0x01,
1198 RGBA = 0x02,
1201 struct NVGscissor {
1202 NVGMatrix xform;
1203 float[2] extent = -1.0f;
1206 /// General NanoVega vertex struct. Contains geometry coordinates, and (sometimes unused) texture coordinates.
1207 public struct NVGVertex {
1208 float x, y, u, v;
1211 struct NVGpath {
1212 int first;
1213 int count;
1214 bool closed;
1215 int nbevel;
1216 NVGVertex* fill;
1217 int nfill;
1218 NVGVertex* stroke;
1219 int nstroke;
1220 NVGWinding mWinding;
1221 bool convex;
1222 bool cloned;
1224 @disable this (this); // no copies
1225 void opAssign() (in auto ref NVGpath a) { static assert(0, "no copies!"); }
1227 void clear () nothrow @trusted @nogc {
1228 import core.stdc.stdlib : free;
1229 import core.stdc.string : memset;
1230 if (cloned) {
1231 if (stroke !is null && stroke !is fill) free(stroke);
1232 if (fill !is null) free(fill);
1234 memset(&this, 0, this.sizeof);
1237 // won't clear current path
1238 void copyFrom (const NVGpath* src) nothrow @trusted @nogc {
1239 import core.stdc.string : memcpy;
1240 assert(src !is null);
1241 memcpy(&this, src, NVGpath.sizeof);
1242 this.fill = xdup(src.fill, src.nfill);
1243 if (src.stroke is src.fill) {
1244 this.stroke = this.fill;
1245 } else {
1246 this.stroke = xdup(src.stroke, src.nstroke);
1248 this.cloned = true;
1251 public @property const(NVGVertex)[] fillVertices () const pure nothrow @trusted @nogc {
1252 pragma(inline, true);
1253 return (nfill > 0 ? fill[0..nfill] : null);
1256 public @property const(NVGVertex)[] strokeVertices () const pure nothrow @trusted @nogc {
1257 pragma(inline, true);
1258 return (nstroke > 0 ? stroke[0..nstroke] : null);
1261 public @property NVGWinding winding () const pure nothrow @trusted @nogc { pragma(inline, true); return mWinding; }
1262 public @property bool complex () const pure nothrow @trusted @nogc { pragma(inline, true); return !convex; }
1266 struct NVGparams {
1267 void* userPtr;
1268 bool edgeAntiAlias;
1269 bool fontAA;
1270 bool function (void* uptr) nothrow @trusted @nogc renderCreate;
1271 int function (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc renderCreateTexture;
1272 bool function (void* uptr, int image) nothrow @trusted @nogc renderTextureIncRef;
1273 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
1274 bool function (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc renderUpdateTexture;
1275 bool function (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc renderGetTextureSize;
1276 void function (void* uptr, int width, int height) nothrow @trusted @nogc renderViewport; // called in [beginFrame]
1277 void function (void* uptr) nothrow @trusted @nogc renderCancel;
1278 void function (void* uptr) nothrow @trusted @nogc renderFlush;
1279 void function (void* uptr) nothrow @trusted @nogc renderPushClip; // backend should support stack of at least [NVG_MAX_STATES] elements
1280 void function (void* uptr) nothrow @trusted @nogc renderPopClip; // backend should support stack of at least [NVG_MAX_STATES] elements
1281 void function (void* uptr) nothrow @trusted @nogc renderResetClip; // reset current clip region to `non-clipped`
1282 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;
1283 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;
1284 void function (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts, float fringeWidth) nothrow @trusted @nogc renderTriangles;
1285 void function (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc renderSetAffine;
1286 void function (void* uptr) nothrow @trusted @nogc renderDelete;
1289 // ////////////////////////////////////////////////////////////////////////// //
1290 private:
1292 enum NVG_INIT_FONTIMAGE_SIZE = 512;
1293 enum NVG_MAX_FONTIMAGE_SIZE = 2048;
1294 enum NVG_MAX_FONTIMAGES = 4;
1296 enum NVG_INIT_COMMANDS_SIZE = 256;
1297 enum NVG_INIT_POINTS_SIZE = 128;
1298 enum NVG_INIT_PATHS_SIZE = 16;
1299 enum NVG_INIT_VERTS_SIZE = 256;
1300 enum NVG_MAX_STATES = 32;
1302 public enum NVG_KAPPA90 = 0.5522847493f; /// Length proportional to radius of a cubic bezier handle for 90deg arcs.
1303 enum NVG_MIN_FEATHER = 0.001f; // it should be greater than zero, 'cause it is used in shader for divisions
1305 enum Command {
1306 MoveTo = 0,
1307 LineTo = 1,
1308 BezierTo = 2,
1309 Close = 3,
1310 Winding = 4,
1313 enum PointFlag : int {
1314 Corner = 0x01,
1315 Left = 0x02,
1316 Bevel = 0x04,
1317 InnerBevelPR = 0x08,
1320 struct NVGstate {
1321 NVGCompositeOperationState compositeOperation;
1322 bool shapeAntiAlias = true;
1323 NVGPaint fill;
1324 NVGPaint stroke;
1325 float strokeWidth = 1.0f;
1326 float miterLimit = 10.0f;
1327 NVGLineCap lineJoin = NVGLineCap.Miter;
1328 NVGLineCap lineCap = NVGLineCap.Butt;
1329 float alpha = 1.0f;
1330 NVGMatrix xform;
1331 NVGscissor scissor;
1332 float fontSize = 16.0f;
1333 float letterSpacing = 0.0f;
1334 float lineHeight = 1.0f;
1335 float fontBlur = 0.0f;
1336 NVGTextAlign textAlign;
1337 int fontId = 0;
1338 bool evenOddMode = false; // use even-odd filling rule (required for some svgs); otherwise use non-zero fill
1339 // dashing
1340 enum MaxDashes = 32; // max 16 dashes
1341 float[MaxDashes] dashes;
1342 uint dashCount = 0;
1343 uint lastFlattenDashCount = 0;
1344 float dashStart = 0;
1345 float totalDashLen;
1346 bool firstDashIsGap = false;
1347 // dasher state for flattener
1348 bool dasherActive = false;
1350 void clearPaint () nothrow @trusted @nogc {
1351 fill.clear();
1352 stroke.clear();
1356 struct NVGpoint {
1357 float x, y;
1358 float dx, dy;
1359 float len;
1360 float dmx, dmy;
1361 ubyte flags;
1364 struct NVGpathCache {
1365 NVGpoint* points;
1366 int npoints;
1367 int cpoints;
1368 NVGpath* paths;
1369 int npaths;
1370 int cpaths;
1371 NVGVertex* verts;
1372 int nverts;
1373 int cverts;
1374 float[4] bounds;
1375 // this is required for saved paths
1376 bool strokeReady;
1377 bool fillReady;
1378 float strokeAlphaMul;
1379 float strokeWidth;
1380 float fringeWidth;
1381 bool evenOddMode;
1382 NVGClipMode clipmode;
1383 // non-saved path will not have this
1384 float* commands;
1385 int ncommands;
1387 @disable this (this); // no copies
1388 void opAssign() (in auto ref NVGpathCache a) { static assert(0, "no copies!"); }
1390 // won't clear current path
1391 void copyFrom (const NVGpathCache* src) nothrow @trusted @nogc {
1392 import core.stdc.stdlib : malloc;
1393 import core.stdc.string : memcpy, memset;
1394 assert(src !is null);
1395 memcpy(&this, src, NVGpathCache.sizeof);
1396 this.points = xdup(src.points, src.npoints);
1397 this.cpoints = src.npoints;
1398 this.verts = xdup(src.verts, src.nverts);
1399 this.cverts = src.nverts;
1400 this.commands = xdup(src.commands, src.ncommands);
1401 if (src.npaths > 0) {
1402 this.paths = cast(NVGpath*)malloc(src.npaths*NVGpath.sizeof);
1403 memset(this.paths, 0, npaths*NVGpath.sizeof);
1404 foreach (immutable pidx; 0..npaths) this.paths[pidx].copyFrom(&src.paths[pidx]);
1405 this.cpaths = src.npaths;
1406 } else {
1407 this.npaths = this.cpaths = 0;
1411 void clear () nothrow @trusted @nogc {
1412 import core.stdc.stdlib : free;
1413 import core.stdc.string : memset;
1414 if (paths !is null) {
1415 foreach (ref p; paths[0..npaths]) p.clear();
1416 free(paths);
1418 if (points !is null) free(points);
1419 if (verts !is null) free(verts);
1420 if (commands !is null) free(commands);
1421 memset(&this, 0, this.sizeof);
1425 /// Pointer to opaque NanoVega context structure.
1426 /// Group: context_management
1427 public alias NVGContext = NVGcontextinternal*;
1429 /// FontStash context
1430 /// Group: font_stash
1431 public alias FONSContext = FONScontextInternal*;
1433 /// Returns FontStash context of the given NanoVega context.
1434 /// Group: font_stash
1435 public FONSContext fonsContext (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.fs : null); }
1437 /// Returns scale that should be applied to FontStash parameters due to matrix transformations on the context (or 1)
1438 /// Group: font_stash
1439 public float fonsScale (NVGContext ctx) /*pure*/ nothrow @trusted @nogc {
1440 pragma(inline, true);
1441 return (ctx !is null && ctx.contextAlive && ctx.nstates > 0 ? nvg__getFontScale(&ctx.states.ptr[ctx.nstates-1])*ctx.devicePxRatio : 1);
1444 /// Setup FontStash from the given NanoVega context font parameters. Note that this will apply transformation scale too.
1445 /// Returns `false` if FontStash or NanoVega context is not active.
1446 /// Group: font_stash
1447 public bool setupFonsFrom (FONSContext stash, NVGContext ctx) nothrow @trusted @nogc {
1448 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false;
1449 NVGstate* state = nvg__getState(ctx);
1450 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
1451 stash.size = state.fontSize*scale;
1452 stash.spacing = state.letterSpacing*scale;
1453 stash.blur = state.fontBlur*scale;
1454 stash.textAlign = state.textAlign;
1455 stash.fontId = state.fontId;
1456 return true;
1459 /// Setup NanoVega context font parameters from the given FontStash. Note that NanoVega can apply transformation scale later.
1460 /// Returns `false` if FontStash or NanoVega context is not active.
1461 /// Group: font_stash
1462 public bool setupCtxFrom (NVGContext ctx, FONSContext stash) nothrow @trusted @nogc {
1463 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false;
1464 NVGstate* state = nvg__getState(ctx);
1465 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
1466 state.fontSize = stash.size;
1467 state.letterSpacing = stash.spacing;
1468 state.fontBlur = stash.blur;
1469 state.textAlign = stash.textAlign;
1470 state.fontId = stash.fontId;
1471 return true;
1474 /** Bezier curve rasterizer.
1476 * De Casteljau Bezier rasterizer is faster, but currently rasterizing curves with cusps sligtly wrong.
1477 * It doesn't really matter in practice.
1479 * AFD tesselator is somewhat slower, but does cusps better.
1481 * McSeem rasterizer should have the best quality, bit it is the slowest method. Basically, you will
1482 * never notice any visial difference (and this code is not really debugged), so you probably should
1483 * not use it. It is there for further experiments.
1485 public enum NVGTesselation {
1486 DeCasteljau, /// default: standard well-known tesselation algorithm
1487 AFD, /// adaptive forward differencing
1488 DeCasteljauMcSeem, /// standard well-known tesselation algorithm, with improvements from Maxim Shemanarev; slowest one, but should give best results
1491 /// Default tesselator for Bezier curves.
1492 public __gshared NVGTesselation NVG_DEFAULT_TESSELATOR = NVGTesselation.DeCasteljau;
1495 // some public info
1497 /// valid only inside [beginFrame]/[endFrame]
1498 /// Group: context_management
1499 public int width (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mWidth : 0); }
1501 /// valid only inside [beginFrame]/[endFrame]
1502 /// Group: context_management
1503 public int height (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mHeight : 0); }
1505 /// valid only inside [beginFrame]/[endFrame]
1506 /// Group: context_management
1507 public float devicePixelRatio (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.devicePxRatio : float.nan); }
1509 /// Returns `true` if [beginFrame] was called, and neither [endFrame], nor [cancelFrame] were.
1510 /// Group: context_management
1511 public bool inFrame (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.nstates > 0 : false); }
1513 // path autoregistration
1515 /// [pickid] to stop autoregistration.
1516 /// Group: context_management
1517 public enum NVGNoPick = -1;
1519 /// >=0: this pickid will be assigned to all filled/stroked paths
1520 /// Group: context_management
1521 public int pickid (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickId : NVGNoPick); }
1523 /// >=0: this pickid will be assigned to all filled/stroked paths
1524 /// Group: context_management
1525 public void pickid (NVGContext ctx, int v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.pathPickId = v; }
1527 /// pick autoregistration mode; see [NVGPickKind]
1528 /// Group: context_management
1529 public uint pickmode (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickRegistered&NVGPickKind.All : 0); }
1531 /// pick autoregistration mode; see [NVGPickKind]
1532 /// Group: context_management
1533 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); }
1535 // tesselator options
1537 /// Get current Bezier tesselation mode. See [NVGTesselation].
1538 /// Group: context_management
1539 public NVGTesselation tesselation (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.tesselatortype : NVGTesselation.DeCasteljau); }
1541 /// Set current Bezier tesselation mode. See [NVGTesselation].
1542 /// Group: context_management
1543 public void tesselation (NVGContext ctx, NVGTesselation v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.tesselatortype = v; }
1546 private struct NVGcontextinternal {
1547 private:
1548 NVGparams params;
1549 float* commands;
1550 int ccommands;
1551 int ncommands;
1552 float commandx, commandy;
1553 NVGstate[NVG_MAX_STATES] states;
1554 int nstates;
1555 NVGpathCache* cache;
1556 public float tessTol;
1557 public float angleTol; // 0.0f -- angle tolerance for McSeem Bezier rasterizer
1558 public float cuspLimit; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps)
1559 float distTol;
1560 public float fringeWidth;
1561 float devicePxRatio;
1562 FONSContext fs;
1563 NVGImage[NVG_MAX_FONTIMAGES] fontImages;
1564 int fontImageIdx;
1565 int drawCallCount;
1566 int fillTriCount;
1567 int strokeTriCount;
1568 int textTriCount;
1569 NVGTesselation tesselatortype;
1570 // picking API
1571 NVGpickScene* pickScene;
1572 int pathPickId; // >=0: register all paths for picking using this id
1573 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
1574 // path recording
1575 NVGPathSet recset;
1576 int recstart; // used to cancel recording
1577 bool recblockdraw;
1578 // internals
1579 NVGMatrix gpuAffine;
1580 int mWidth, mHeight;
1581 // image manager
1582 shared int imageCount; // number of alive images in this context
1583 bool contextAlive; // context can be dead, but still contain some images
1585 @disable this (this); // no copies
1586 void opAssign() (in auto ref NVGcontextinternal a) { static assert(0, "no copies!"); }
1588 // debug feature
1589 public @property int getImageCount () nothrow @trusted @nogc {
1590 import core.atomic;
1591 return atomicLoad(imageCount);
1596 /** Returns number of tesselated pathes in context.
1598 * Tesselated pathes are either triangle strips (for strokes), or
1599 * triangle fans (for fills). Note that NanoVega can generate some
1600 * surprising pathes (like fringe stroke for antialiasing, for example).
1602 * One render path can contain vertices both for fill, and for stroke triangles.
1604 public int renderPathCount (NVGContext ctx) pure nothrow @trusted @nogc {
1605 pragma(inline, true);
1606 return (ctx !is null && ctx.contextAlive ? ctx.cache.npaths : 0);
1609 /** Get vertices of "fill" triangle fan for the given render path. Can return empty slice.
1611 * $(WARNING Returned slice can be invalidated by any other NanoVega API call
1612 * (except the calls to render path accessors), and using it in such
1613 * case is UB. So copy vertices to freshly allocated array if you want
1614 * to keep them for further processing.)
1616 public const(NVGVertex)[] renderPathFillVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1617 pragma(inline, true);
1618 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].fillVertices : null);
1621 /** Get vertices of "stroke" triangle strip for the given render path. Can return empty slice.
1623 * $(WARNING Returned slice can be invalidated by any other NanoVega API call
1624 * (except the calls to render path accessors), and using it in such
1625 * case is UB. So copy vertices to freshly allocated array if you want
1626 * to keep them for further processing.)
1628 public const(NVGVertex)[] renderPathStrokeVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1629 pragma(inline, true);
1630 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].strokeVertices : null);
1634 /// Returns winding for the given render path.
1635 public NVGWinding renderPathWinding (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1636 pragma(inline, true);
1637 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].winding : NVGWinding.CCW);
1641 /// Returns "complex path" flag for the given render path.
1642 public bool renderPathComplex (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1643 pragma(inline, true);
1644 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].complex : false);
1648 void nvg__imageIncRef (NVGContext ctx, int imgid, bool increfInGL=true) nothrow @trusted @nogc {
1649 if (ctx !is null && imgid > 0) {
1650 import core.atomic : atomicOp;
1651 atomicOp!"+="(ctx.imageCount, 1);
1652 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[++]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); }
1653 if (ctx.contextAlive && increfInGL) ctx.params.renderTextureIncRef(ctx.params.userPtr, imgid);
1657 void nvg__imageDecRef (NVGContext ctx, int imgid) nothrow @trusted @nogc {
1658 if (ctx !is null && imgid > 0) {
1659 import core.atomic : atomicOp;
1660 int icnt = atomicOp!"-="(ctx.imageCount, 1);
1661 if (icnt < 0) assert(0, "NanoVega: internal image refcounting error");
1662 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[--]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); }
1663 if (ctx.contextAlive) ctx.params.renderDeleteTexture(ctx.params.userPtr, imgid);
1664 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); }
1665 if (!ctx.contextAlive && icnt == 0) {
1666 // it is finally safe to free context memory
1667 import core.stdc.stdlib : free;
1668 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("killed zombie context %p\n", ctx); }
1669 free(ctx);
1675 public import core.stdc.math :
1676 nvg__sqrtf = sqrtf,
1677 nvg__modf = fmodf,
1678 nvg__sinf = sinf,
1679 nvg__cosf = cosf,
1680 nvg__tanf = tanf,
1681 nvg__atan2f = atan2f,
1682 nvg__acosf = acosf,
1683 nvg__ceilf = ceilf,
1684 nvg__floorf = floorf;
1686 version(Windows) {
1687 public int nvg__lrintf (float f) nothrow @trusted @nogc { pragma(inline, true); return cast(int)(f+0.5); }
1688 } else {
1689 public import core.stdc.math : nvg__lrintf = lrintf;
1692 public auto nvg__min(T) (T a, T b) { pragma(inline, true); return (a < b ? a : b); }
1693 public auto nvg__max(T) (T a, T b) { pragma(inline, true); return (a > b ? a : b); }
1694 public auto nvg__clamp(T) (T a, T mn, T mx) { pragma(inline, true); return (a < mn ? mn : (a > mx ? mx : a)); }
1695 //float nvg__absf() (float a) { pragma(inline, true); return (a >= 0.0f ? a : -a); }
1696 public auto nvg__sign(T) (T a) { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); }
1697 public float nvg__cross() (float dx0, float dy0, float dx1, float dy1) { pragma(inline, true); return (dx1*dy0-dx0*dy1); }
1699 //public import core.stdc.math : nvg__absf = fabsf;
1700 public import core.math : nvg__absf = fabs;
1703 float nvg__normalize (float* x, float* y) nothrow @safe @nogc {
1704 float d = nvg__sqrtf((*x)*(*x)+(*y)*(*y));
1705 if (d > 1e-6f) {
1706 immutable float id = 1.0f/d;
1707 *x *= id;
1708 *y *= id;
1710 return d;
1713 void nvg__deletePathCache (ref NVGpathCache* c) nothrow @trusted @nogc {
1714 if (c !is null) {
1715 c.clear();
1716 free(c);
1720 NVGpathCache* nvg__allocPathCache () nothrow @trusted @nogc {
1721 NVGpathCache* c = cast(NVGpathCache*)malloc(NVGpathCache.sizeof);
1722 if (c is null) goto error;
1723 memset(c, 0, NVGpathCache.sizeof);
1725 c.points = cast(NVGpoint*)malloc(NVGpoint.sizeof*NVG_INIT_POINTS_SIZE);
1726 if (c.points is null) goto error;
1727 assert(c.npoints == 0);
1728 c.cpoints = NVG_INIT_POINTS_SIZE;
1730 c.paths = cast(NVGpath*)malloc(NVGpath.sizeof*NVG_INIT_PATHS_SIZE);
1731 if (c.paths is null) goto error;
1732 assert(c.npaths == 0);
1733 c.cpaths = NVG_INIT_PATHS_SIZE;
1735 c.verts = cast(NVGVertex*)malloc(NVGVertex.sizeof*NVG_INIT_VERTS_SIZE);
1736 if (c.verts is null) goto error;
1737 assert(c.nverts == 0);
1738 c.cverts = NVG_INIT_VERTS_SIZE;
1740 return c;
1742 error:
1743 nvg__deletePathCache(c);
1744 return null;
1747 void nvg__setDevicePixelRatio (NVGContext ctx, float ratio) pure nothrow @safe @nogc {
1748 ctx.tessTol = 0.25f/ratio;
1749 ctx.distTol = 0.01f/ratio;
1750 ctx.fringeWidth = 1.0f/ratio;
1751 ctx.devicePxRatio = ratio;
1754 NVGCompositeOperationState nvg__compositeOperationState (NVGCompositeOperation op) pure nothrow @safe @nogc {
1755 NVGCompositeOperationState state;
1756 NVGBlendFactor sfactor, dfactor;
1758 if (op == NVGCompositeOperation.SourceOver) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha;}
1759 else if (op == NVGCompositeOperation.SourceIn) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.Zero; }
1760 else if (op == NVGCompositeOperation.SourceOut) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.Zero; }
1761 else if (op == NVGCompositeOperation.SourceAtop) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.OneMinusSrcAlpha; }
1762 else if (op == NVGCompositeOperation.DestinationOver) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.One; }
1763 else if (op == NVGCompositeOperation.DestinationIn) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.SrcAlpha; }
1764 else if (op == NVGCompositeOperation.DestinationOut) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.OneMinusSrcAlpha; }
1765 else if (op == NVGCompositeOperation.DestinationAtop) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.SrcAlpha; }
1766 else if (op == NVGCompositeOperation.Lighter) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.One; }
1767 else if (op == NVGCompositeOperation.Copy) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.Zero; }
1768 else if (op == NVGCompositeOperation.Xor) {
1769 state.simple = false;
1770 state.srcRGB = NVGBlendFactor.OneMinusDstColor;
1771 state.srcAlpha = NVGBlendFactor.OneMinusDstAlpha;
1772 state.dstRGB = NVGBlendFactor.OneMinusSrcColor;
1773 state.dstAlpha = NVGBlendFactor.OneMinusSrcAlpha;
1774 return state;
1776 else { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } // default value for invalid op: SourceOver
1778 state.simple = true;
1779 state.srcAlpha = sfactor;
1780 state.dstAlpha = dfactor;
1781 return state;
1784 NVGstate* nvg__getState (NVGContext ctx) pure nothrow @trusted @nogc {
1785 pragma(inline, true);
1786 if (ctx is null || !ctx.contextAlive || ctx.nstates == 0) assert(0, "NanoVega: cannot perform commands on inactive context");
1787 return &ctx.states.ptr[ctx.nstates-1];
1790 // Constructor called by the render back-end.
1791 NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc {
1792 FONSParams fontParams;
1793 NVGContext ctx = cast(NVGContext)malloc(NVGcontextinternal.sizeof);
1794 if (ctx is null) goto error;
1795 memset(ctx, 0, NVGcontextinternal.sizeof);
1797 ctx.angleTol = 0; // angle tolerance for McSeem Bezier rasterizer
1798 ctx.cuspLimit = 0; // cusp limit for McSeem Bezier rasterizer (0: real cusps)
1800 ctx.contextAlive = true;
1802 ctx.params = *params;
1803 //ctx.fontImages[0..NVG_MAX_FONTIMAGES] = 0;
1805 ctx.commands = cast(float*)malloc(float.sizeof*NVG_INIT_COMMANDS_SIZE);
1806 if (ctx.commands is null) goto error;
1807 ctx.ncommands = 0;
1808 ctx.ccommands = NVG_INIT_COMMANDS_SIZE;
1810 ctx.cache = nvg__allocPathCache();
1811 if (ctx.cache is null) goto error;
1813 ctx.save();
1814 ctx.reset();
1816 nvg__setDevicePixelRatio(ctx, 1.0f);
1817 ctx.mWidth = ctx.mHeight = 0;
1819 if (!ctx.params.renderCreate(ctx.params.userPtr)) goto error;
1821 // init font rendering
1822 memset(&fontParams, 0, fontParams.sizeof);
1823 fontParams.width = NVG_INIT_FONTIMAGE_SIZE;
1824 fontParams.height = NVG_INIT_FONTIMAGE_SIZE;
1825 fontParams.flags = FONSParams.Flag.ZeroTopLeft;
1826 fontParams.renderCreate = null;
1827 fontParams.renderUpdate = null;
1828 fontParams.renderDelete = null;
1829 fontParams.userPtr = null;
1830 ctx.fs = FONSContext.create(fontParams);
1831 if (ctx.fs is null) goto error;
1833 // create font texture
1834 ctx.fontImages[0].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, fontParams.width, fontParams.height, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null);
1835 if (ctx.fontImages[0].id == 0) goto error;
1836 ctx.fontImages[0].ctx = ctx;
1837 ctx.nvg__imageIncRef(ctx.fontImages[0].id, false); // don't increment driver refcount
1838 ctx.fontImageIdx = 0;
1840 ctx.pathPickId = -1;
1841 ctx.tesselatortype = NVG_DEFAULT_TESSELATOR;
1843 return ctx;
1845 error:
1846 ctx.deleteInternal();
1847 return null;
1850 // Called by render backend.
1851 NVGparams* internalParams (NVGContext ctx) nothrow @trusted @nogc {
1852 return &ctx.params;
1855 // Destructor called by the render back-end.
1856 void deleteInternal (ref NVGContext ctx) nothrow @trusted @nogc {
1857 if (ctx is null) return;
1858 if (ctx.contextAlive) {
1859 if (ctx.commands !is null) free(ctx.commands);
1860 nvg__deletePathCache(ctx.cache);
1862 if (ctx.fs) ctx.fs.kill();
1864 foreach (uint i; 0..NVG_MAX_FONTIMAGES) ctx.fontImages[i].clear();
1866 if (ctx.params.renderDelete !is null) ctx.params.renderDelete(ctx.params.userPtr);
1868 if (ctx.pickScene !is null) nvg__deletePickScene(ctx.pickScene);
1870 ctx.contextAlive = false;
1872 import core.atomic : atomicLoad;
1873 if (atomicLoad(ctx.imageCount) == 0) {
1874 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("destroyed context %p\n", ctx); }
1875 free(ctx);
1876 } else {
1877 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("context %p is zombie now (%d image refs)\n", ctx, ctx.imageCount); }
1882 /// Delete NanoVega context.
1883 /// Group: context_management
1884 public void kill (ref NVGContext ctx) nothrow @trusted @nogc {
1885 if (ctx !is null) {
1886 ctx.deleteInternal();
1887 ctx = null;
1891 /// Returns `true` if the given context is not `null` and can be used for painting.
1892 /// Group: context_management
1893 public bool valid (in NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive); }
1896 // ////////////////////////////////////////////////////////////////////////// //
1897 // Frame Management
1899 /** Begin drawing a new frame.
1901 * Calls to NanoVega drawing API should be wrapped in [beginFrame] and [endFrame]
1903 * [beginFrame] defines the size of the window to render to in relation currently
1904 * set viewport (i.e. glViewport on GL backends). Device pixel ration allows to
1905 * control the rendering on Hi-DPI devices.
1907 * For example, GLFW returns two dimension for an opened window: window size and
1908 * frame buffer size. In that case you would set windowWidth/windowHeight to the window size,
1909 * devicePixelRatio to: `windowWidth/windowHeight`.
1911 * Default ratio is `1`.
1913 * Note that fractional ratio can (and will) distort your fonts and images.
1915 * This call also resets pick marks (see picking API for non-rasterized paths),
1916 * path recording, and GPU affine transformatin matrix.
1918 * see also [glNVGClearFlags], which returns necessary flags for [glClear].
1920 * Group: frame_management
1922 public void beginFrame (NVGContext ctx, int windowWidth, int windowHeight, float devicePixelRatio=1.0f) nothrow @trusted @nogc {
1923 import std.math : isNaN;
1925 printf("Tris: draws:%d fill:%d stroke:%d text:%d TOT:%d\n",
1926 ctx.drawCallCount, ctx.fillTriCount, ctx.strokeTriCount, ctx.textTriCount,
1927 ctx.fillTriCount+ctx.strokeTriCount+ctx.textTriCount);
1929 if (ctx.nstates > 0) ctx.cancelFrame();
1931 if (windowWidth < 1) windowWidth = 1;
1932 if (windowHeight < 1) windowHeight = 1;
1934 if (isNaN(devicePixelRatio)) devicePixelRatio = (windowHeight > 0 ? cast(float)windowWidth/cast(float)windowHeight : 1024.0/768.0);
1936 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
1937 ctx.nstates = 0;
1938 ctx.save();
1939 ctx.reset();
1941 nvg__setDevicePixelRatio(ctx, devicePixelRatio);
1943 ctx.params.renderViewport(ctx.params.userPtr, windowWidth, windowHeight);
1944 ctx.mWidth = windowWidth;
1945 ctx.mHeight = windowHeight;
1947 ctx.recset = null;
1948 ctx.recstart = -1;
1950 ctx.pathPickId = NVGNoPick;
1951 ctx.pathPickRegistered = 0;
1953 ctx.drawCallCount = 0;
1954 ctx.fillTriCount = 0;
1955 ctx.strokeTriCount = 0;
1956 ctx.textTriCount = 0;
1958 ctx.ncommands = 0;
1959 ctx.pathPickRegistered = 0;
1960 nvg__clearPathCache(ctx);
1962 ctx.gpuAffine = NVGMatrix.Identity;
1964 nvg__pickBeginFrame(ctx, windowWidth, windowHeight);
1967 /// Cancels drawing the current frame. Cancels path recording.
1968 /// Group: frame_management
1969 public void cancelFrame (NVGContext ctx) nothrow @trusted @nogc {
1970 ctx.cancelRecording();
1971 //ctx.mWidth = 0;
1972 //ctx.mHeight = 0;
1973 // cancel render queue
1974 ctx.params.renderCancel(ctx.params.userPtr);
1975 // clear saved states (this may free some textures)
1976 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
1977 ctx.nstates = 0;
1980 /// Ends drawing the current frame (flushing remaining render state). Commits recorded paths.
1981 /// Group: frame_management
1982 public void endFrame (NVGContext ctx) nothrow @trusted @nogc {
1983 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx);
1984 ctx.stopRecording();
1985 //ctx.mWidth = 0;
1986 //ctx.mHeight = 0;
1987 // flush render queue
1988 NVGstate* state = nvg__getState(ctx);
1989 ctx.params.renderFlush(ctx.params.userPtr);
1990 if (ctx.fontImageIdx != 0) {
1991 auto fontImage = ctx.fontImages[ctx.fontImageIdx];
1992 int j = 0, iw, ih;
1993 // delete images that smaller than current one
1994 if (!fontImage.valid) return;
1995 ctx.imageSize(fontImage, iw, ih);
1996 foreach (int i; 0..ctx.fontImageIdx) {
1997 if (ctx.fontImages[i].valid) {
1998 int nw, nh;
1999 ctx.imageSize(ctx.fontImages[i], nw, nh);
2000 if (nw < iw || nh < ih) {
2001 ctx.deleteImage(ctx.fontImages[i]);
2002 } else {
2003 ctx.fontImages[j++] = ctx.fontImages[i];
2007 // make current font image to first
2008 ctx.fontImages[j++] = ctx.fontImages[0];
2009 ctx.fontImages[0] = fontImage;
2010 ctx.fontImageIdx = 0;
2011 // clear all images after j
2012 ctx.fontImages[j..NVG_MAX_FONTIMAGES] = NVGImage.init;
2014 // clear saved states (this may free some textures)
2015 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2016 ctx.nstates = 0;
2020 // ////////////////////////////////////////////////////////////////////////// //
2021 // Recording and Replaying Pathes
2023 // Saved path set.
2024 // Group: path_recording
2025 public alias NVGPathSet = NVGPathSetS*;
2028 //TODO: save scissor info?
2029 struct NVGPathSetS {
2030 private:
2031 // either path cache, or text item
2032 static struct Node {
2033 NVGPaint paint;
2034 NVGpathCache* path;
2037 private:
2038 Node* nodes;
2039 int nnodes, cnodes;
2040 NVGpickScene* pickscene;
2041 //int npickscenes, cpickscenes;
2042 NVGContext svctx; // used to do some sanity checks, and to free resources
2044 private:
2045 Node* allocNode () nothrow @trusted @nogc {
2046 import core.stdc.string : memset;
2047 // grow buffer if necessary
2048 if (nnodes+1 > cnodes) {
2049 import core.stdc.stdlib : realloc;
2050 int newsz = (cnodes == 0 ? 8 : cnodes <= 1024 ? cnodes*2 : cnodes+1024);
2051 nodes = cast(Node*)realloc(nodes, newsz*Node.sizeof);
2052 if (nodes is null) assert(0, "NanoVega: out of memory");
2053 //memset(svp.caches+svp.ccaches, 0, (newsz-svp.ccaches)*NVGpathCache.sizeof);
2054 cnodes = newsz;
2056 assert(nnodes < cnodes);
2057 memset(nodes+nnodes, 0, Node.sizeof);
2058 return &nodes[nnodes++];
2061 Node* allocPathNode () nothrow @trusted @nogc {
2062 import core.stdc.stdlib : malloc;
2063 import core.stdc.string : memset;
2064 auto node = allocNode();
2065 // allocate path cache
2066 auto pc = cast(NVGpathCache*)malloc(NVGpathCache.sizeof);
2067 if (pc is null) assert(0, "NanoVega: out of memory");
2068 node.path = pc;
2069 return node;
2072 void clearNode (int idx) nothrow @trusted @nogc {
2073 if (idx < 0 || idx >= nnodes) return;
2074 Node* node = &nodes[idx];
2075 if (svctx !is null && node.paint.image.valid) node.paint.image.clear();
2076 if (node.path !is null) node.path.clear();
2079 private:
2080 void takeCurrentPickScene (NVGContext ctx) nothrow @trusted @nogc {
2081 NVGpickScene* ps = ctx.pickScene;
2082 if (ps is null) return; // nothing to do
2083 if (ps.npaths == 0) return; // pick scene is empty
2084 ctx.pickScene = null;
2085 pickscene = ps;
2088 void replay (NVGContext ctx, in ref NVGColor fillTint, in ref NVGColor strokeTint) nothrow @trusted @nogc {
2089 NVGstate* state = nvg__getState(ctx);
2090 foreach (ref node; nodes[0..nnodes]) {
2091 if (auto cc = node.path) {
2092 if (cc.npaths <= 0) continue;
2094 if (cc.fillReady) {
2095 NVGPaint fillPaint = node.paint;
2097 // apply global alpha
2098 fillPaint.innerColor.a *= state.alpha;
2099 fillPaint.middleColor.a *= state.alpha;
2100 fillPaint.outerColor.a *= state.alpha;
2102 fillPaint.innerColor.applyTint(fillTint);
2103 fillPaint.middleColor.applyTint(fillTint);
2104 fillPaint.outerColor.applyTint(fillTint);
2106 ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &fillPaint, &state.scissor, cc.fringeWidth, cc.bounds.ptr, cc.paths, cc.npaths, cc.evenOddMode);
2108 // count triangles
2109 foreach (int i; 0..cc.npaths) {
2110 NVGpath* path = &cc.paths[i];
2111 ctx.fillTriCount += path.nfill-2;
2112 ctx.fillTriCount += path.nstroke-2;
2113 ctx.drawCallCount += 2;
2117 if (cc.strokeReady) {
2118 NVGPaint strokePaint = node.paint;
2120 strokePaint.innerColor.a *= cc.strokeAlphaMul;
2121 strokePaint.middleColor.a *= cc.strokeAlphaMul;
2122 strokePaint.outerColor.a *= cc.strokeAlphaMul;
2124 // apply global alpha
2125 strokePaint.innerColor.a *= state.alpha;
2126 strokePaint.middleColor.a *= state.alpha;
2127 strokePaint.outerColor.a *= state.alpha;
2129 strokePaint.innerColor.applyTint(strokeTint);
2130 strokePaint.middleColor.applyTint(strokeTint);
2131 strokePaint.outerColor.applyTint(strokeTint);
2133 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &strokePaint, &state.scissor, cc.fringeWidth, cc.strokeWidth, cc.paths, cc.npaths);
2135 // count triangles
2136 foreach (int i; 0..cc.npaths) {
2137 NVGpath* path = &cc.paths[i];
2138 ctx.strokeTriCount += path.nstroke-2;
2139 ++ctx.drawCallCount;
2146 public:
2147 @disable this (this); // no copies
2148 void opAssign() (in auto ref NVGPathSetS a) { static assert(0, "no copies!"); }
2150 // pick test
2151 // Call delegate [dg] for each path under the specified position (in no particular order).
2152 // Returns the id of the path for which delegate [dg] returned true or -1.
2153 // dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending).
2154 int hitTestDG(bool bestOrder=false, DG) (in float x, in float y, NVGPickKind kind, scope DG dg) if (IsGoodHitTestDG!DG || IsGoodHitTestInternalDG!DG) {
2155 if (pickscene is null) return -1;
2157 NVGpickScene* ps = pickscene;
2158 int levelwidth = 1<<(ps.nlevels-1);
2159 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth);
2160 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth);
2161 int npicked = 0;
2163 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) {
2164 NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx];
2165 while (pp !is null) {
2166 if (nvg__pickPathTestBounds(svctx, ps, pp, x, y)) {
2167 int hit = 0;
2168 if ((kind&NVGPickKind.Stroke) && (pp.flags&NVGPathFlags.Stroke)) hit = nvg__pickPathStroke(ps, pp, x, y);
2169 if (!hit && (kind&NVGPickKind.Fill) && (pp.flags&NVGPathFlags.Fill)) hit = nvg__pickPath(ps, pp, x, y);
2170 if (hit) {
2171 static if (IsGoodHitTestDG!DG) {
2172 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) {
2173 if (dg(pp.id, cast(int)pp.order)) return pp.id;
2174 } else {
2175 dg(pp.id, cast(int)pp.order);
2177 } else {
2178 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) {
2179 if (dg(pp)) return pp.id;
2180 } else {
2181 dg(pp);
2186 pp = pp.next;
2188 cellx >>= 1;
2189 celly >>= 1;
2190 levelwidth >>= 1;
2193 return -1;
2196 // Fills ids with a list of the top most hit ids under the specified position.
2197 // Returns the slice of [ids].
2198 int[] hitTestAll (in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc {
2199 if (pickscene is null || ids.length == 0) return ids[0..0];
2201 int npicked = 0;
2202 NVGpickScene* ps = pickscene;
2204 hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
2205 if (npicked == ps.cpicked) {
2206 int cpicked = ps.cpicked+ps.cpicked;
2207 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked);
2208 if (picked is null) return true; // abort
2209 ps.cpicked = cpicked;
2210 ps.picked = picked;
2212 ps.picked[npicked] = pp;
2213 ++npicked;
2214 return false; // go on
2217 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths);
2219 assert(npicked >= 0);
2220 if (npicked > ids.length) npicked = cast(int)ids.length;
2221 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id;
2223 return ids[0..npicked];
2226 // Returns the id of the pickable shape containing x,y or -1 if no shape was found.
2227 int hitTest (in float x, in float y, NVGPickKind kind) nothrow @trusted @nogc {
2228 if (pickscene is null) return -1;
2230 int bestOrder = -1;
2231 int bestID = -1;
2233 hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
2234 if (pp.order > bestOrder) {
2235 bestOrder = pp.order;
2236 bestID = pp.id;
2240 return bestID;
2244 // Append current path to existing path set. Is is safe to call this with `null` [svp].
2245 void appendCurrentPathToCache (NVGContext ctx, NVGPathSet svp, in ref NVGPaint paint) nothrow @trusted @nogc {
2246 if (ctx is null || svp is null) return;
2247 if (ctx !is svp.svctx) assert(0, "NanoVega: cannot save paths from different contexts");
2248 if (ctx.ncommands == 0) {
2249 assert(ctx.cache.npaths == 0);
2250 return;
2252 if (!ctx.cache.fillReady && !ctx.cache.strokeReady) return;
2254 // tesselate current path
2255 //if (!ctx.cache.fillReady) nvg__prepareFill(ctx);
2256 //if (!ctx.cache.strokeReady) nvg__prepareStroke(ctx);
2258 auto node = svp.allocPathNode();
2259 NVGpathCache* cc = node.path;
2260 cc.copyFrom(ctx.cache);
2261 node.paint = paint;
2262 // copy path commands (we may need 'em for picking)
2263 version(all) {
2264 cc.ncommands = ctx.ncommands;
2265 if (cc.ncommands) {
2266 import core.stdc.stdlib : malloc;
2267 import core.stdc.string : memcpy;
2268 cc.commands = cast(float*)malloc(ctx.ncommands*float.sizeof);
2269 if (cc.commands is null) assert(0, "NanoVega: out of memory");
2270 memcpy(cc.commands, ctx.commands, ctx.ncommands*float.sizeof);
2271 } else {
2272 cc.commands = null;
2277 // Create new empty path set.
2278 // Group: path_recording
2279 public NVGPathSet newPathSet (NVGContext ctx) nothrow @trusted @nogc {
2280 import core.stdc.stdlib : malloc;
2281 import core.stdc.string : memset;
2282 if (ctx is null) return null;
2283 NVGPathSet res = cast(NVGPathSet)malloc(NVGPathSetS.sizeof);
2284 if (res is null) assert(0, "NanoVega: out of memory");
2285 memset(res, 0, NVGPathSetS.sizeof);
2286 res.svctx = ctx;
2287 return res;
2290 // Is the given path set empty? Empty path set can be `null`.
2291 // Group: path_recording
2292 public bool empty (NVGPathSet svp) pure nothrow @safe @nogc { pragma(inline, true); return (svp is null || svp.nnodes == 0); }
2294 // Clear path set contents. Will release $(B some) allocated memory (this function is meant to clear something that will be reused).
2295 // Group: path_recording
2296 public void clear (NVGPathSet svp) nothrow @trusted @nogc {
2297 if (svp !is null) {
2298 import core.stdc.stdlib : free;
2299 foreach (immutable idx; 0.. svp.nnodes) svp.clearNode(idx);
2300 svp.nnodes = 0;
2304 // Destroy path set (frees all allocated memory).
2305 // Group: path_recording
2306 public void kill (ref NVGPathSet svp) nothrow @trusted @nogc {
2307 if (svp !is null) {
2308 import core.stdc.stdlib : free;
2309 svp.clear();
2310 if (svp.nodes !is null) free(svp.nodes);
2311 free(svp);
2312 if (svp.pickscene !is null) nvg__deletePickScene(svp.pickscene);
2313 svp = null;
2317 // Start path recording. [svp] should be alive until recording is cancelled or stopped.
2318 // Group: path_recording
2319 public void startRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc {
2320 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2321 ctx.stopRecording();
2322 ctx.recset = svp;
2323 ctx.recstart = (svp !is null ? svp.nnodes : -1);
2324 ctx.recblockdraw = false;
2327 /* Start path recording. [svp] should be alive until recording is cancelled or stopped.
2329 * This will block all rendering, so you can call your rendering functions to record paths without actual drawing.
2330 * Commiting or cancelling will re-enable rendering.
2331 * You can call this with `null` svp to block rendering without recording any paths.
2333 * Group: path_recording
2335 public void startBlockingRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc {
2336 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2337 ctx.stopRecording();
2338 ctx.recset = svp;
2339 ctx.recstart = (svp !is null ? svp.nnodes : -1);
2340 ctx.recblockdraw = true;
2343 // Commit recorded paths. It is safe to call this when recording is not started.
2344 // Group: path_recording
2345 public void stopRecording (NVGContext ctx) nothrow @trusted @nogc {
2346 if (ctx.recset !is null && ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2347 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx);
2348 ctx.recset = null;
2349 ctx.recstart = -1;
2350 ctx.recblockdraw = false;
2353 // Cancel path recording.
2354 // Group: path_recording
2355 public void cancelRecording (NVGContext ctx) nothrow @trusted @nogc {
2356 if (ctx.recset !is null) {
2357 if (ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2358 assert(ctx.recstart >= 0 && ctx.recstart <= ctx.recset.nnodes);
2359 foreach (immutable idx; ctx.recstart..ctx.recset.nnodes) ctx.recset.clearNode(idx);
2360 ctx.recset.nnodes = ctx.recstart;
2361 ctx.recset = null;
2362 ctx.recstart = -1;
2364 ctx.recblockdraw = false;
2367 /* Replay saved path set.
2369 * Replaying record while you're recording another one is undefined behavior.
2371 * Group: path_recording
2373 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint, in auto ref NVGColor strokeTint) nothrow @trusted @nogc {
2374 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2375 svp.replay(ctx, fillTint, strokeTint);
2378 /// Ditto.
2379 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint) nothrow @trusted @nogc { ctx.replayRecording(svp, fillTint, NVGColor.transparent); }
2381 /// Ditto.
2382 public void replayRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { ctx.replayRecording(svp, NVGColor.transparent, NVGColor.transparent); }
2385 // ////////////////////////////////////////////////////////////////////////// //
2386 // Composite operation
2388 /// Sets the composite operation.
2389 /// Group: composite_operation
2390 public void globalCompositeOperation (NVGContext ctx, NVGCompositeOperation op) nothrow @trusted @nogc {
2391 NVGstate* state = nvg__getState(ctx);
2392 state.compositeOperation = nvg__compositeOperationState(op);
2395 /// Sets the composite operation with custom pixel arithmetic.
2396 /// Group: composite_operation
2397 public void globalCompositeBlendFunc (NVGContext ctx, NVGBlendFactor sfactor, NVGBlendFactor dfactor) nothrow @trusted @nogc {
2398 ctx.globalCompositeBlendFuncSeparate(sfactor, dfactor, sfactor, dfactor);
2401 /// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately.
2402 /// Group: composite_operation
2403 public void globalCompositeBlendFuncSeparate (NVGContext ctx, NVGBlendFactor srcRGB, NVGBlendFactor dstRGB, NVGBlendFactor srcAlpha, NVGBlendFactor dstAlpha) nothrow @trusted @nogc {
2404 NVGCompositeOperationState op;
2405 op.simple = false;
2406 op.srcRGB = srcRGB;
2407 op.dstRGB = dstRGB;
2408 op.srcAlpha = srcAlpha;
2409 op.dstAlpha = dstAlpha;
2410 NVGstate* state = nvg__getState(ctx);
2411 state.compositeOperation = op;
2415 // ////////////////////////////////////////////////////////////////////////// //
2416 // Color utils
2418 /// Returns a color value from string form.
2419 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb"
2420 /// Group: color_utils
2421 public NVGColor nvgRGB (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); }
2423 /// Ditto.
2424 public NVGColor nvgRGBA (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); }
2426 /// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f).
2427 /// Group: color_utils
2428 public NVGColor nvgRGB (int r, int g, int b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(nvgClampToByte(r), nvgClampToByte(g), nvgClampToByte(b), 255); }
2430 /// Returns a color value from red, green, blue values. Alpha will be set to 1.0f.
2431 /// Group: color_utils
2432 public NVGColor nvgRGBf (float r, float g, float b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(r, g, b, 1.0f); }
2434 /// Returns a color value from red, green, blue and alpha values.
2435 /// Group: color_utils
2436 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)); }
2438 /// Returns a color value from red, green, blue and alpha values.
2439 /// Group: color_utils
2440 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); }
2442 /// Returns new color with transparency (alpha) set to [a].
2443 /// Group: color_utils
2444 public NVGColor nvgTransRGBA (NVGColor c, ubyte a) nothrow @trusted @nogc {
2445 pragma(inline, true);
2446 c.a = a/255.0f;
2447 return c;
2450 /// Ditto.
2451 public NVGColor nvgTransRGBAf (NVGColor c, float a) nothrow @trusted @nogc {
2452 pragma(inline, true);
2453 c.a = a;
2454 return c;
2457 /// Linearly interpolates from color c0 to c1, and returns resulting color value.
2458 /// Group: color_utils
2459 public NVGColor nvgLerpRGBA() (in auto ref NVGColor c0, in auto ref NVGColor c1, float u) nothrow @trusted @nogc {
2460 NVGColor cint = void;
2461 u = nvg__clamp(u, 0.0f, 1.0f);
2462 float oneminu = 1.0f-u;
2463 foreach (uint i; 0..4) cint.rgba.ptr[i] = c0.rgba.ptr[i]*oneminu+c1.rgba.ptr[i]*u;
2464 return cint;
2467 /* see below
2468 public NVGColor nvgHSL() (float h, float s, float l) {
2469 //pragma(inline, true); // alas
2470 return nvgHSLA(h, s, l, 255);
2474 float nvg__hue (float h, float m1, float m2) pure nothrow @safe @nogc {
2475 if (h < 0) h += 1;
2476 if (h > 1) h -= 1;
2477 if (h < 1.0f/6.0f) return m1+(m2-m1)*h*6.0f;
2478 if (h < 3.0f/6.0f) return m2;
2479 if (h < 4.0f/6.0f) return m1+(m2-m1)*(2.0f/3.0f-h)*6.0f;
2480 return m1;
2483 /// Returns color value specified by hue, saturation and lightness.
2484 /// HSL values are all in range [0..1], alpha will be set to 255.
2485 /// Group: color_utils
2486 public alias nvgHSL = nvgHSLA; // trick to allow inlining
2488 /// Returns color value specified by hue, saturation and lightness and alpha.
2489 /// HSL values are all in range [0..1], alpha in range [0..255].
2490 /// Group: color_utils
2491 public NVGColor nvgHSLA (float h, float s, float l, ubyte a=255) nothrow @trusted @nogc {
2492 pragma(inline, true);
2493 NVGColor col = void;
2494 h = nvg__modf(h, 1.0f);
2495 if (h < 0.0f) h += 1.0f;
2496 s = nvg__clamp(s, 0.0f, 1.0f);
2497 l = nvg__clamp(l, 0.0f, 1.0f);
2498 immutable float m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s);
2499 immutable float m1 = 2*l-m2;
2500 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2501 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f);
2502 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2503 col.a = a/255.0f;
2504 return col;
2507 /// Returns color value specified by hue, saturation and lightness and alpha.
2508 /// HSL values and alpha are all in range [0..1].
2509 /// Group: color_utils
2510 public NVGColor nvgHSLA (float h, float s, float l, float a) nothrow @trusted @nogc {
2511 // sorry for copypasta, it is for inliner
2512 static if (__VERSION__ >= 2072) pragma(inline, true);
2513 NVGColor col = void;
2514 h = nvg__modf(h, 1.0f);
2515 if (h < 0.0f) h += 1.0f;
2516 s = nvg__clamp(s, 0.0f, 1.0f);
2517 l = nvg__clamp(l, 0.0f, 1.0f);
2518 immutable m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s);
2519 immutable m1 = 2*l-m2;
2520 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2521 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f);
2522 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2523 col.a = a;
2524 return col;
2528 // ////////////////////////////////////////////////////////////////////////// //
2529 // Matrices and Transformations
2531 /** Matrix class.
2533 * Group: matrices
2535 public align(1) struct NVGMatrix {
2536 align(1):
2537 private:
2538 static immutable float[6] IdentityMat = [
2539 1.0f, 0.0f,
2540 0.0f, 1.0f,
2541 0.0f, 0.0f,
2544 public:
2545 /// Matrix values. Initial value is identity matrix.
2546 float[6] mat = [
2547 1.0f, 0.0f,
2548 0.0f, 1.0f,
2549 0.0f, 0.0f,
2552 public nothrow @trusted @nogc:
2553 /// Create Matrix with the given values.
2554 this (const(float)[] amat...) {
2555 pragma(inline, true);
2556 if (amat.length >= 6) {
2557 mat.ptr[0..6] = amat.ptr[0..6];
2558 } else {
2559 mat.ptr[0..6] = 0;
2560 mat.ptr[0..amat.length] = amat[];
2564 /// Can be used to check validity of [inverted] result
2565 @property bool valid () const { import core.stdc.math : isfinite; return (isfinite(mat.ptr[0]) != 0); }
2567 /// Returns `true` if this matrix is identity matrix.
2568 @property bool isIdentity () const { version(aliced) pragma(inline, true); return (mat[] == IdentityMat[]); }
2570 /// Returns new inverse matrix.
2571 /// If inverted matrix cannot be calculated, `res.valid` fill be `false`.
2572 NVGMatrix inverted () const {
2573 NVGMatrix res = this;
2574 res.invert;
2575 return res;
2578 /// Inverts this matrix.
2579 /// If inverted matrix cannot be calculated, `this.valid` fill be `false`.
2580 ref NVGMatrix invert () {
2581 float[6] inv = void;
2582 immutable double det = cast(double)mat.ptr[0]*mat.ptr[3]-cast(double)mat.ptr[2]*mat.ptr[1];
2583 if (det > -1e-6 && det < 1e-6) {
2584 inv[] = float.nan;
2585 } else {
2586 immutable double invdet = 1.0/det;
2587 inv.ptr[0] = cast(float)(mat.ptr[3]*invdet);
2588 inv.ptr[2] = cast(float)(-mat.ptr[2]*invdet);
2589 inv.ptr[4] = cast(float)((cast(double)mat.ptr[2]*mat.ptr[5]-cast(double)mat.ptr[3]*mat.ptr[4])*invdet);
2590 inv.ptr[1] = cast(float)(-mat.ptr[1]*invdet);
2591 inv.ptr[3] = cast(float)(mat.ptr[0]*invdet);
2592 inv.ptr[5] = cast(float)((cast(double)mat.ptr[1]*mat.ptr[4]-cast(double)mat.ptr[0]*mat.ptr[5])*invdet);
2594 mat.ptr[0..6] = inv.ptr[0..6];
2595 return this;
2598 /// Sets this matrix to identity matrix.
2599 ref NVGMatrix identity () { version(aliced) pragma(inline, true); mat[] = IdentityMat[]; return this; }
2601 /// Translate this matrix.
2602 ref NVGMatrix translate (in float tx, in float ty) {
2603 version(aliced) pragma(inline, true);
2604 return this.mul(Translated(tx, ty));
2607 /// Scale this matrix.
2608 ref NVGMatrix scale (in float sx, in float sy) {
2609 version(aliced) pragma(inline, true);
2610 return this.mul(Scaled(sx, sy));
2613 /// Rotate this matrix.
2614 ref NVGMatrix rotate (in float a) {
2615 version(aliced) pragma(inline, true);
2616 return this.mul(Rotated(a));
2619 /// Skew this matrix by X axis.
2620 ref NVGMatrix skewX (in float a) {
2621 version(aliced) pragma(inline, true);
2622 return this.mul(SkewedX(a));
2625 /// Skew this matrix by Y axis.
2626 ref NVGMatrix skewY (in float a) {
2627 version(aliced) pragma(inline, true);
2628 return this.mul(SkewedY(a));
2631 /// Skew this matrix by both axes.
2632 ref NVGMatrix skewY (in float ax, in float ay) {
2633 version(aliced) pragma(inline, true);
2634 return this.mul(SkewedXY(ax, ay));
2637 /// Transform point with this matrix. `null` destinations are allowed.
2638 /// [sx] and [sy] is the source point. [dx] and [dy] may point to the same variables.
2639 void point (float* dx, float* dy, float sx, float sy) nothrow @trusted @nogc {
2640 version(aliced) pragma(inline, true);
2641 if (dx !is null) *dx = sx*mat.ptr[0]+sy*mat.ptr[2]+mat.ptr[4];
2642 if (dy !is null) *dy = sx*mat.ptr[1]+sy*mat.ptr[3]+mat.ptr[5];
2645 /// Transform point with this matrix.
2646 void point (ref float x, ref float y) nothrow @trusted @nogc {
2647 version(aliced) pragma(inline, true);
2648 immutable float nx = x*mat.ptr[0]+y*mat.ptr[2]+mat.ptr[4];
2649 immutable float ny = x*mat.ptr[1]+y*mat.ptr[3]+mat.ptr[5];
2650 x = nx;
2651 y = ny;
2654 /// Sets this matrix to the result of multiplication of `this` and [s] (this * S).
2655 ref NVGMatrix mul() (in auto ref NVGMatrix s) {
2656 immutable float t0 = mat.ptr[0]*s.mat.ptr[0]+mat.ptr[1]*s.mat.ptr[2];
2657 immutable float t2 = mat.ptr[2]*s.mat.ptr[0]+mat.ptr[3]*s.mat.ptr[2];
2658 immutable float t4 = mat.ptr[4]*s.mat.ptr[0]+mat.ptr[5]*s.mat.ptr[2]+s.mat.ptr[4];
2659 mat.ptr[1] = mat.ptr[0]*s.mat.ptr[1]+mat.ptr[1]*s.mat.ptr[3];
2660 mat.ptr[3] = mat.ptr[2]*s.mat.ptr[1]+mat.ptr[3]*s.mat.ptr[3];
2661 mat.ptr[5] = mat.ptr[4]*s.mat.ptr[1]+mat.ptr[5]*s.mat.ptr[3]+s.mat.ptr[5];
2662 mat.ptr[0] = t0;
2663 mat.ptr[2] = t2;
2664 mat.ptr[4] = t4;
2665 return this;
2668 /// Sets this matrix to the result of multiplication of [s] and `this` (S * this).
2669 /// Sets the transform to the result of multiplication of two transforms, of A = B*A.
2670 /// Group: matrices
2671 ref NVGMatrix premul() (in auto ref NVGMatrix s) {
2672 NVGMatrix s2 = s;
2673 s2.mul(this);
2674 mat[] = s2.mat[];
2675 return this;
2678 /// Multiply this matrix by [s], return result as new matrix.
2679 /// Performs operations in this left-to-right order.
2680 NVGMatrix opBinary(string op="*") (in auto ref NVGMatrix s) const {
2681 version(aliced) pragma(inline, true);
2682 NVGMatrix res = this;
2683 res.mul(s);
2684 return res;
2687 /// Multiply this matrix by [s].
2688 /// Performs operations in this left-to-right order.
2689 ref NVGMatrix opOpAssign(string op="*") (in auto ref NVGMatrix s) {
2690 version(aliced) pragma(inline, true);
2691 return this.mul(s);
2694 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.
2695 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.
2696 float rotation () const { pragma(inline, true); return nvg__atan2f(mat.ptr[1], mat.ptr[0]); } /// Returns rotation of this matrix.
2697 float tx () const { pragma(inline, true); return mat.ptr[4]; } /// Returns x translation of this matrix.
2698 float ty () const { pragma(inline, true); return mat.ptr[5]; } /// Returns y translation of this matrix.
2700 ref NVGMatrix scaleX (in float v) { pragma(inline, true); return scaleRotateTransform(v, scaleY, rotation, tx, ty); } /// Sets x scaling of this matrix.
2701 ref NVGMatrix scaleY (in float v) { pragma(inline, true); return scaleRotateTransform(scaleX, v, rotation, tx, ty); } /// Sets y scaling of this matrix.
2702 ref NVGMatrix rotation (in float v) { pragma(inline, true); return scaleRotateTransform(scaleX, scaleY, v, tx, ty); } /// Sets rotation of this matrix.
2703 ref NVGMatrix tx (in float v) { pragma(inline, true); mat.ptr[4] = v; return this; } /// Sets x translation of this matrix.
2704 ref NVGMatrix ty (in float v) { pragma(inline, true); mat.ptr[5] = v; return this; } /// Sets y translation of this matrix.
2706 /// Utility function to be used in `setXXX()`.
2707 /// This is the same as doing: `mat.identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster
2708 ref NVGMatrix scaleRotateTransform (in float xscale, in float yscale, in float a, in float tx, in float ty) {
2709 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2710 mat.ptr[0] = xscale*cs; mat.ptr[1] = yscale*sn;
2711 mat.ptr[2] = xscale*-sn; mat.ptr[3] = yscale*cs;
2712 mat.ptr[4] = tx; mat.ptr[5] = ty;
2713 return this;
2716 /// This is the same as doing: `mat.identity.rotate(a).translate(tx, ty)`, only faster
2717 ref NVGMatrix rotateTransform (in float a, in float tx, in float ty) {
2718 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2719 mat.ptr[0] = cs; mat.ptr[1] = sn;
2720 mat.ptr[2] = -sn; mat.ptr[3] = cs;
2721 mat.ptr[4] = tx; mat.ptr[5] = ty;
2722 return this;
2725 /// Returns new identity matrix.
2726 static NVGMatrix Identity () { pragma(inline, true); return NVGMatrix.init; }
2728 /// Returns new translation matrix.
2729 static NVGMatrix Translated (in float tx, in float ty) {
2730 version(aliced) pragma(inline, true);
2731 NVGMatrix res = void;
2732 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f;
2733 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f;
2734 res.mat.ptr[4] = tx; res.mat.ptr[5] = ty;
2735 return res;
2738 /// Returns new scaling matrix.
2739 static NVGMatrix Scaled (in float sx, in float sy) {
2740 version(aliced) pragma(inline, true);
2741 NVGMatrix res = void;
2742 res.mat.ptr[0] = sx; res.mat.ptr[1] = 0.0f;
2743 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = sy;
2744 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2745 return res;
2748 /// Returns new rotation matrix. Angle is specified in radians.
2749 static NVGMatrix Rotated (in float a) {
2750 version(aliced) pragma(inline, true);
2751 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2752 NVGMatrix res = void;
2753 res.mat.ptr[0] = cs; res.mat.ptr[1] = sn;
2754 res.mat.ptr[2] = -sn; res.mat.ptr[3] = cs;
2755 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2756 return res;
2759 /// Returns new x-skewing matrix. Angle is specified in radians.
2760 static NVGMatrix SkewedX (in float a) {
2761 version(aliced) pragma(inline, true);
2762 NVGMatrix res = void;
2763 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f;
2764 res.mat.ptr[2] = nvg__tanf(a); res.mat.ptr[3] = 1.0f;
2765 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2766 return res;
2769 /// Returns new y-skewing matrix. Angle is specified in radians.
2770 static NVGMatrix SkewedY (in float a) {
2771 version(aliced) pragma(inline, true);
2772 NVGMatrix res = void;
2773 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(a);
2774 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f;
2775 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2776 return res;
2779 /// Returns new xy-skewing matrix. Angles are specified in radians.
2780 static NVGMatrix SkewedXY (in float ax, in float ay) {
2781 version(aliced) pragma(inline, true);
2782 NVGMatrix res = void;
2783 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(ay);
2784 res.mat.ptr[2] = nvg__tanf(ax); res.mat.ptr[3] = 1.0f;
2785 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2786 return res;
2789 /// Utility function to be used in `setXXX()`.
2790 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster
2791 static NVGMatrix ScaledRotatedTransformed (in float xscale, in float yscale, in float a, in float tx, in float ty) {
2792 NVGMatrix res = void;
2793 res.scaleRotateTransform(xscale, yscale, a, tx, ty);
2794 return res;
2797 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).translate(tx, ty)`, only faster
2798 static NVGMatrix RotatedTransformed (in float a, in float tx, in float ty) {
2799 NVGMatrix res = void;
2800 res.rotateTransform(a, tx, ty);
2801 return res;
2806 /// Converts degrees to radians.
2807 /// Group: matrices
2808 public float nvgDegToRad() (in float deg) pure nothrow @safe @nogc { pragma(inline, true); return deg/180.0f*NVG_PI; }
2810 /// Converts radians to degrees.
2811 /// Group: matrices
2812 public float nvgRadToDeg() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad/NVG_PI*180.0f; }
2814 public alias nvgDegrees = nvgDegToRad; /// Use this like `42.nvgDegrees`
2815 public float nvgRadians() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad; } /// Use this like `0.1.nvgRadians`
2818 // ////////////////////////////////////////////////////////////////////////// //
2819 void nvg__setPaintColor() (ref NVGPaint p, in auto ref NVGColor color) nothrow @trusted @nogc {
2820 p.clear();
2821 p.xform.identity;
2822 p.radius = 0.0f;
2823 p.feather = 1.0f;
2824 p.innerColor = p.middleColor = p.outerColor = color;
2825 p.midp = -1;
2826 p.simpleColor = true;
2830 // ////////////////////////////////////////////////////////////////////////// //
2831 // State handling
2833 version(nanovega_debug_clipping) {
2834 public void nvgClipDumpOn (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, true); }
2835 public void nvgClipDumpOff (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, false); }
2838 /** Pushes and saves the current render state into a state stack.
2839 * A matching [restore] must be used to restore the state.
2840 * Returns `false` if state stack overflowed.
2842 * Group: state_handling
2844 public bool save (NVGContext ctx) nothrow @trusted @nogc {
2845 if (ctx.nstates >= NVG_MAX_STATES) return false;
2846 if (ctx.nstates > 0) {
2847 //memcpy(&ctx.states[ctx.nstates], &ctx.states[ctx.nstates-1], NVGstate.sizeof);
2848 ctx.states[ctx.nstates] = ctx.states[ctx.nstates-1];
2849 ctx.params.renderPushClip(ctx.params.userPtr);
2851 ++ctx.nstates;
2852 return true;
2855 /// Pops and restores current render state.
2856 /// Group: state_handling
2857 public bool restore (NVGContext ctx) nothrow @trusted @nogc {
2858 if (ctx.nstates <= 1) return false;
2859 ctx.states[ctx.nstates-1].clearPaint();
2860 ctx.params.renderPopClip(ctx.params.userPtr);
2861 --ctx.nstates;
2862 return true;
2865 /// Resets current render state to default values. Does not affect the render state stack.
2866 /// Group: state_handling
2867 public void reset (NVGContext ctx) nothrow @trusted @nogc {
2868 NVGstate* state = nvg__getState(ctx);
2869 state.clearPaint();
2871 nvg__setPaintColor(state.fill, nvgRGBA(255, 255, 255, 255));
2872 nvg__setPaintColor(state.stroke, nvgRGBA(0, 0, 0, 255));
2873 state.compositeOperation = nvg__compositeOperationState(NVGCompositeOperation.SourceOver);
2874 state.shapeAntiAlias = true;
2875 state.strokeWidth = 1.0f;
2876 state.miterLimit = 10.0f;
2877 state.lineCap = NVGLineCap.Butt;
2878 state.lineJoin = NVGLineCap.Miter;
2879 state.alpha = 1.0f;
2880 state.xform.identity;
2882 state.scissor.extent[] = -1.0f;
2884 state.fontSize = 16.0f;
2885 state.letterSpacing = 0.0f;
2886 state.lineHeight = 1.0f;
2887 state.fontBlur = 0.0f;
2888 state.textAlign.reset;
2889 state.fontId = 0;
2890 state.evenOddMode = false;
2891 state.dashCount = 0;
2892 state.lastFlattenDashCount = 0;
2893 state.dashStart = 0;
2894 state.firstDashIsGap = false;
2895 state.dasherActive = false;
2897 ctx.params.renderResetClip(ctx.params.userPtr);
2900 /** Returns `true` if we have any room in state stack.
2901 * It is guaranteed to have at least 32 stack slots.
2903 * Group: state_handling
2905 public bool canSave (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates < NVG_MAX_STATES); }
2907 /** Returns `true` if we have any saved state.
2909 * Group: state_handling
2911 public bool canRestore (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates > 1); }
2913 /// Returns `true` if rendering is currently blocked.
2914 /// Group: state_handling
2915 public bool renderBlocked (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.recblockdraw : false); }
2917 /// Blocks/unblocks rendering
2918 /// Group: state_handling
2919 public void renderBlocked (NVGContext ctx, bool v) pure nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null && ctx.contextAlive) ctx.recblockdraw = v; }
2921 /// Blocks/unblocks rendering; returns previous state.
2922 /// Group: state_handling
2923 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; }
2926 // ////////////////////////////////////////////////////////////////////////// //
2927 // Render styles
2929 /// Sets filling mode to "even-odd".
2930 /// Group: render_styles
2931 public void evenOddFill (NVGContext ctx) nothrow @trusted @nogc {
2932 NVGstate* state = nvg__getState(ctx);
2933 state.evenOddMode = true;
2936 /// Sets filling mode to "non-zero" (this is default mode).
2937 /// Group: render_styles
2938 public void nonZeroFill (NVGContext ctx) nothrow @trusted @nogc {
2939 NVGstate* state = nvg__getState(ctx);
2940 state.evenOddMode = false;
2943 /// Sets whether to draw antialias for [stroke] and [fill]. It's enabled by default.
2944 /// Group: render_styles
2945 public void shapeAntiAlias (NVGContext ctx, bool enabled) {
2946 NVGstate* state = nvg__getState(ctx);
2947 state.shapeAntiAlias = enabled;
2950 /// Sets the stroke width of the stroke style.
2951 /// Group: render_styles
2952 @scriptable
2953 public void strokeWidth (NVGContext ctx, float width) nothrow @trusted @nogc {
2954 NVGstate* state = nvg__getState(ctx);
2955 state.strokeWidth = width;
2958 /// Sets the miter limit of the stroke style. Miter limit controls when a sharp corner is beveled.
2959 /// Group: render_styles
2960 public void miterLimit (NVGContext ctx, float limit) nothrow @trusted @nogc {
2961 NVGstate* state = nvg__getState(ctx);
2962 state.miterLimit = limit;
2965 /// Sets how the end of the line (cap) is drawn,
2966 /// Can be one of: NVGLineCap.Butt (default), NVGLineCap.Round, NVGLineCap.Square.
2967 /// Group: render_styles
2968 public void lineCap (NVGContext ctx, NVGLineCap cap) nothrow @trusted @nogc {
2969 NVGstate* state = nvg__getState(ctx);
2970 state.lineCap = cap;
2973 /// Sets how sharp path corners are drawn.
2974 /// Can be one of NVGLineCap.Miter (default), NVGLineCap.Round, NVGLineCap.Bevel.
2975 /// Group: render_styles
2976 public void lineJoin (NVGContext ctx, NVGLineCap join) nothrow @trusted @nogc {
2977 NVGstate* state = nvg__getState(ctx);
2978 state.lineJoin = join;
2981 /// Sets stroke dashing, using (dash_length, gap_length) pairs.
2982 /// Current limit is 16 pairs.
2983 /// Resets dash start to zero.
2984 /// Group: render_styles
2985 public void setLineDash (NVGContext ctx, const(float)[] dashdata) nothrow @trusted @nogc {
2986 NVGstate* state = nvg__getState(ctx);
2987 state.dashCount = 0;
2988 state.dashStart = 0;
2989 state.firstDashIsGap = false;
2990 if (dashdata.length >= 2) {
2991 bool curFIsGap = true; // trick
2992 foreach (immutable idx, float f; dashdata) {
2993 curFIsGap = !curFIsGap;
2994 if (f < 0.01f) continue; // skip it
2995 if (idx == 0) {
2996 // register first dash
2997 state.firstDashIsGap = curFIsGap;
2998 state.dashes.ptr[state.dashCount++] = f;
2999 } else {
3000 if ((idx&1) != ((state.dashCount&1)^cast(uint)state.firstDashIsGap)) {
3001 // oops, continuation
3002 state.dashes[state.dashCount-1] += f;
3003 } else {
3004 if (state.dashCount == state.dashes.length) break;
3005 state.dashes[state.dashCount++] = f;
3009 if (state.dashCount&1) {
3010 if (state.dashCount == 1) {
3011 state.dashCount = 0;
3012 } else {
3013 assert(state.dashCount < state.dashes.length);
3014 state.dashes[state.dashCount++] = 0;
3017 // calculate total dash path length
3018 state.totalDashLen = 0;
3019 foreach (float f; state.dashes.ptr[0..state.dashCount]) state.totalDashLen += f;
3020 if (state.totalDashLen < 0.01f) {
3021 state.dashCount = 0; // nothing to do
3022 } else {
3023 if (state.lastFlattenDashCount != 0) state.lastFlattenDashCount = uint.max; // force re-flattening
3028 public alias lineDash = setLineDash; /// Ditto.
3030 /// Sets stroke dashing, using (dash_length, gap_length) pairs.
3031 /// Current limit is 16 pairs.
3032 /// Group: render_styles
3033 public void setLineDashStart (NVGContext ctx, in float dashStart) nothrow @trusted @nogc {
3034 NVGstate* state = nvg__getState(ctx);
3035 if (state.lastFlattenDashCount != 0 && state.dashStart != dashStart) {
3036 state.lastFlattenDashCount = uint.max; // force re-flattening
3038 state.dashStart = dashStart;
3041 public alias lineDashStart = setLineDashStart; /// Ditto.
3043 /// Sets the transparency applied to all rendered shapes.
3044 /// Already transparent paths will get proportionally more transparent as well.
3045 /// Group: render_styles
3046 public void globalAlpha (NVGContext ctx, float alpha) nothrow @trusted @nogc {
3047 NVGstate* state = nvg__getState(ctx);
3048 state.alpha = alpha;
3051 static if (NanoVegaHasArsdColor) {
3052 /// Sets current stroke style to a solid color.
3053 /// Group: render_styles
3054 public void strokeColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
3055 NVGstate* state = nvg__getState(ctx);
3056 nvg__setPaintColor(state.stroke, NVGColor(color));
3060 /// Sets current stroke style to a solid color.
3061 /// Group: render_styles
3062 public void strokeColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc {
3063 NVGstate* state = nvg__getState(ctx);
3064 nvg__setPaintColor(state.stroke, color);
3067 /// Sets current stroke style to a paint, which can be a one of the gradients or a pattern.
3068 /// Group: render_styles
3069 public void strokePaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc {
3070 NVGstate* state = nvg__getState(ctx);
3071 state.stroke = paint;
3072 //nvgTransformMultiply(state.stroke.xform[], state.xform[]);
3073 state.stroke.xform.mul(state.xform);
3076 // this is a hack to work around https://issues.dlang.org/show_bug.cgi?id=16206
3077 // for scriptable reflection. it just needs to be declared first among the overloads
3078 private void fillColor (NVGContext ctx) nothrow @trusted @nogc { }
3080 static if (NanoVegaHasArsdColor) {
3081 /// Sets current fill style to a solid color.
3082 /// Group: render_styles
3083 @scriptable
3084 public void fillColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
3085 NVGstate* state = nvg__getState(ctx);
3086 nvg__setPaintColor(state.fill, NVGColor(color));
3090 /// Sets current fill style to a solid color.
3091 /// Group: render_styles
3092 public void fillColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc {
3093 NVGstate* state = nvg__getState(ctx);
3094 nvg__setPaintColor(state.fill, color);
3097 /// Sets current fill style to a paint, which can be a one of the gradients or a pattern.
3098 /// Group: render_styles
3099 public void fillPaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc {
3100 NVGstate* state = nvg__getState(ctx);
3101 state.fill = paint;
3102 //nvgTransformMultiply(state.fill.xform[], state.xform[]);
3103 state.fill.xform.mul(state.xform);
3106 /// Sets current fill style to a multistop linear gradient.
3107 /// Group: render_styles
3108 public void fillPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc {
3109 if (!lgs.valid) {
3110 NVGPaint p = void;
3111 memset(&p, 0, p.sizeof);
3112 nvg__setPaintColor(p, NVGColor.red);
3113 ctx.fillPaint = p;
3114 } else if (lgs.midp >= -1) {
3115 //{ import core.stdc.stdio; printf("SIMPLE! midp=%f\n", cast(double)lgs.midp); }
3116 ctx.fillPaint = ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
3117 } else {
3118 ctx.fillPaint = ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
3122 /// Returns current transformation matrix.
3123 /// Group: render_transformations
3124 public NVGMatrix currTransform (NVGContext ctx) pure nothrow @trusted @nogc {
3125 NVGstate* state = nvg__getState(ctx);
3126 return state.xform;
3129 /// Sets current transformation matrix.
3130 /// Group: render_transformations
3131 public void currTransform() (NVGContext ctx, in auto ref NVGMatrix m) nothrow @trusted @nogc {
3132 NVGstate* state = nvg__getState(ctx);
3133 state.xform = m;
3136 /// Resets current transform to an identity matrix.
3137 /// Group: render_transformations
3138 @scriptable
3139 public void resetTransform (NVGContext ctx) nothrow @trusted @nogc {
3140 NVGstate* state = nvg__getState(ctx);
3141 state.xform.identity;
3144 /// Premultiplies current coordinate system by specified matrix.
3145 /// Group: render_transformations
3146 public void transform() (NVGContext ctx, in auto ref NVGMatrix mt) nothrow @trusted @nogc {
3147 NVGstate* state = nvg__getState(ctx);
3148 //nvgTransformPremultiply(state.xform[], t[]);
3149 state.xform *= mt;
3152 /// Translates current coordinate system.
3153 /// Group: render_transformations
3154 @scriptable
3155 public void translate (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
3156 NVGstate* state = nvg__getState(ctx);
3157 //NVGMatrix t = void;
3158 //nvgTransformTranslate(t[], x, y);
3159 //nvgTransformPremultiply(state.xform[], t[]);
3160 state.xform.premul(NVGMatrix.Translated(x, y));
3163 /// Rotates current coordinate system. Angle is specified in radians.
3164 /// Group: render_transformations
3165 @scriptable
3166 public void rotate (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3167 NVGstate* state = nvg__getState(ctx);
3168 //NVGMatrix t = void;
3169 //nvgTransformRotate(t[], angle);
3170 //nvgTransformPremultiply(state.xform[], t[]);
3171 state.xform.premul(NVGMatrix.Rotated(angle));
3174 /// Skews the current coordinate system along X axis. Angle is specified in radians.
3175 /// Group: render_transformations
3176 @scriptable
3177 public void skewX (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3178 NVGstate* state = nvg__getState(ctx);
3179 //NVGMatrix t = void;
3180 //nvgTransformSkewX(t[], angle);
3181 //nvgTransformPremultiply(state.xform[], t[]);
3182 state.xform.premul(NVGMatrix.SkewedX(angle));
3185 /// Skews the current coordinate system along Y axis. Angle is specified in radians.
3186 /// Group: render_transformations
3187 @scriptable
3188 public void skewY (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3189 NVGstate* state = nvg__getState(ctx);
3190 //NVGMatrix t = void;
3191 //nvgTransformSkewY(t[], angle);
3192 //nvgTransformPremultiply(state.xform[], t[]);
3193 state.xform.premul(NVGMatrix.SkewedY(angle));
3196 /// Scales the current coordinate system.
3197 /// Group: render_transformations
3198 @scriptable
3199 public void scale (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
3200 NVGstate* state = nvg__getState(ctx);
3201 //NVGMatrix t = void;
3202 //nvgTransformScale(t[], x, y);
3203 //nvgTransformPremultiply(state.xform[], t[]);
3204 state.xform.premul(NVGMatrix.Scaled(x, y));
3208 // ////////////////////////////////////////////////////////////////////////// //
3209 // Images
3211 /// Creates image by loading it from the disk from specified file name.
3212 /// Returns handle to the image or 0 on error.
3213 /// Group: images
3214 public NVGImage createImage() (NVGContext ctx, const(char)[] filename, const(NVGImageFlag)[] imageFlagsList...) {
3215 static if (NanoVegaHasArsdImage) {
3216 import arsd.image;
3217 // do we have new arsd API to load images?
3218 static if (!is(typeof(MemoryImage.fromImageFile)) || !is(typeof(MemoryImage.clearInternal))) {
3219 static assert(0, "Sorry, your ARSD is too old. Please, update it.");
3221 try {
3222 auto oimg = MemoryImage.fromImageFile(filename);
3223 if (auto img = cast(TrueColorImage)oimg) {
3224 scope(exit) oimg.clearInternal();
3225 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList);
3226 } else {
3227 TrueColorImage img = oimg.getAsTrueColorImage;
3228 scope(exit) img.clearInternal();
3229 oimg.clearInternal(); // drop original image, as `getAsTrueColorImage()` MUST create a new one here
3230 oimg = null;
3231 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList);
3233 } catch (Exception) {}
3234 return NVGImage.init;
3235 } else {
3236 import std.internal.cstring;
3237 ubyte* img;
3238 int w, h, n;
3239 stbi_set_unpremultiply_on_load(1);
3240 stbi_convert_iphone_png_to_rgb(1);
3241 img = stbi_load(filename.tempCString, &w, &h, &n, 4);
3242 if (img is null) {
3243 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
3244 return NVGImage.init;
3246 auto image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList);
3247 stbi_image_free(img);
3248 return image;
3252 static if (NanoVegaHasArsdImage) {
3253 /// Creates image by loading it from the specified memory image.
3254 /// Returns handle to the image or 0 on error.
3255 /// Group: images
3256 public NVGImage createImageFromMemoryImage() (NVGContext ctx, MemoryImage img, const(NVGImageFlag)[] imageFlagsList...) {
3257 if (img is null) return NVGImage.init;
3258 if (auto tc = cast(TrueColorImage)img) {
3259 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList);
3260 } else {
3261 auto tc = img.getAsTrueColorImage;
3262 scope(exit) tc.clearInternal(); // here, it is guaranteed that `tc` is newly allocated image, so it is safe to kill it
3263 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList);
3266 } else {
3267 /// Creates image by loading it from the specified chunk of memory.
3268 /// Returns handle to the image or 0 on error.
3269 /// Group: images
3270 public NVGImage createImageMem() (NVGContext ctx, const(ubyte)* data, int ndata, const(NVGImageFlag)[] imageFlagsList...) {
3271 int w, h, n, image;
3272 ubyte* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4);
3273 if (img is null) {
3274 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
3275 return NVGImage.init;
3277 image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList);
3278 stbi_image_free(img);
3279 return image;
3283 /// Creates image from specified image data.
3284 /// Returns handle to the image or 0 on error.
3285 /// Group: images
3286 public NVGImage createImageRGBA (NVGContext ctx, int w, int h, const(void)[] data, const(NVGImageFlag)[] imageFlagsList...) nothrow @trusted @nogc {
3287 if (w < 1 || h < 1 || data.length < w*h*4) return NVGImage.init;
3288 uint imageFlags = 0;
3289 foreach (immutable uint flag; imageFlagsList) imageFlags |= flag;
3290 NVGImage res;
3291 res.id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.RGBA, w, h, imageFlags, cast(const(ubyte)*)data.ptr);
3292 if (res.id > 0) {
3293 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); }
3294 res.ctx = ctx;
3295 ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount
3297 return res;
3300 /// Updates image data specified by image handle.
3301 /// Group: images
3302 public void updateImage() (NVGContext ctx, auto ref NVGImage image, const(void)[] data) nothrow @trusted @nogc {
3303 if (image.valid) {
3304 int w, h;
3305 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3306 ctx.params.renderGetTextureSize(ctx.params.userPtr, image.id, &w, &h);
3307 ctx.params.renderUpdateTexture(ctx.params.userPtr, image.id, 0, 0, w, h, cast(const(ubyte)*)data.ptr);
3311 /// Returns the dimensions of a created image.
3312 /// Group: images
3313 public void imageSize() (NVGContext ctx, in auto ref NVGImage image, out int w, out int h) nothrow @trusted @nogc {
3314 if (image.valid) {
3315 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3316 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, image.id, &w, &h);
3320 /// Deletes created image.
3321 /// Group: images
3322 public void deleteImage() (NVGContext ctx, ref NVGImage image) nothrow @trusted @nogc {
3323 if (ctx is null || !image.valid) return;
3324 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3325 image.clear();
3329 // ////////////////////////////////////////////////////////////////////////// //
3330 // Paints
3332 static if (NanoVegaHasArsdColor) {
3333 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates
3334 * of the linear gradient, icol specifies the start color and ocol the end color.
3335 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3337 * Group: paints
3339 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 {
3340 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), NVGColor(ocol));
3342 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
3343 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
3344 * range `(0..1)`, and ocol the end color.
3345 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3347 * Group: paints
3349 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 {
3350 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), midp, NVGColor(mcol), NVGColor(ocol));
3354 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates
3355 * of the linear gradient, icol specifies the start color and ocol the end color.
3356 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3358 * Group: paints
3360 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 {
3361 enum large = 1e5f;
3363 NVGPaint p = void;
3364 memset(&p, 0, p.sizeof);
3365 p.simpleColor = false;
3367 // Calculate transform aligned to the line
3368 float dx = ex-sx;
3369 float dy = ey-sy;
3370 immutable float d = nvg__sqrtf(dx*dx+dy*dy);
3371 if (d > 0.0001f) {
3372 dx /= d;
3373 dy /= d;
3374 } else {
3375 dx = 0;
3376 dy = 1;
3379 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx;
3380 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy;
3381 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large;
3383 p.extent.ptr[0] = large;
3384 p.extent.ptr[1] = large+d*0.5f;
3386 p.radius = 0.0f;
3388 p.feather = nvg__max(NVG_MIN_FEATHER, d);
3390 p.innerColor = p.middleColor = icol;
3391 p.outerColor = ocol;
3392 p.midp = -1;
3394 return p;
3397 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
3398 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
3399 * range `(0..1)`, and ocol the end color.
3400 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3402 * Group: paints
3404 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 {
3405 enum large = 1e5f;
3407 NVGPaint p = void;
3408 memset(&p, 0, p.sizeof);
3409 p.simpleColor = false;
3411 // Calculate transform aligned to the line
3412 float dx = ex-sx;
3413 float dy = ey-sy;
3414 immutable float d = nvg__sqrtf(dx*dx+dy*dy);
3415 if (d > 0.0001f) {
3416 dx /= d;
3417 dy /= d;
3418 } else {
3419 dx = 0;
3420 dy = 1;
3423 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx;
3424 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy;
3425 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large;
3427 p.extent.ptr[0] = large;
3428 p.extent.ptr[1] = large+d*0.5f;
3430 p.radius = 0.0f;
3432 p.feather = nvg__max(NVG_MIN_FEATHER, d);
3434 if (midp <= 0) {
3435 p.innerColor = p.middleColor = mcol;
3436 p.midp = -1;
3437 } else if (midp > 1) {
3438 p.innerColor = p.middleColor = icol;
3439 p.midp = -1;
3440 } else {
3441 p.innerColor = icol;
3442 p.middleColor = mcol;
3443 p.midp = midp;
3445 p.outerColor = ocol;
3447 return p;
3450 static if (NanoVegaHasArsdColor) {
3451 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify
3452 * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
3453 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3455 * Group: paints
3457 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 {
3458 return ctx.radialGradient(cx, cy, inr, outr, NVGColor(icol), NVGColor(ocol));
3462 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify
3463 * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
3464 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3466 * Group: paints
3468 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 {
3469 immutable float r = (inr+outr)*0.5f;
3470 immutable float f = (outr-inr);
3472 NVGPaint p = void;
3473 memset(&p, 0, p.sizeof);
3474 p.simpleColor = false;
3476 p.xform.identity;
3477 p.xform.mat.ptr[4] = cx;
3478 p.xform.mat.ptr[5] = cy;
3480 p.extent.ptr[0] = r;
3481 p.extent.ptr[1] = r;
3483 p.radius = r;
3485 p.feather = nvg__max(NVG_MIN_FEATHER, f);
3487 p.innerColor = p.middleColor = icol;
3488 p.outerColor = ocol;
3489 p.midp = -1;
3491 return p;
3494 static if (NanoVegaHasArsdColor) {
3495 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
3496 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle,
3497 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
3498 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
3499 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3501 * Group: paints
3503 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 {
3504 return ctx.boxGradient(x, y, w, h, r, f, NVGColor(icol), NVGColor(ocol));
3508 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
3509 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle,
3510 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
3511 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
3512 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3514 * Group: paints
3516 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 {
3517 NVGPaint p = void;
3518 memset(&p, 0, p.sizeof);
3519 p.simpleColor = false;
3521 p.xform.identity;
3522 p.xform.mat.ptr[4] = x+w*0.5f;
3523 p.xform.mat.ptr[5] = y+h*0.5f;
3525 p.extent.ptr[0] = w*0.5f;
3526 p.extent.ptr[1] = h*0.5f;
3528 p.radius = r;
3530 p.feather = nvg__max(NVG_MIN_FEATHER, f);
3532 p.innerColor = p.middleColor = icol;
3533 p.outerColor = ocol;
3534 p.midp = -1;
3536 return p;
3539 /** Creates and returns an image pattern. Parameters `(cx, cy)` specify the left-top location of the image pattern,
3540 * `(w, h)` the size of one image, [angle] rotation around the top-left corner, [image] is handle to the image to render.
3541 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3543 * Group: paints
3545 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 {
3546 NVGPaint p = void;
3547 memset(&p, 0, p.sizeof);
3548 p.simpleColor = false;
3550 p.xform.identity.rotate(angle);
3551 p.xform.mat.ptr[4] = cx;
3552 p.xform.mat.ptr[5] = cy;
3554 p.extent.ptr[0] = w;
3555 p.extent.ptr[1] = h;
3557 p.image = image;
3559 p.innerColor = p.middleColor = p.outerColor = nvgRGBAf(1, 1, 1, alpha);
3560 p.midp = -1;
3562 return p;
3565 /// Linear gradient with multiple stops.
3566 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3567 /// Group: paints
3568 public struct NVGLGS {
3569 private:
3570 NVGColor ic, mc, oc; // inner, middle, out
3571 float midp;
3572 NVGImage imgid;
3573 // [imagePattern] arguments
3574 float cx, cy, dimx, dimy; // dimx and dimy are ex and ey for simple gradients
3575 public float angle; ///
3577 public:
3578 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (imgid.valid || midp >= -1); } ///
3579 void clear () nothrow @safe @nogc { pragma(inline, true); imgid.clear(); midp = float.nan; } ///
3582 /** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops].
3583 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3585 * $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3586 * Group: paints
3588 public NVGPaint asPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc {
3589 if (!lgs.valid) {
3590 NVGPaint p = void;
3591 memset(&p, 0, p.sizeof);
3592 nvg__setPaintColor(p, NVGColor.red);
3593 return p;
3594 } else if (lgs.midp >= -1) {
3595 return ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
3596 } else {
3597 return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
3601 /// Gradient Stop Point.
3602 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3603 /// Group: paints
3604 public struct NVGGradientStop {
3605 float offset = 0; /// [0..1]
3606 NVGColor color; ///
3608 this() (in float aofs, in auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } ///
3609 static if (NanoVegaHasArsdColor) {
3610 this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } ///
3614 /// Create linear gradient data suitable to use with `linearGradient(res)`.
3615 /// Don't forget to destroy the result when you don't need it anymore with `ctx.kill(res);`.
3616 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3617 /// Group: paints
3618 public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, const(NVGGradientStop)[] stops...) nothrow @trusted @nogc {
3619 // based on the code by Jorge Acereda <jacereda@gmail.com>
3620 enum NVG_GRADIENT_SAMPLES = 1024;
3621 static void gradientSpan (uint* dst, const(NVGGradientStop)* s0, const(NVGGradientStop)* s1) nothrow @trusted @nogc {
3622 immutable float s0o = nvg__clamp(s0.offset, 0.0f, 1.0f);
3623 immutable float s1o = nvg__clamp(s1.offset, 0.0f, 1.0f);
3624 uint s = cast(uint)(s0o*NVG_GRADIENT_SAMPLES);
3625 uint e = cast(uint)(s1o*NVG_GRADIENT_SAMPLES);
3626 uint sc = 0xffffffffU;
3627 uint sh = 24;
3628 uint r = cast(uint)(s0.color.rgba[0]*sc);
3629 uint g = cast(uint)(s0.color.rgba[1]*sc);
3630 uint b = cast(uint)(s0.color.rgba[2]*sc);
3631 uint a = cast(uint)(s0.color.rgba[3]*sc);
3632 uint dr = cast(uint)((s1.color.rgba[0]*sc-r)/(e-s));
3633 uint dg = cast(uint)((s1.color.rgba[1]*sc-g)/(e-s));
3634 uint db = cast(uint)((s1.color.rgba[2]*sc-b)/(e-s));
3635 uint da = cast(uint)((s1.color.rgba[3]*sc-a)/(e-s));
3636 dst += s;
3637 foreach (immutable _; s..e) {
3638 version(BigEndian) {
3639 *dst++ = ((r>>sh)<<24)+((g>>sh)<<16)+((b>>sh)<<8)+((a>>sh)<<0);
3640 } else {
3641 *dst++ = ((a>>sh)<<24)+((b>>sh)<<16)+((g>>sh)<<8)+((r>>sh)<<0);
3643 r += dr;
3644 g += dg;
3645 b += db;
3646 a += da;
3650 NVGLGS res;
3651 res.cx = sx;
3652 res.cy = sy;
3654 if (stops.length == 2 && stops.ptr[0].offset <= 0 && stops.ptr[1].offset >= 1) {
3655 // create simple linear gradient
3656 res.ic = res.mc = stops.ptr[0].color;
3657 res.oc = stops.ptr[1].color;
3658 res.midp = -1;
3659 res.dimx = ex;
3660 res.dimy = ey;
3661 } else if (stops.length == 3 && stops.ptr[0].offset <= 0 && stops.ptr[2].offset >= 1) {
3662 // create simple linear gradient with middle stop
3663 res.ic = stops.ptr[0].color;
3664 res.mc = stops.ptr[1].color;
3665 res.oc = stops.ptr[2].color;
3666 res.midp = stops.ptr[1].offset;
3667 res.dimx = ex;
3668 res.dimy = ey;
3669 } else {
3670 // create image gradient
3671 uint[NVG_GRADIENT_SAMPLES] data = void;
3672 immutable float w = ex-sx;
3673 immutable float h = ey-sy;
3674 res.dimx = nvg__sqrtf(w*w+h*h);
3675 res.dimy = 1; //???
3677 res.angle =
3678 (/*nvg__absf(h) < 0.0001 ? 0 :
3679 nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/
3680 nvg__atan2f(h/*ey-sy*/, w/*ex-sx*/));
3682 if (stops.length > 0) {
3683 auto s0 = NVGGradientStop(0, nvgRGBAf(0, 0, 0, 1));
3684 auto s1 = NVGGradientStop(1, nvgRGBAf(1, 1, 1, 1));
3685 if (stops.length > 64) stops = stops[0..64];
3686 if (stops.length) {
3687 s0.color = stops[0].color;
3688 s1.color = stops[$-1].color;
3690 gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1));
3691 foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1);
3692 gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1);
3693 res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[]/*, NVGImageFlag.RepeatX, NVGImageFlag.RepeatY*/);
3696 return res;
3700 // ////////////////////////////////////////////////////////////////////////// //
3701 // Scissoring
3703 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform.
3704 /// Group: scissoring
3705 public void scissor (NVGContext ctx, in float x, in float y, float w, float h) nothrow @trusted @nogc {
3706 NVGstate* state = nvg__getState(ctx);
3708 w = nvg__max(0.0f, w);
3709 h = nvg__max(0.0f, h);
3711 state.scissor.xform.identity;
3712 state.scissor.xform.mat.ptr[4] = x+w*0.5f;
3713 state.scissor.xform.mat.ptr[5] = y+h*0.5f;
3714 //nvgTransformMultiply(state.scissor.xform[], state.xform[]);
3715 state.scissor.xform.mul(state.xform);
3717 state.scissor.extent.ptr[0] = w*0.5f;
3718 state.scissor.extent.ptr[1] = h*0.5f;
3721 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform.
3722 /// Arguments: [x, y, w, h]*
3723 /// Group: scissoring
3724 public void scissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
3725 enum ArgC = 4;
3726 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [scissor] call");
3727 if (args.length < ArgC) return;
3728 NVGstate* state = nvg__getState(ctx);
3729 const(float)* aptr = args.ptr;
3730 foreach (immutable idx; 0..args.length/ArgC) {
3731 immutable x = *aptr++;
3732 immutable y = *aptr++;
3733 immutable w = nvg__max(0.0f, *aptr++);
3734 immutable h = nvg__max(0.0f, *aptr++);
3736 state.scissor.xform.identity;
3737 state.scissor.xform.mat.ptr[4] = x+w*0.5f;
3738 state.scissor.xform.mat.ptr[5] = y+h*0.5f;
3739 //nvgTransformMultiply(state.scissor.xform[], state.xform[]);
3740 state.scissor.xform.mul(state.xform);
3742 state.scissor.extent.ptr[0] = w*0.5f;
3743 state.scissor.extent.ptr[1] = h*0.5f;
3747 void nvg__isectRects (float* dst, float ax, float ay, float aw, float ah, float bx, float by, float bw, float bh) nothrow @trusted @nogc {
3748 immutable float minx = nvg__max(ax, bx);
3749 immutable float miny = nvg__max(ay, by);
3750 immutable float maxx = nvg__min(ax+aw, bx+bw);
3751 immutable float maxy = nvg__min(ay+ah, by+bh);
3752 dst[0] = minx;
3753 dst[1] = miny;
3754 dst[2] = nvg__max(0.0f, maxx-minx);
3755 dst[3] = nvg__max(0.0f, maxy-miny);
3758 /** Intersects current scissor rectangle with the specified rectangle.
3759 * The scissor rectangle is transformed by the current transform.
3760 * Note: in case the rotation of previous scissor rect differs from
3761 * the current one, the intersection will be done between the specified
3762 * rectangle and the previous scissor rectangle transformed in the current
3763 * transform space. The resulting shape is always rectangle.
3765 * Group: scissoring
3767 public void intersectScissor (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc {
3768 NVGstate* state = nvg__getState(ctx);
3770 // If no previous scissor has been set, set the scissor as current scissor.
3771 if (state.scissor.extent.ptr[0] < 0) {
3772 ctx.scissor(x, y, w, h);
3773 return;
3776 NVGMatrix pxform = void;
3777 NVGMatrix invxorm = void;
3778 float[4] rect = void;
3780 // Transform the current scissor rect into current transform space.
3781 // If there is difference in rotation, this will be approximation.
3782 //memcpy(pxform.mat.ptr, state.scissor.xform.ptr, float.sizeof*6);
3783 pxform = state.scissor.xform;
3784 immutable float ex = state.scissor.extent.ptr[0];
3785 immutable float ey = state.scissor.extent.ptr[1];
3786 //nvgTransformInverse(invxorm[], state.xform[]);
3787 invxorm = state.xform.inverted;
3788 //nvgTransformMultiply(pxform[], invxorm[]);
3789 pxform.mul(invxorm);
3790 immutable float tex = ex*nvg__absf(pxform.mat.ptr[0])+ey*nvg__absf(pxform.mat.ptr[2]);
3791 immutable float tey = ex*nvg__absf(pxform.mat.ptr[1])+ey*nvg__absf(pxform.mat.ptr[3]);
3793 // Intersect rects.
3794 nvg__isectRects(rect.ptr, pxform.mat.ptr[4]-tex, pxform.mat.ptr[5]-tey, tex*2, tey*2, x, y, w, h);
3796 //ctx.scissor(rect.ptr[0], rect.ptr[1], rect.ptr[2], rect.ptr[3]);
3797 ctx.scissor(rect.ptr[0..4]);
3800 /** Intersects current scissor rectangle with the specified rectangle.
3801 * The scissor rectangle is transformed by the current transform.
3802 * Note: in case the rotation of previous scissor rect differs from
3803 * the current one, the intersection will be done between the specified
3804 * rectangle and the previous scissor rectangle transformed in the current
3805 * transform space. The resulting shape is always rectangle.
3807 * Arguments: [x, y, w, h]*
3809 * Group: scissoring
3811 public void intersectScissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
3812 enum ArgC = 4;
3813 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [intersectScissor] call");
3814 if (args.length < ArgC) return;
3815 const(float)* aptr = args.ptr;
3816 foreach (immutable idx; 0..args.length/ArgC) {
3817 immutable x = *aptr++;
3818 immutable y = *aptr++;
3819 immutable w = *aptr++;
3820 immutable h = *aptr++;
3821 ctx.intersectScissor(x, y, w, h);
3825 /// Reset and disables scissoring.
3826 /// Group: scissoring
3827 public void resetScissor (NVGContext ctx) nothrow @trusted @nogc {
3828 NVGstate* state = nvg__getState(ctx);
3829 state.scissor.xform.mat[] = 0.0f;
3830 state.scissor.extent[] = -1.0f;
3834 // ////////////////////////////////////////////////////////////////////////// //
3835 // Render-Time Affine Transformations
3837 /// Sets GPU affine transformatin matrix. Don't do scaling or skewing here.
3838 /// This matrix won't be saved/restored with context state save/restore operations, as it is not a part of that state.
3839 /// Group: gpu_affine
3840 public void affineGPU() (NVGContext ctx, in auto ref NVGMatrix mat) nothrow @trusted @nogc {
3841 ctx.gpuAffine = mat;
3842 ctx.params.renderSetAffine(ctx.params.userPtr, ctx.gpuAffine);
3845 /// Get current GPU affine transformatin matrix.
3846 /// Group: gpu_affine
3847 public NVGMatrix affineGPU (NVGContext ctx) nothrow @safe @nogc {
3848 pragma(inline, true);
3849 return ctx.gpuAffine;
3852 /// "Untransform" point using current GPU affine matrix.
3853 /// Group: gpu_affine
3854 public void gpuUntransformPoint (NVGContext ctx, float *dx, float *dy, in float x, in float y) nothrow @safe @nogc {
3855 if (ctx.gpuAffine.isIdentity) {
3856 if (dx !is null) *dx = x;
3857 if (dy !is null) *dy = y;
3858 } else {
3859 // inverse GPU transformation
3860 NVGMatrix igpu = ctx.gpuAffine.inverted;
3861 igpu.point(dx, dy, x, y);
3866 // ////////////////////////////////////////////////////////////////////////// //
3867 // rasterization (tesselation) code
3869 int nvg__ptEquals (float x1, float y1, float x2, float y2, float tol) pure nothrow @safe @nogc {
3870 //pragma(inline, true);
3871 immutable float dx = x2-x1;
3872 immutable float dy = y2-y1;
3873 return dx*dx+dy*dy < tol*tol;
3876 float nvg__distPtSeg (float x, float y, float px, float py, float qx, float qy) pure nothrow @safe @nogc {
3877 immutable float pqx = qx-px;
3878 immutable float pqy = qy-py;
3879 float dx = x-px;
3880 float dy = y-py;
3881 immutable float d = pqx*pqx+pqy*pqy;
3882 float t = pqx*dx+pqy*dy;
3883 if (d > 0) t /= d;
3884 if (t < 0) t = 0; else if (t > 1) t = 1;
3885 dx = px+t*pqx-x;
3886 dy = py+t*pqy-y;
3887 return dx*dx+dy*dy;
3890 void nvg__appendCommands(bool useCommand=true) (NVGContext ctx, Command acmd, const(float)[] vals...) nothrow @trusted @nogc {
3891 int nvals = cast(int)vals.length;
3892 static if (useCommand) {
3893 enum addon = 1;
3894 } else {
3895 enum addon = 0;
3896 if (nvals == 0) return; // nothing to do
3899 NVGstate* state = nvg__getState(ctx);
3901 if (ctx.ncommands+nvals+addon > ctx.ccommands) {
3902 //int ccommands = ctx.ncommands+nvals+ctx.ccommands/2;
3903 int ccommands = ((ctx.ncommands+(nvals+addon))|0xfff)+1;
3904 float* commands = cast(float*)realloc(ctx.commands, float.sizeof*ccommands);
3905 if (commands is null) assert(0, "NanoVega: out of memory");
3906 ctx.commands = commands;
3907 ctx.ccommands = ccommands;
3908 assert(ctx.ncommands+(nvals+addon) <= ctx.ccommands);
3911 static if (!useCommand) acmd = cast(Command)vals.ptr[0];
3913 if (acmd != Command.Close && acmd != Command.Winding) {
3914 //assert(nvals+addon >= 3);
3915 ctx.commandx = vals.ptr[nvals-2];
3916 ctx.commandy = vals.ptr[nvals-1];
3919 // copy commands
3920 float* vp = ctx.commands+ctx.ncommands;
3921 static if (useCommand) {
3922 vp[0] = cast(float)acmd;
3923 if (nvals > 0) memcpy(vp+1, vals.ptr, nvals*float.sizeof);
3924 } else {
3925 memcpy(vp, vals.ptr, nvals*float.sizeof);
3927 ctx.ncommands += nvals+addon;
3929 // transform commands
3930 int i = nvals+addon;
3931 while (i > 0) {
3932 int nlen = 1;
3933 final switch (cast(Command)(*vp)) {
3934 case Command.MoveTo:
3935 case Command.LineTo:
3936 assert(i >= 3);
3937 state.xform.point(vp+1, vp+2, vp[1], vp[2]);
3938 nlen = 3;
3939 break;
3940 case Command.BezierTo:
3941 assert(i >= 7);
3942 state.xform.point(vp+1, vp+2, vp[1], vp[2]);
3943 state.xform.point(vp+3, vp+4, vp[3], vp[4]);
3944 state.xform.point(vp+5, vp+6, vp[5], vp[6]);
3945 nlen = 7;
3946 break;
3947 case Command.Close:
3948 nlen = 1;
3949 break;
3950 case Command.Winding:
3951 nlen = 2;
3952 break;
3954 assert(nlen > 0 && nlen <= i);
3955 i -= nlen;
3956 vp += nlen;
3960 void nvg__clearPathCache (NVGContext ctx) nothrow @trusted @nogc {
3961 // no need to clear paths, as data is not copied there
3962 //foreach (ref p; ctx.cache.paths[0..ctx.cache.npaths]) p.clear();
3963 ctx.cache.npoints = 0;
3964 ctx.cache.npaths = 0;
3965 ctx.cache.fillReady = ctx.cache.strokeReady = false;
3966 ctx.cache.clipmode = NVGClipMode.None;
3969 NVGpath* nvg__lastPath (NVGContext ctx) nothrow @trusted @nogc {
3970 return (ctx.cache.npaths > 0 ? &ctx.cache.paths[ctx.cache.npaths-1] : null);
3973 void nvg__addPath (NVGContext ctx) nothrow @trusted @nogc {
3974 import core.stdc.stdlib : realloc;
3975 import core.stdc.string : memset;
3977 if (ctx.cache.npaths+1 > ctx.cache.cpaths) {
3978 int cpaths = ctx.cache.npaths+1+ctx.cache.cpaths/2;
3979 NVGpath* paths = cast(NVGpath*)realloc(ctx.cache.paths, NVGpath.sizeof*cpaths);
3980 if (paths is null) assert(0, "NanoVega: out of memory");
3981 ctx.cache.paths = paths;
3982 ctx.cache.cpaths = cpaths;
3985 NVGpath* path = &ctx.cache.paths[ctx.cache.npaths++];
3986 memset(path, 0, NVGpath.sizeof);
3987 path.first = ctx.cache.npoints;
3988 path.mWinding = NVGWinding.CCW;
3991 NVGpoint* nvg__lastPoint (NVGContext ctx) nothrow @trusted @nogc {
3992 return (ctx.cache.npoints > 0 ? &ctx.cache.points[ctx.cache.npoints-1] : null);
3995 void nvg__addPoint (NVGContext ctx, float x, float y, int flags) nothrow @trusted @nogc {
3996 NVGpath* path = nvg__lastPath(ctx);
3997 if (path is null) return;
3999 if (path.count > 0 && ctx.cache.npoints > 0) {
4000 NVGpoint* pt = nvg__lastPoint(ctx);
4001 if (nvg__ptEquals(pt.x, pt.y, x, y, ctx.distTol)) {
4002 pt.flags |= flags;
4003 return;
4007 if (ctx.cache.npoints+1 > ctx.cache.cpoints) {
4008 int cpoints = ctx.cache.npoints+1+ctx.cache.cpoints/2;
4009 NVGpoint* points = cast(NVGpoint*)realloc(ctx.cache.points, NVGpoint.sizeof*cpoints);
4010 if (points is null) return;
4011 ctx.cache.points = points;
4012 ctx.cache.cpoints = cpoints;
4015 NVGpoint* pt = &ctx.cache.points[ctx.cache.npoints];
4016 memset(pt, 0, (*pt).sizeof);
4017 pt.x = x;
4018 pt.y = y;
4019 pt.flags = cast(ubyte)flags;
4021 ++ctx.cache.npoints;
4022 ++path.count;
4025 void nvg__closePath (NVGContext ctx) nothrow @trusted @nogc {
4026 NVGpath* path = nvg__lastPath(ctx);
4027 if (path is null) return;
4028 path.closed = true;
4031 void nvg__pathWinding (NVGContext ctx, NVGWinding winding) nothrow @trusted @nogc {
4032 NVGpath* path = nvg__lastPath(ctx);
4033 if (path is null) return;
4034 path.mWinding = winding;
4037 float nvg__getAverageScale() (in auto ref NVGMatrix t) /*pure*/ nothrow @trusted @nogc {
4038 immutable float sx = nvg__sqrtf(t.mat.ptr[0]*t.mat.ptr[0]+t.mat.ptr[2]*t.mat.ptr[2]);
4039 immutable float sy = nvg__sqrtf(t.mat.ptr[1]*t.mat.ptr[1]+t.mat.ptr[3]*t.mat.ptr[3]);
4040 return (sx+sy)*0.5f;
4043 NVGVertex* nvg__allocTempVerts (NVGContext ctx, int nverts) nothrow @trusted @nogc {
4044 if (nverts > ctx.cache.cverts) {
4045 int cverts = (nverts+0xff)&~0xff; // Round up to prevent allocations when things change just slightly.
4046 NVGVertex* verts = cast(NVGVertex*)realloc(ctx.cache.verts, NVGVertex.sizeof*cverts);
4047 if (verts is null) return null;
4048 ctx.cache.verts = verts;
4049 ctx.cache.cverts = cverts;
4052 return ctx.cache.verts;
4055 float nvg__triarea2 (float ax, float ay, float bx, float by, float cx, float cy) pure nothrow @safe @nogc {
4056 immutable float abx = bx-ax;
4057 immutable float aby = by-ay;
4058 immutable float acx = cx-ax;
4059 immutable float acy = cy-ay;
4060 return acx*aby-abx*acy;
4063 float nvg__polyArea (NVGpoint* pts, int npts) nothrow @trusted @nogc {
4064 float area = 0;
4065 foreach (int i; 2..npts) {
4066 NVGpoint* a = &pts[0];
4067 NVGpoint* b = &pts[i-1];
4068 NVGpoint* c = &pts[i];
4069 area += nvg__triarea2(a.x, a.y, b.x, b.y, c.x, c.y);
4071 return area*0.5f;
4074 void nvg__polyReverse (NVGpoint* pts, int npts) nothrow @trusted @nogc {
4075 NVGpoint tmp = void;
4076 int i = 0, j = npts-1;
4077 while (i < j) {
4078 tmp = pts[i];
4079 pts[i] = pts[j];
4080 pts[j] = tmp;
4081 ++i;
4082 --j;
4086 void nvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc {
4087 vtx.x = x;
4088 vtx.y = y;
4089 vtx.u = u;
4090 vtx.v = v;
4093 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 {
4094 if (level > 10) return;
4096 // check for collinear points, and use AFD tesselator on such curves (it is WAY faster for this case)
4098 if (level == 0 && ctx.tesselatortype == NVGTesselation.Combined) {
4099 static bool collinear (in float v0x, in float v0y, in float v1x, in float v1y, in float v2x, in float v2y) nothrow @trusted @nogc {
4100 immutable float cz = (v1x-v0x)*(v2y-v0y)-(v2x-v0x)*(v1y-v0y);
4101 return (nvg__absf(cz*cz) <= 0.01f); // arbitrary number, seems to work ok with NanoSVG output
4103 if (collinear(x1, y1, x2, y2, x3, y3) && collinear(x2, y2, x3, y3, x3, y4)) {
4104 //{ import core.stdc.stdio; printf("AFD fallback!\n"); }
4105 ctx.nvg__tesselateBezierAFD(x1, y1, x2, y2, x3, y3, x4, y4, type);
4106 return;
4111 immutable float x12 = (x1+x2)*0.5f;
4112 immutable float y12 = (y1+y2)*0.5f;
4113 immutable float x23 = (x2+x3)*0.5f;
4114 immutable float y23 = (y2+y3)*0.5f;
4115 immutable float x34 = (x3+x4)*0.5f;
4116 immutable float y34 = (y3+y4)*0.5f;
4117 immutable float x123 = (x12+x23)*0.5f;
4118 immutable float y123 = (y12+y23)*0.5f;
4120 immutable float dx = x4-x1;
4121 immutable float dy = y4-y1;
4122 immutable float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx));
4123 immutable float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx));
4125 if ((d2+d3)*(d2+d3) < ctx.tessTol*(dx*dx+dy*dy)) {
4126 nvg__addPoint(ctx, x4, y4, type);
4127 return;
4130 immutable float x234 = (x23+x34)*0.5f;
4131 immutable float y234 = (y23+y34)*0.5f;
4132 immutable float x1234 = (x123+x234)*0.5f;
4133 immutable float y1234 = (y123+y234)*0.5f;
4135 // "taxicab" / "manhattan" check for flat curves
4136 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) {
4137 nvg__addPoint(ctx, x1234, y1234, type);
4138 return;
4141 nvg__tesselateBezier(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
4142 nvg__tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
4145 // based on the ideas and code of Maxim Shemanarev. Rest in Peace, bro!
4146 // see http://www.antigrain.com/research/adaptive_bezier/index.html
4147 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 {
4148 enum CollinearEPS = 0.00000001f; // 0.00001f;
4149 enum AngleTolEPS = 0.01f;
4151 static float distSquared (in float x1, in float y1, in float x2, in float y2) pure nothrow @safe @nogc {
4152 pragma(inline, true);
4153 immutable float dx = x2-x1;
4154 immutable float dy = y2-y1;
4155 return dx*dx+dy*dy;
4158 if (level == 0) {
4159 nvg__addPoint(ctx, x1, y1, 0);
4160 nvg__tesselateBezierMcSeem(ctx, x1, y1, x2, y2, x3, y3, x4, y4, 1, type);
4161 nvg__addPoint(ctx, x4, y4, type);
4162 return;
4165 if (level >= 32) return; // recurse limit; practically, it should be never reached, but...
4167 // calculate all the mid-points of the line segments
4168 immutable float x12 = (x1+x2)*0.5f;
4169 immutable float y12 = (y1+y2)*0.5f;
4170 immutable float x23 = (x2+x3)*0.5f;
4171 immutable float y23 = (y2+y3)*0.5f;
4172 immutable float x34 = (x3+x4)*0.5f;
4173 immutable float y34 = (y3+y4)*0.5f;
4174 immutable float x123 = (x12+x23)*0.5f;
4175 immutable float y123 = (y12+y23)*0.5f;
4176 immutable float x234 = (x23+x34)*0.5f;
4177 immutable float y234 = (y23+y34)*0.5f;
4178 immutable float x1234 = (x123+x234)*0.5f;
4179 immutable float y1234 = (y123+y234)*0.5f;
4181 // try to approximate the full cubic curve by a single straight line
4182 immutable float dx = x4-x1;
4183 immutable float dy = y4-y1;
4185 float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx));
4186 float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx));
4187 //immutable float da1, da2, k;
4189 final switch ((cast(int)(d2 > CollinearEPS)<<1)+cast(int)(d3 > CollinearEPS)) {
4190 case 0:
4191 // all collinear or p1 == p4
4192 float k = dx*dx+dy*dy;
4193 if (k == 0) {
4194 d2 = distSquared(x1, y1, x2, y2);
4195 d3 = distSquared(x4, y4, x3, y3);
4196 } else {
4197 k = 1.0f/k;
4198 float da1 = x2-x1;
4199 float da2 = y2-y1;
4200 d2 = k*(da1*dx+da2*dy);
4201 da1 = x3-x1;
4202 da2 = y3-y1;
4203 d3 = k*(da1*dx+da2*dy);
4204 if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) {
4205 // Simple collinear case, 1---2---3---4
4206 // We can leave just two endpoints
4207 return;
4209 if (d2 <= 0) d2 = distSquared(x2, y2, x1, y1);
4210 else if (d2 >= 1) d2 = distSquared(x2, y2, x4, y4);
4211 else d2 = distSquared(x2, y2, x1+d2*dx, y1+d2*dy);
4213 if (d3 <= 0) d3 = distSquared(x3, y3, x1, y1);
4214 else if (d3 >= 1) d3 = distSquared(x3, y3, x4, y4);
4215 else d3 = distSquared(x3, y3, x1+d3*dx, y1+d3*dy);
4217 if (d2 > d3) {
4218 if (d2 < ctx.tessTol) {
4219 nvg__addPoint(ctx, x2, y2, type);
4220 return;
4222 } if (d3 < ctx.tessTol) {
4223 nvg__addPoint(ctx, x3, y3, type);
4224 return;
4226 break;
4227 case 1:
4228 // p1,p2,p4 are collinear, p3 is significant
4229 if (d3*d3 <= ctx.tessTol*(dx*dx+dy*dy)) {
4230 if (ctx.angleTol < AngleTolEPS) {
4231 nvg__addPoint(ctx, x23, y23, type);
4232 return;
4233 } else {
4234 // angle condition
4235 float da1 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-nvg__atan2f(y3-y2, x3-x2));
4236 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4237 if (da1 < ctx.angleTol) {
4238 nvg__addPoint(ctx, x2, y2, type);
4239 nvg__addPoint(ctx, x3, y3, type);
4240 return;
4242 if (ctx.cuspLimit != 0.0) {
4243 if (da1 > ctx.cuspLimit) {
4244 nvg__addPoint(ctx, x3, y3, type);
4245 return;
4250 break;
4251 case 2:
4252 // p1,p3,p4 are collinear, p2 is significant
4253 if (d2*d2 <= ctx.tessTol*(dx*dx+dy*dy)) {
4254 if (ctx.angleTol < AngleTolEPS) {
4255 nvg__addPoint(ctx, x23, y23, type);
4256 return;
4257 } else {
4258 // angle condition
4259 float da1 = nvg__absf(nvg__atan2f(y3-y2, x3-x2)-nvg__atan2f(y2-y1, x2-x1));
4260 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4261 if (da1 < ctx.angleTol) {
4262 nvg__addPoint(ctx, x2, y2, type);
4263 nvg__addPoint(ctx, x3, y3, type);
4264 return;
4266 if (ctx.cuspLimit != 0.0) {
4267 if (da1 > ctx.cuspLimit) {
4268 nvg__addPoint(ctx, x2, y2, type);
4269 return;
4274 break;
4275 case 3:
4276 // regular case
4277 if ((d2+d3)*(d2+d3) <= ctx.tessTol*(dx*dx+dy*dy)) {
4278 // if the curvature doesn't exceed the distance tolerance value, we tend to finish subdivisions
4279 if (ctx.angleTol < AngleTolEPS) {
4280 nvg__addPoint(ctx, x23, y23, type);
4281 return;
4282 } else {
4283 // angle and cusp condition
4284 immutable float k = nvg__atan2f(y3-y2, x3-x2);
4285 float da1 = nvg__absf(k-nvg__atan2f(y2-y1, x2-x1));
4286 float da2 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-k);
4287 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4288 if (da2 >= NVG_PI) da2 = 2*NVG_PI-da2;
4289 if (da1+da2 < ctx.angleTol) {
4290 // finally we can stop the recursion
4291 nvg__addPoint(ctx, x23, y23, type);
4292 return;
4294 if (ctx.cuspLimit != 0.0) {
4295 if (da1 > ctx.cuspLimit) {
4296 nvg__addPoint(ctx, x2, y2, type);
4297 return;
4299 if (da2 > ctx.cuspLimit) {
4300 nvg__addPoint(ctx, x3, y3, type);
4301 return;
4306 break;
4309 // continue subdivision
4310 nvg__tesselateBezierMcSeem(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
4311 nvg__tesselateBezierMcSeem(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
4315 // Adaptive forward differencing for bezier tesselation.
4316 // See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt.
4317 // "Adaptive forward differencing for rendering curves and surfaces."
4318 // ACM SIGGRAPH Computer Graphics. Vol. 21. No. 4. ACM, 1987.
4319 // original code by Taylor Holliday <taylor@audulus.com>
4320 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 {
4321 enum AFD_ONE = (1<<10);
4323 // power basis
4324 immutable float ax = -x1+3*x2-3*x3+x4;
4325 immutable float ay = -y1+3*y2-3*y3+y4;
4326 immutable float bx = 3*x1-6*x2+3*x3;
4327 immutable float by = 3*y1-6*y2+3*y3;
4328 immutable float cx = -3*x1+3*x2;
4329 immutable float cy = -3*y1+3*y2;
4331 // Transform to forward difference basis (stepsize 1)
4332 float px = x1;
4333 float py = y1;
4334 float dx = ax+bx+cx;
4335 float dy = ay+by+cy;
4336 float ddx = 6*ax+2*bx;
4337 float ddy = 6*ay+2*by;
4338 float dddx = 6*ax;
4339 float dddy = 6*ay;
4341 //printf("dx: %f, dy: %f\n", dx, dy);
4342 //printf("ddx: %f, ddy: %f\n", ddx, ddy);
4343 //printf("dddx: %f, dddy: %f\n", dddx, dddy);
4345 int t = 0;
4346 int dt = AFD_ONE;
4348 immutable float tol = ctx.tessTol*4;
4350 while (t < AFD_ONE) {
4351 // Flatness measure.
4352 float d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4354 // printf("d: %f, th: %f\n", d, th);
4356 // Go to higher resolution if we're moving a lot or overshooting the end.
4357 while ((d > tol && dt > 1) || (t+dt > AFD_ONE)) {
4358 // printf("up\n");
4360 // Apply L to the curve. Increase curve resolution.
4361 dx = 0.5f*dx-(1.0f/8.0f)*ddx+(1.0f/16.0f)*dddx;
4362 dy = 0.5f*dy-(1.0f/8.0f)*ddy+(1.0f/16.0f)*dddy;
4363 ddx = (1.0f/4.0f)*ddx-(1.0f/8.0f)*dddx;
4364 ddy = (1.0f/4.0f)*ddy-(1.0f/8.0f)*dddy;
4365 dddx = (1.0f/8.0f)*dddx;
4366 dddy = (1.0f/8.0f)*dddy;
4368 // Half the stepsize.
4369 dt >>= 1;
4371 // Recompute d
4372 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4375 // Go to lower resolution if we're really flat
4376 // and we aren't going to overshoot the end.
4377 // XXX: tol/32 is just a guess for when we are too flat.
4378 while ((d > 0 && d < tol/32.0f && dt < AFD_ONE) && (t+2*dt <= AFD_ONE)) {
4379 // printf("down\n");
4381 // Apply L^(-1) to the curve. Decrease curve resolution.
4382 dx = 2*dx+ddx;
4383 dy = 2*dy+ddy;
4384 ddx = 4*ddx+4*dddx;
4385 ddy = 4*ddy+4*dddy;
4386 dddx = 8*dddx;
4387 dddy = 8*dddy;
4389 // Double the stepsize.
4390 dt <<= 1;
4392 // Recompute d
4393 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4396 // Forward differencing.
4397 px += dx;
4398 py += dy;
4399 dx += ddx;
4400 dy += ddy;
4401 ddx += dddx;
4402 ddy += dddy;
4404 // Output a point.
4405 nvg__addPoint(ctx, px, py, (t > 0 ? type : 0));
4407 // Advance along the curve.
4408 t += dt;
4410 // Ensure we don't overshoot.
4411 assert(t <= AFD_ONE);
4416 void nvg__dashLastPath (NVGContext ctx) nothrow @trusted @nogc {
4417 import core.stdc.stdlib : realloc;
4418 import core.stdc.string : memcpy;
4420 NVGpathCache* cache = ctx.cache;
4421 if (cache.npaths == 0) return;
4423 NVGpath* path = nvg__lastPath(ctx);
4424 if (path is null) return;
4426 NVGstate* state = nvg__getState(ctx);
4427 if (!state.dasherActive) return;
4429 static NVGpoint* pts = null;
4430 static uint ptsCount = 0;
4431 static uint ptsSize = 0;
4433 if (path.count < 2) return; // just in case
4435 // copy path points (reserve one point for closed pathes)
4436 if (ptsSize < path.count+1) {
4437 ptsSize = cast(uint)(path.count+1);
4438 pts = cast(NVGpoint*)realloc(pts, ptsSize*NVGpoint.sizeof);
4439 if (pts is null) assert(0, "NanoVega: out of memory");
4441 ptsCount = cast(uint)path.count;
4442 memcpy(pts, &cache.points[path.first], ptsCount*NVGpoint.sizeof);
4443 // add closing point for closed pathes
4444 if (path.closed && !nvg__ptEquals(pts[0].x, pts[0].y, pts[ptsCount-1].x, pts[ptsCount-1].y, ctx.distTol)) {
4445 pts[ptsCount++] = pts[0];
4448 // remove last path (with its points)
4449 --cache.npaths;
4450 cache.npoints -= path.count;
4452 // add stroked pathes
4453 const(float)* dashes = state.dashes.ptr;
4454 immutable uint dashCount = state.dashCount;
4455 float currDashStart = 0;
4456 uint currDashIdx = 0;
4457 immutable bool firstIsGap = state.firstDashIsGap;
4459 // calculate lengthes
4461 NVGpoint* v1 = &pts[0];
4462 NVGpoint* v2 = &pts[1];
4463 foreach (immutable _; 0..ptsCount) {
4464 float dx = v2.x-v1.x;
4465 float dy = v2.y-v1.y;
4466 v1.len = nvg__normalize(&dx, &dy);
4467 v1 = v2++;
4471 void calcDashStart (float ds) {
4472 if (ds < 0) {
4473 ds = ds%state.totalDashLen;
4474 while (ds < 0) ds += state.totalDashLen;
4476 currDashIdx = 0;
4477 currDashStart = 0;
4478 while (ds > 0) {
4479 if (ds > dashes[currDashIdx]) {
4480 ds -= dashes[currDashIdx];
4481 ++currDashIdx;
4482 currDashStart = 0;
4483 if (currDashIdx >= dashCount) currDashIdx = 0;
4484 } else {
4485 currDashStart = ds;
4486 ds = 0;
4491 calcDashStart(state.dashStart);
4493 uint srcPointIdx = 1;
4494 const(NVGpoint)* v1 = &pts[0];
4495 const(NVGpoint)* v2 = &pts[1];
4496 float currRest = v1.len;
4497 nvg__addPath(ctx);
4498 nvg__addPoint(ctx, v1.x, v1.y, PointFlag.Corner);
4500 void fixLastPoint () {
4501 auto lpt = nvg__lastPath(ctx);
4502 if (lpt !is null && lpt.count > 0) {
4503 // fix last point
4504 if (auto lps = nvg__lastPoint(ctx)) lps.flags = PointFlag.Corner;
4505 // fix first point
4506 NVGpathCache* cache = ctx.cache;
4507 cache.points[lpt.first].flags = PointFlag.Corner;
4511 for (;;) {
4512 immutable float dlen = dashes[currDashIdx];
4513 if (dlen == 0) {
4514 ++currDashIdx;
4515 if (currDashIdx >= dashCount) currDashIdx = 0;
4516 continue;
4518 immutable float dashRest = dlen-currDashStart;
4519 if ((currDashIdx&1) != firstIsGap) {
4520 // this is "moveto" command, so create new path
4521 fixLastPoint();
4522 nvg__addPath(ctx);
4524 if (currRest > dashRest) {
4525 currRest -= dashRest;
4526 ++currDashIdx;
4527 if (currDashIdx >= dashCount) currDashIdx = 0;
4528 currDashStart = 0;
4529 nvg__addPoint(ctx,
4530 v2.x-(v2.x-v1.x)*currRest/v1.len,
4531 v2.y-(v2.y-v1.y)*currRest/v1.len,
4532 PointFlag.Corner
4534 } else {
4535 currDashStart += currRest;
4536 nvg__addPoint(ctx, v2.x, v2.y, v1.flags); //k8:fix flags here?
4537 ++srcPointIdx;
4538 v1 = v2;
4539 currRest = v1.len;
4540 if (srcPointIdx >= ptsCount) break;
4541 v2 = &pts[srcPointIdx];
4544 fixLastPoint();
4548 version(nanovg_bench_flatten) import iv.timer : Timer;
4550 void nvg__flattenPaths(bool asStroke) (NVGContext ctx) nothrow @trusted @nogc {
4551 version(nanovg_bench_flatten) {
4552 Timer timer;
4553 char[128] tmbuf;
4554 int bzcount;
4556 NVGpathCache* cache = ctx.cache;
4557 NVGstate* state = nvg__getState(ctx);
4559 // check if we already did flattening
4560 static if (asStroke) {
4561 if (state.dashCount >= 2) {
4562 if (cache.npaths > 0 && state.lastFlattenDashCount == state.dashCount) return; // already flattened
4563 state.dasherActive = true;
4564 state.lastFlattenDashCount = state.dashCount;
4565 } else {
4566 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened
4567 state.dasherActive = false;
4568 state.lastFlattenDashCount = 0;
4570 } else {
4571 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened
4572 state.lastFlattenDashCount = 0; // so next stroke flattening will redo it
4573 state.dasherActive = false;
4576 // clear path cache
4577 cache.npaths = 0;
4578 cache.npoints = 0;
4580 // flatten
4581 version(nanovg_bench_flatten) timer.restart();
4582 int i = 0;
4583 while (i < ctx.ncommands) {
4584 final switch (cast(Command)ctx.commands[i]) {
4585 case Command.MoveTo:
4586 //assert(i+3 <= ctx.ncommands);
4587 static if (asStroke) {
4588 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx);
4590 nvg__addPath(ctx);
4591 const p = &ctx.commands[i+1];
4592 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner);
4593 i += 3;
4594 break;
4595 case Command.LineTo:
4596 //assert(i+3 <= ctx.ncommands);
4597 const p = &ctx.commands[i+1];
4598 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner);
4599 i += 3;
4600 break;
4601 case Command.BezierTo:
4602 //assert(i+7 <= ctx.ncommands);
4603 const last = nvg__lastPoint(ctx);
4604 if (last !is null) {
4605 const cp1 = &ctx.commands[i+1];
4606 const cp2 = &ctx.commands[i+3];
4607 const p = &ctx.commands[i+5];
4608 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) {
4609 nvg__tesselateBezier(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
4610 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) {
4611 nvg__tesselateBezierMcSeem(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
4612 } else {
4613 nvg__tesselateBezierAFD(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], PointFlag.Corner);
4615 version(nanovg_bench_flatten) ++bzcount;
4617 i += 7;
4618 break;
4619 case Command.Close:
4620 //assert(i+1 <= ctx.ncommands);
4621 nvg__closePath(ctx);
4622 i += 1;
4623 break;
4624 case Command.Winding:
4625 //assert(i+2 <= ctx.ncommands);
4626 nvg__pathWinding(ctx, cast(NVGWinding)ctx.commands[i+1]);
4627 i += 2;
4628 break;
4631 static if (asStroke) {
4632 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx);
4634 version(nanovg_bench_flatten) {{
4635 timer.stop();
4636 auto xb = timer.toBuffer(tmbuf[]);
4637 import core.stdc.stdio : printf;
4638 printf("flattening time: [%.*s] (%d beziers)\n", cast(uint)xb.length, xb.ptr, bzcount);
4641 cache.bounds.ptr[0] = cache.bounds.ptr[1] = float.max;
4642 cache.bounds.ptr[2] = cache.bounds.ptr[3] = -float.max;
4644 // calculate the direction and length of line segments
4645 version(nanovg_bench_flatten) timer.restart();
4646 foreach (int j; 0..cache.npaths) {
4647 NVGpath* path = &cache.paths[j];
4648 NVGpoint* pts = &cache.points[path.first];
4650 // if the first and last points are the same, remove the last, mark as closed path
4651 NVGpoint* p0 = &pts[path.count-1];
4652 NVGpoint* p1 = &pts[0];
4653 if (nvg__ptEquals(p0.x, p0.y, p1.x, p1.y, ctx.distTol)) {
4654 --path.count;
4655 p0 = &pts[path.count-1];
4656 path.closed = true;
4659 // enforce winding
4660 if (path.count > 2) {
4661 immutable float area = nvg__polyArea(pts, path.count);
4662 if (path.mWinding == NVGWinding.CCW && area < 0.0f) nvg__polyReverse(pts, path.count);
4663 if (path.mWinding == NVGWinding.CW && area > 0.0f) nvg__polyReverse(pts, path.count);
4666 foreach (immutable _; 0..path.count) {
4667 // calculate segment direction and length
4668 p0.dx = p1.x-p0.x;
4669 p0.dy = p1.y-p0.y;
4670 p0.len = nvg__normalize(&p0.dx, &p0.dy);
4671 // update bounds
4672 cache.bounds.ptr[0] = nvg__min(cache.bounds.ptr[0], p0.x);
4673 cache.bounds.ptr[1] = nvg__min(cache.bounds.ptr[1], p0.y);
4674 cache.bounds.ptr[2] = nvg__max(cache.bounds.ptr[2], p0.x);
4675 cache.bounds.ptr[3] = nvg__max(cache.bounds.ptr[3], p0.y);
4676 // advance
4677 p0 = p1++;
4680 version(nanovg_bench_flatten) {{
4681 timer.stop();
4682 auto xb = timer.toBuffer(tmbuf[]);
4683 import core.stdc.stdio : printf;
4684 printf("segment calculation time: [%.*s]\n", cast(uint)xb.length, xb.ptr);
4688 int nvg__curveDivs (float r, float arc, float tol) nothrow @trusted @nogc {
4689 immutable float da = nvg__acosf(r/(r+tol))*2.0f;
4690 return nvg__max(2, cast(int)nvg__ceilf(arc/da));
4693 void nvg__chooseBevel (int bevel, NVGpoint* p0, NVGpoint* p1, float w, float* x0, float* y0, float* x1, float* y1) nothrow @trusted @nogc {
4694 if (bevel) {
4695 *x0 = p1.x+p0.dy*w;
4696 *y0 = p1.y-p0.dx*w;
4697 *x1 = p1.x+p1.dy*w;
4698 *y1 = p1.y-p1.dx*w;
4699 } else {
4700 *x0 = p1.x+p1.dmx*w;
4701 *y0 = p1.y+p1.dmy*w;
4702 *x1 = p1.x+p1.dmx*w;
4703 *y1 = p1.y+p1.dmy*w;
4707 NVGVertex* nvg__roundJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, int ncap, float fringe) nothrow @trusted @nogc {
4708 float dlx0 = p0.dy;
4709 float dly0 = -p0.dx;
4710 float dlx1 = p1.dy;
4711 float dly1 = -p1.dx;
4712 //NVG_NOTUSED(fringe);
4714 if (p1.flags&PointFlag.Left) {
4715 float lx0 = void, ly0 = void, lx1 = void, ly1 = void;
4716 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1);
4717 immutable float a0 = nvg__atan2f(-dly0, -dlx0);
4718 float a1 = nvg__atan2f(-dly1, -dlx1);
4719 if (a1 > a0) a1 -= NVG_PI*2;
4721 nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4722 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4724 int n = nvg__clamp(cast(int)nvg__ceilf(((a0-a1)/NVG_PI)*ncap), 2, ncap);
4725 for (int i = 0; i < n; ++i) {
4726 float u = i/cast(float)(n-1);
4727 float a = a0+u*(a1-a0);
4728 float rx = p1.x+nvg__cosf(a)*rw;
4729 float ry = p1.y+nvg__sinf(a)*rw;
4730 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4731 nvg__vset(dst, rx, ry, ru, 1); ++dst;
4734 nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4735 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4737 } else {
4738 float rx0 = void, ry0 = void, rx1 = void, ry1 = void;
4739 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1);
4740 immutable float a0 = nvg__atan2f(dly0, dlx0);
4741 float a1 = nvg__atan2f(dly1, dlx1);
4742 if (a1 < a0) a1 += NVG_PI*2;
4744 nvg__vset(dst, p1.x+dlx0*rw, p1.y+dly0*rw, lu, 1); ++dst;
4745 nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4747 int n = nvg__clamp(cast(int)nvg__ceilf(((a1-a0)/NVG_PI)*ncap), 2, ncap);
4748 for (int i = 0; i < n; i++) {
4749 float u = i/cast(float)(n-1);
4750 float a = a0+u*(a1-a0);
4751 float lx = p1.x+nvg__cosf(a)*lw;
4752 float ly = p1.y+nvg__sinf(a)*lw;
4753 nvg__vset(dst, lx, ly, lu, 1); ++dst;
4754 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4757 nvg__vset(dst, p1.x+dlx1*rw, p1.y+dly1*rw, lu, 1); ++dst;
4758 nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
4761 return dst;
4764 NVGVertex* nvg__bevelJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, float fringe) nothrow @trusted @nogc {
4765 float rx0, ry0, rx1, ry1;
4766 float lx0, ly0, lx1, ly1;
4767 float dlx0 = p0.dy;
4768 float dly0 = -p0.dx;
4769 float dlx1 = p1.dy;
4770 float dly1 = -p1.dx;
4771 //NVG_NOTUSED(fringe);
4773 if (p1.flags&PointFlag.Left) {
4774 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1);
4776 nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4777 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4779 if (p1.flags&PointFlag.Bevel) {
4780 nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4781 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4783 nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4784 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4785 } else {
4786 rx0 = p1.x-p1.dmx*rw;
4787 ry0 = p1.y-p1.dmy*rw;
4789 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4790 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4792 nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4793 nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4795 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4796 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4799 nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4800 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4802 } else {
4803 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1);
4805 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
4806 nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4808 if (p1.flags&PointFlag.Bevel) {
4809 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
4810 nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4812 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
4813 nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
4814 } else {
4815 lx0 = p1.x+p1.dmx*lw;
4816 ly0 = p1.y+p1.dmy*lw;
4818 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
4819 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4821 nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4822 nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4824 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
4825 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4828 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
4829 nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
4832 return dst;
4835 NVGVertex* nvg__buttCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa, float u0, float u1) nothrow @trusted @nogc {
4836 immutable float px = p.x-dx*d;
4837 immutable float py = p.y-dy*d;
4838 immutable float dlx = dy;
4839 immutable float dly = -dx;
4840 nvg__vset(dst, px+dlx*w-dx*aa, py+dly*w-dy*aa, u0,0); ++dst;
4841 nvg__vset(dst, px-dlx*w-dx*aa, py-dly*w-dy*aa, u1,0); ++dst;
4842 nvg__vset(dst, px+dlx*w, py+dly*w, u0, 1); ++dst;
4843 nvg__vset(dst, px-dlx*w, py-dly*w, u1, 1); ++dst;
4844 return dst;
4847 NVGVertex* nvg__buttCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa, float u0, float u1) nothrow @trusted @nogc {
4848 immutable float px = p.x+dx*d;
4849 immutable float py = p.y+dy*d;
4850 immutable float dlx = dy;
4851 immutable float dly = -dx;
4852 nvg__vset(dst, px+dlx*w, py+dly*w, u0, 1); ++dst;
4853 nvg__vset(dst, px-dlx*w, py-dly*w, u1, 1); ++dst;
4854 nvg__vset(dst, px+dlx*w+dx*aa, py+dly*w+dy*aa, u0, 0); ++dst;
4855 nvg__vset(dst, px-dlx*w+dx*aa, py-dly*w+dy*aa, u1, 0); ++dst;
4856 return dst;
4859 NVGVertex* nvg__roundCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa, float u0, float u1) nothrow @trusted @nogc {
4860 immutable float px = p.x;
4861 immutable float py = p.y;
4862 immutable float dlx = dy;
4863 immutable float dly = -dx;
4864 //NVG_NOTUSED(aa);
4865 immutable float ncpf = cast(float)(ncap-1);
4866 foreach (int i; 0..ncap) {
4867 float a = i/*/cast(float)(ncap-1)*//ncpf*NVG_PI;
4868 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w;
4869 nvg__vset(dst, px-dlx*ax-dx*ay, py-dly*ax-dy*ay, 0, 1); ++dst;
4870 nvg__vset(dst, px, py, 0.5f, 1); ++dst;
4872 nvg__vset(dst, px+dlx*w, py+dly*w, u0, 1); ++dst;
4873 nvg__vset(dst, px-dlx*w, py-dly*w, u1, 1); ++dst;
4874 return dst;
4877 NVGVertex* nvg__roundCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa, float u0, float u1) nothrow @trusted @nogc {
4878 immutable float px = p.x;
4879 immutable float py = p.y;
4880 immutable float dlx = dy;
4881 immutable float dly = -dx;
4882 //NVG_NOTUSED(aa);
4883 nvg__vset(dst, px+dlx*w, py+dly*w, u0, 1); ++dst;
4884 nvg__vset(dst, px-dlx*w, py-dly*w, u1, 1); ++dst;
4885 immutable float ncpf = cast(float)(ncap-1);
4886 foreach (int i; 0..ncap) {
4887 float a = i/*cast(float)(ncap-1)*//ncpf*NVG_PI;
4888 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w;
4889 nvg__vset(dst, px, py, 0.5f, 1); ++dst;
4890 nvg__vset(dst, px-dlx*ax+dx*ay, py-dly*ax+dy*ay, u0, 1); ++dst;
4892 return dst;
4895 void nvg__calculateJoins (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc {
4896 NVGpathCache* cache = ctx.cache;
4897 float iw = 0.0f;
4899 if (w > 0.0f) iw = 1.0f/w;
4901 // Calculate which joins needs extra vertices to append, and gather vertex count.
4902 foreach (int i; 0..cache.npaths) {
4903 NVGpath* path = &cache.paths[i];
4904 NVGpoint* pts = &cache.points[path.first];
4905 NVGpoint* p0 = &pts[path.count-1];
4906 NVGpoint* p1 = &pts[0];
4907 int nleft = 0;
4909 path.nbevel = 0;
4911 foreach (int j; 0..path.count) {
4912 //float dlx0, dly0, dlx1, dly1, dmr2, cross, limit;
4913 immutable float dlx0 = p0.dy;
4914 immutable float dly0 = -p0.dx;
4915 immutable float dlx1 = p1.dy;
4916 immutable float dly1 = -p1.dx;
4917 // Calculate extrusions
4918 p1.dmx = (dlx0+dlx1)*0.5f;
4919 p1.dmy = (dly0+dly1)*0.5f;
4920 immutable float dmr2 = p1.dmx*p1.dmx+p1.dmy*p1.dmy;
4921 if (dmr2 > 0.000001f) {
4922 float scale = 1.0f/dmr2;
4923 if (scale > 600.0f) scale = 600.0f;
4924 p1.dmx *= scale;
4925 p1.dmy *= scale;
4928 // Clear flags, but keep the corner.
4929 p1.flags = (p1.flags&PointFlag.Corner) ? PointFlag.Corner : 0;
4931 // Keep track of left turns.
4932 immutable float cross = p1.dx*p0.dy-p0.dx*p1.dy;
4933 if (cross > 0.0f) {
4934 nleft++;
4935 p1.flags |= PointFlag.Left;
4938 // Calculate if we should use bevel or miter for inner join.
4939 immutable float limit = nvg__max(1.01f, nvg__min(p0.len, p1.len)*iw);
4940 if ((dmr2*limit*limit) < 1.0f) p1.flags |= PointFlag.InnerBevelPR;
4942 // Check to see if the corner needs to be beveled.
4943 if (p1.flags&PointFlag.Corner) {
4944 if ((dmr2*miterLimit*miterLimit) < 1.0f || lineJoin == NVGLineCap.Bevel || lineJoin == NVGLineCap.Round) {
4945 p1.flags |= PointFlag.Bevel;
4949 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) path.nbevel++;
4951 p0 = p1++;
4954 path.convex = (nleft == path.count);
4958 void nvg__expandStroke (NVGContext ctx, float w, float fringe, int lineCap, int lineJoin, float miterLimit) nothrow @trusted @nogc {
4959 NVGpathCache* cache = ctx.cache;
4960 immutable float aa = fringe; //ctx.fringeWidth;
4961 float u0 = 0.0f, u1 = 1.0f;
4962 int ncap = nvg__curveDivs(w, NVG_PI, ctx.tessTol); // Calculate divisions per half circle.
4964 w += aa*0.5f;
4966 // Disable the gradient used for antialiasing when antialiasing is not used.
4967 if (aa == 0.0f) {
4968 u0 = 0.5f;
4969 u1 = 0.5f;
4972 nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
4974 // Calculate max vertex usage.
4975 int cverts = 0;
4976 foreach (int i; 0..cache.npaths) {
4977 NVGpath* path = &cache.paths[i];
4978 immutable bool loop = path.closed;
4979 if (lineJoin == NVGLineCap.Round) {
4980 cverts += (path.count+path.nbevel*(ncap+2)+1)*2; // plus one for loop
4981 } else {
4982 cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop
4984 if (!loop) {
4985 // space for caps
4986 if (lineCap == NVGLineCap.Round) {
4987 cverts += (ncap*2+2)*2;
4988 } else {
4989 cverts += (3+3)*2;
4994 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts);
4995 if (verts is null) return;
4997 foreach (int i; 0..cache.npaths) {
4998 NVGpath* path = &cache.paths[i];
4999 NVGpoint* pts = &cache.points[path.first];
5000 NVGpoint* p0;
5001 NVGpoint* p1;
5002 int s, e;
5004 path.fill = null;
5005 path.nfill = 0;
5007 // Calculate fringe or stroke
5008 immutable bool loop = path.closed;
5009 NVGVertex* dst = verts;
5010 path.stroke = dst;
5012 if (loop) {
5013 // Looping
5014 p0 = &pts[path.count-1];
5015 p1 = &pts[0];
5016 s = 0;
5017 e = path.count;
5018 } else {
5019 // Add cap
5020 p0 = &pts[0];
5021 p1 = &pts[1];
5022 s = 1;
5023 e = path.count-1;
5026 if (!loop) {
5027 // Add cap
5028 float dx = p1.x-p0.x;
5029 float dy = p1.y-p0.y;
5030 nvg__normalize(&dx, &dy);
5031 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa, u0, u1);
5032 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa, u0, u1);
5033 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa, u0, u1);
5036 foreach (int j; s..e) {
5037 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) {
5038 if (lineJoin == NVGLineCap.Round) {
5039 dst = nvg__roundJoin(dst, p0, p1, w, w, u0, u1, ncap, aa);
5040 } else {
5041 dst = nvg__bevelJoin(dst, p0, p1, w, w, u0, u1, aa);
5043 } else {
5044 nvg__vset(dst, p1.x+(p1.dmx*w), p1.y+(p1.dmy*w), u0, 1); ++dst;
5045 nvg__vset(dst, p1.x-(p1.dmx*w), p1.y-(p1.dmy*w), u1, 1); ++dst;
5047 p0 = p1++;
5050 if (loop) {
5051 // Loop it
5052 nvg__vset(dst, verts[0].x, verts[0].y, u0, 1); ++dst;
5053 nvg__vset(dst, verts[1].x, verts[1].y, u1, 1); ++dst;
5054 } else {
5055 // Add cap
5056 float dx = p1.x-p0.x;
5057 float dy = p1.y-p0.y;
5058 nvg__normalize(&dx, &dy);
5059 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa, u0, u1);
5060 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa, u0, u1);
5061 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa, u0, u1);
5064 path.nstroke = cast(int)(dst-verts);
5066 verts = dst;
5070 void nvg__expandFill (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5071 NVGpathCache* cache = ctx.cache;
5072 immutable float aa = nvg_getFringe(ctx)/*ctx.fringeWidth*/;
5073 bool fringe = (w > 0.0f);
5075 nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
5077 // Calculate max vertex usage.
5078 int cverts = 0;
5079 foreach (int i; 0..cache.npaths) {
5080 NVGpath* path = &cache.paths[i];
5081 cverts += path.count+path.nbevel+1;
5082 if (fringe) cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop
5085 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts);
5086 if (verts is null) return;
5088 bool convex = (cache.npaths == 1 && cache.paths[0].convex);
5090 foreach (int i; 0..cache.npaths) {
5091 NVGpath* path = &cache.paths[i];
5092 NVGpoint* pts = &cache.points[path.first];
5094 // Calculate shape vertices.
5095 immutable float woff = 0.5f*aa;
5096 NVGVertex* dst = verts;
5097 path.fill = dst;
5099 if (fringe) {
5100 // Looping
5101 NVGpoint* p0 = &pts[path.count-1];
5102 NVGpoint* p1 = &pts[0];
5103 foreach (int j; 0..path.count) {
5104 if (p1.flags&PointFlag.Bevel) {
5105 immutable float dlx0 = p0.dy;
5106 immutable float dly0 = -p0.dx;
5107 immutable float dlx1 = p1.dy;
5108 immutable float dly1 = -p1.dx;
5109 if (p1.flags&PointFlag.Left) {
5110 immutable float lx = p1.x+p1.dmx*woff;
5111 immutable float ly = p1.y+p1.dmy*woff;
5112 nvg__vset(dst, lx, ly, 0.5f, 1); ++dst;
5113 } else {
5114 immutable float lx0 = p1.x+dlx0*woff;
5115 immutable float ly0 = p1.y+dly0*woff;
5116 immutable float lx1 = p1.x+dlx1*woff;
5117 immutable float ly1 = p1.y+dly1*woff;
5118 nvg__vset(dst, lx0, ly0, 0.5f, 1); ++dst;
5119 nvg__vset(dst, lx1, ly1, 0.5f, 1); ++dst;
5121 } else {
5122 nvg__vset(dst, p1.x+(p1.dmx*woff), p1.y+(p1.dmy*woff), 0.5f, 1); ++dst;
5124 p0 = p1++;
5126 } else {
5127 foreach (int j; 0..path.count) {
5128 nvg__vset(dst, pts[j].x, pts[j].y, 0.5f, 1);
5129 ++dst;
5133 path.nfill = cast(int)(dst-verts);
5134 verts = dst;
5136 // Calculate fringe
5137 if (fringe) {
5138 float lw = w+woff;
5139 immutable float rw = w-woff;
5140 float lu = 0;
5141 immutable float ru = 1;
5142 dst = verts;
5143 path.stroke = dst;
5145 // Create only half a fringe for convex shapes so that
5146 // the shape can be rendered without stenciling.
5147 if (convex) {
5148 lw = woff; // This should generate the same vertex as fill inset above.
5149 lu = 0.5f; // Set outline fade at middle.
5152 // Looping
5153 NVGpoint* p0 = &pts[path.count-1];
5154 NVGpoint* p1 = &pts[0];
5156 foreach (int j; 0..path.count) {
5157 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) {
5158 dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, nvg_getFringe(ctx)/*ctx.fringeWidth*/);
5159 } else {
5160 nvg__vset(dst, p1.x+(p1.dmx*lw), p1.y+(p1.dmy*lw), lu, 1); ++dst;
5161 nvg__vset(dst, p1.x-(p1.dmx*rw), p1.y-(p1.dmy*rw), ru, 1); ++dst;
5163 p0 = p1++;
5166 // Loop it
5167 nvg__vset(dst, verts[0].x, verts[0].y, lu, 1); ++dst;
5168 nvg__vset(dst, verts[1].x, verts[1].y, ru, 1); ++dst;
5170 path.nstroke = cast(int)(dst-verts);
5171 verts = dst;
5172 } else {
5173 path.stroke = null;
5174 path.nstroke = 0;
5180 // ////////////////////////////////////////////////////////////////////////// //
5181 // Paths
5183 /// Clears the current path and sub-paths.
5184 /// Group: paths
5185 @scriptable
5186 public void beginPath (NVGContext ctx) nothrow @trusted @nogc {
5187 ctx.ncommands = 0;
5188 ctx.pathPickRegistered &= NVGPickKind.All; // reset "registered" flags
5189 nvg__clearPathCache(ctx);
5192 public alias newPath = beginPath; /// Ditto.
5194 /// Starts new sub-path with specified point as first point.
5195 /// Group: paths
5196 @scriptable
5197 public void moveTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
5198 nvg__appendCommands(ctx, Command.MoveTo, x, y);
5201 /// Starts new sub-path with specified point as first point.
5202 /// Arguments: [x, y]*
5203 /// Group: paths
5204 public void moveTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5205 enum ArgC = 2;
5206 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [moveTo] call");
5207 if (args.length < ArgC) return;
5208 nvg__appendCommands(ctx, Command.MoveTo, args[$-2..$]);
5211 /// Adds line segment from the last point in the path to the specified point.
5212 /// Group: paths
5213 @scriptable
5214 public void lineTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
5215 nvg__appendCommands(ctx, Command.LineTo, x, y);
5218 /// Adds line segment from the last point in the path to the specified point.
5219 /// Arguments: [x, y]*
5220 /// Group: paths
5221 public void lineTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5222 enum ArgC = 2;
5223 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [lineTo] call");
5224 if (args.length < ArgC) return;
5225 foreach (immutable idx; 0..args.length/ArgC) {
5226 nvg__appendCommands(ctx, Command.LineTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]);
5230 /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
5231 /// Group: paths
5232 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 {
5233 nvg__appendCommands(ctx, Command.BezierTo, c1x, c1y, c2x, c2y, x, y);
5236 /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
5237 /// Arguments: [c1x, c1y, c2x, c2y, x, y]*
5238 /// Group: paths
5239 public void bezierTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5240 enum ArgC = 6;
5241 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [bezierTo] call");
5242 if (args.length < ArgC) return;
5243 foreach (immutable idx; 0..args.length/ArgC) {
5244 nvg__appendCommands(ctx, Command.BezierTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]);
5248 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
5249 /// Group: paths
5250 public void quadTo (NVGContext ctx, in float cx, in float cy, in float x, in float y) nothrow @trusted @nogc {
5251 immutable float x0 = ctx.commandx;
5252 immutable float y0 = ctx.commandy;
5253 nvg__appendCommands(ctx,
5254 Command.BezierTo,
5255 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
5256 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
5257 x, y,
5261 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
5262 /// Arguments: [cx, cy, x, y]*
5263 /// Group: paths
5264 public void quadTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5265 enum ArgC = 4;
5266 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [quadTo] call");
5267 if (args.length < ArgC) return;
5268 const(float)* aptr = args.ptr;
5269 foreach (immutable idx; 0..args.length/ArgC) {
5270 immutable float x0 = ctx.commandx;
5271 immutable float y0 = ctx.commandy;
5272 immutable float cx = *aptr++;
5273 immutable float cy = *aptr++;
5274 immutable float x = *aptr++;
5275 immutable float y = *aptr++;
5276 nvg__appendCommands(ctx,
5277 Command.BezierTo,
5278 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
5279 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
5280 x, y,
5285 /// Adds an arc segment at the corner defined by the last path point, and two specified points.
5286 /// Group: paths
5287 public void arcTo (NVGContext ctx, in float x1, in float y1, in float x2, in float y2, in float radius) nothrow @trusted @nogc {
5288 if (ctx.ncommands == 0) return;
5290 immutable float x0 = ctx.commandx;
5291 immutable float y0 = ctx.commandy;
5293 // handle degenerate cases
5294 if (nvg__ptEquals(x0, y0, x1, y1, ctx.distTol) ||
5295 nvg__ptEquals(x1, y1, x2, y2, ctx.distTol) ||
5296 nvg__distPtSeg(x1, y1, x0, y0, x2, y2) < ctx.distTol*ctx.distTol ||
5297 radius < ctx.distTol)
5299 ctx.lineTo(x1, y1);
5300 return;
5303 // calculate tangential circle to lines (x0, y0)-(x1, y1) and (x1, y1)-(x2, y2)
5304 float dx0 = x0-x1;
5305 float dy0 = y0-y1;
5306 float dx1 = x2-x1;
5307 float dy1 = y2-y1;
5308 nvg__normalize(&dx0, &dy0);
5309 nvg__normalize(&dx1, &dy1);
5310 immutable float a = nvg__acosf(dx0*dx1+dy0*dy1);
5311 immutable float d = radius/nvg__tanf(a/2.0f);
5313 //printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d);
5315 if (d > 10000.0f) {
5316 ctx.lineTo(x1, y1);
5317 return;
5320 float cx = void, cy = void, a0 = void, a1 = void;
5321 NVGWinding dir;
5322 if (nvg__cross(dx0, dy0, dx1, dy1) > 0.0f) {
5323 cx = x1+dx0*d+dy0*radius;
5324 cy = y1+dy0*d+-dx0*radius;
5325 a0 = nvg__atan2f(dx0, -dy0);
5326 a1 = nvg__atan2f(-dx1, dy1);
5327 dir = NVGWinding.CW;
5328 //printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
5329 } else {
5330 cx = x1+dx0*d+-dy0*radius;
5331 cy = y1+dy0*d+dx0*radius;
5332 a0 = nvg__atan2f(-dx0, dy0);
5333 a1 = nvg__atan2f(dx1, -dy1);
5334 dir = NVGWinding.CCW;
5335 //printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
5338 ctx.arc(dir, cx, cy, radius, a0, a1); // first is line
5342 /// Adds an arc segment at the corner defined by the last path point, and two specified points.
5343 /// Arguments: [x1, y1, x2, y2, radius]*
5344 /// Group: paths
5345 public void arcTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5346 enum ArgC = 5;
5347 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arcTo] call");
5348 if (args.length < ArgC) return;
5349 if (ctx.ncommands == 0) return;
5350 const(float)* aptr = args.ptr;
5351 foreach (immutable idx; 0..args.length/ArgC) {
5352 immutable float x0 = ctx.commandx;
5353 immutable float y0 = ctx.commandy;
5354 immutable float x1 = *aptr++;
5355 immutable float y1 = *aptr++;
5356 immutable float x2 = *aptr++;
5357 immutable float y2 = *aptr++;
5358 immutable float radius = *aptr++;
5359 ctx.arcTo(x1, y1, x2, y2, radius);
5363 /// Closes current sub-path with a line segment.
5364 /// Group: paths
5365 @scriptable
5366 public void closePath (NVGContext ctx) nothrow @trusted @nogc {
5367 nvg__appendCommands(ctx, Command.Close);
5370 /// Sets the current sub-path winding, see NVGWinding and NVGSolidity.
5371 /// Group: paths
5372 public void pathWinding (NVGContext ctx, NVGWinding dir) nothrow @trusted @nogc {
5373 nvg__appendCommands(ctx, Command.Winding, cast(float)dir);
5376 /// Ditto.
5377 public void pathWinding (NVGContext ctx, NVGSolidity dir) nothrow @trusted @nogc {
5378 nvg__appendCommands(ctx, Command.Winding, cast(float)dir);
5381 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r,
5382 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW).
5383 * Angles are specified in radians.
5385 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo
5387 * Group: paths
5389 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 {
5390 static assert(mode == "original" || mode == "move" || mode == "line");
5392 float[3+5*7+100] vals = void;
5393 //int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo);
5394 static if (mode == "original") {
5395 immutable int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo);
5396 } else static if (mode == "move") {
5397 enum move = Command.MoveTo;
5398 } else static if (mode == "line") {
5399 enum move = Command.LineTo;
5400 } else {
5401 static assert(0, "wtf?!");
5404 // Clamp angles
5405 float da = a1-a0;
5406 if (dir == NVGWinding.CW) {
5407 if (nvg__absf(da) >= NVG_PI*2) {
5408 da = NVG_PI*2;
5409 } else {
5410 while (da < 0.0f) da += NVG_PI*2;
5412 } else {
5413 if (nvg__absf(da) >= NVG_PI*2) {
5414 da = -NVG_PI*2;
5415 } else {
5416 while (da > 0.0f) da -= NVG_PI*2;
5420 // Split arc into max 90 degree segments.
5421 immutable int ndivs = nvg__max(1, nvg__min(cast(int)(nvg__absf(da)/(NVG_PI*0.5f)+0.5f), 5));
5422 immutable float hda = (da/cast(float)ndivs)/2.0f;
5423 float kappa = nvg__absf(4.0f/3.0f*(1.0f-nvg__cosf(hda))/nvg__sinf(hda));
5425 if (dir == NVGWinding.CCW) kappa = -kappa;
5427 int nvals = 0;
5428 float px = 0, py = 0, ptanx = 0, ptany = 0;
5429 foreach (int i; 0..ndivs+1) {
5430 immutable float a = a0+da*(i/cast(float)ndivs);
5431 immutable float dx = nvg__cosf(a);
5432 immutable float dy = nvg__sinf(a);
5433 immutable float x = cx+dx*r;
5434 immutable float y = cy+dy*r;
5435 immutable float tanx = -dy*r*kappa;
5436 immutable float tany = dx*r*kappa;
5438 if (i == 0) {
5439 if (vals.length-nvals < 3) {
5440 // flush
5441 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5442 nvals = 0;
5444 vals.ptr[nvals++] = cast(float)move;
5445 vals.ptr[nvals++] = x;
5446 vals.ptr[nvals++] = y;
5447 } else {
5448 if (vals.length-nvals < 7) {
5449 // flush
5450 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5451 nvals = 0;
5453 vals.ptr[nvals++] = Command.BezierTo;
5454 vals.ptr[nvals++] = px+ptanx;
5455 vals.ptr[nvals++] = py+ptany;
5456 vals.ptr[nvals++] = x-tanx;
5457 vals.ptr[nvals++] = y-tany;
5458 vals.ptr[nvals++] = x;
5459 vals.ptr[nvals++] = y;
5461 px = x;
5462 py = y;
5463 ptanx = tanx;
5464 ptany = tany;
5467 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5471 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r,
5472 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW).
5473 * Angles are specified in radians.
5475 * Arguments: [cx, cy, r, a0, a1]*
5477 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo
5479 * Group: paths
5481 public void arc(string mode="original") (NVGContext ctx, NVGWinding dir, in float[] args) nothrow @trusted @nogc {
5482 static assert(mode == "original" || mode == "move" || mode == "line");
5483 enum ArgC = 5;
5484 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arc] call");
5485 if (args.length < ArgC) return;
5486 const(float)* aptr = args.ptr;
5487 foreach (immutable idx; 0..args.length/ArgC) {
5488 immutable cx = *aptr++;
5489 immutable cy = *aptr++;
5490 immutable r = *aptr++;
5491 immutable a0 = *aptr++;
5492 immutable a1 = *aptr++;
5493 ctx.arc!mode(dir, cx, cy, r, a0, a1);
5497 /// Creates new rectangle shaped sub-path.
5498 /// Group: paths
5499 @scriptable
5500 public void rect (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc {
5501 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5502 Command.MoveTo, x, y,
5503 Command.LineTo, x, y+h,
5504 Command.LineTo, x+w, y+h,
5505 Command.LineTo, x+w, y,
5506 Command.Close,
5510 /// Creates new rectangle shaped sub-path.
5511 /// Arguments: [x, y, w, h]*
5512 /// Group: paths
5513 public void rect (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5514 enum ArgC = 4;
5515 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [rect] call");
5516 if (args.length < ArgC) return;
5517 const(float)* aptr = args.ptr;
5518 foreach (immutable idx; 0..args.length/ArgC) {
5519 immutable x = *aptr++;
5520 immutable y = *aptr++;
5521 immutable w = *aptr++;
5522 immutable h = *aptr++;
5523 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5524 Command.MoveTo, x, y,
5525 Command.LineTo, x, y+h,
5526 Command.LineTo, x+w, y+h,
5527 Command.LineTo, x+w, y,
5528 Command.Close,
5533 /// Creates new rounded rectangle shaped sub-path.
5534 /// Group: paths
5535 @scriptable
5536 public void roundedRect (NVGContext ctx, in float x, in float y, in float w, in float h, in float radius) nothrow @trusted @nogc {
5537 ctx.roundedRectVarying(x, y, w, h, radius, radius, radius, radius);
5540 /// Creates new rounded rectangle shaped sub-path.
5541 /// Arguments: [x, y, w, h, radius]*
5542 /// Group: paths
5543 public void roundedRect (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5544 enum ArgC = 5;
5545 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRect] call");
5546 if (args.length < ArgC) return;
5547 const(float)* aptr = args.ptr;
5548 foreach (immutable idx; 0..args.length/ArgC) {
5549 immutable x = *aptr++;
5550 immutable y = *aptr++;
5551 immutable w = *aptr++;
5552 immutable h = *aptr++;
5553 immutable r = *aptr++;
5554 ctx.roundedRectVarying(x, y, w, h, r, r, r, r);
5558 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it.
5559 /// Group: paths
5560 @scriptable
5561 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 {
5562 if (rw < 0.1f || rh < 0.1f) {
5563 rect(ctx, x, y, w, h);
5564 } else {
5565 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5566 Command.MoveTo, x+rw, y,
5567 Command.LineTo, x+w-rw, y,
5568 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh,
5569 Command.LineTo, x+w, y+h-rh,
5570 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h,
5571 Command.LineTo, x+rw, y+h,
5572 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh,
5573 Command.LineTo, x, y+rh,
5574 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y,
5575 Command.Close,
5580 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it.
5581 /// Arguments: [x, y, w, h, rw, rh]*
5582 /// Group: paths
5583 public void roundedRectEllipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5584 enum ArgC = 6;
5585 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectEllipse] call");
5586 if (args.length < ArgC) return;
5587 const(float)* aptr = args.ptr;
5588 foreach (immutable idx; 0..args.length/ArgC) {
5589 immutable x = *aptr++;
5590 immutable y = *aptr++;
5591 immutable w = *aptr++;
5592 immutable h = *aptr++;
5593 immutable rw = *aptr++;
5594 immutable rh = *aptr++;
5595 if (rw < 0.1f || rh < 0.1f) {
5596 rect(ctx, x, y, w, h);
5597 } else {
5598 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5599 Command.MoveTo, x+rw, y,
5600 Command.LineTo, x+w-rw, y,
5601 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh,
5602 Command.LineTo, x+w, y+h-rh,
5603 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h,
5604 Command.LineTo, x+rw, y+h,
5605 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh,
5606 Command.LineTo, x, y+rh,
5607 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y,
5608 Command.Close,
5614 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner.
5615 /// Group: paths
5616 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 {
5617 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) {
5618 ctx.rect(x, y, w, h);
5619 } else {
5620 immutable float halfw = nvg__absf(w)*0.5f;
5621 immutable float halfh = nvg__absf(h)*0.5f;
5622 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h);
5623 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h);
5624 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h);
5625 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h);
5626 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5627 Command.MoveTo, x, y+ryTL,
5628 Command.LineTo, x, y+h-ryBL,
5629 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h,
5630 Command.LineTo, x+w-rxBR, y+h,
5631 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR,
5632 Command.LineTo, x+w, y+ryTR,
5633 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y,
5634 Command.LineTo, x+rxTL, y,
5635 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL,
5636 Command.Close,
5641 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner.
5642 /// Arguments: [x, y, w, h, radTopLeft, radTopRight, radBottomRight, radBottomLeft]*
5643 /// Group: paths
5644 public void roundedRectVarying (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5645 enum ArgC = 8;
5646 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectVarying] call");
5647 if (args.length < ArgC) return;
5648 const(float)* aptr = args.ptr;
5649 foreach (immutable idx; 0..args.length/ArgC) {
5650 immutable x = *aptr++;
5651 immutable y = *aptr++;
5652 immutable w = *aptr++;
5653 immutable h = *aptr++;
5654 immutable radTopLeft = *aptr++;
5655 immutable radTopRight = *aptr++;
5656 immutable radBottomRight = *aptr++;
5657 immutable radBottomLeft = *aptr++;
5658 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) {
5659 ctx.rect(x, y, w, h);
5660 } else {
5661 immutable float halfw = nvg__absf(w)*0.5f;
5662 immutable float halfh = nvg__absf(h)*0.5f;
5663 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h);
5664 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h);
5665 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h);
5666 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h);
5667 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5668 Command.MoveTo, x, y+ryTL,
5669 Command.LineTo, x, y+h-ryBL,
5670 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h,
5671 Command.LineTo, x+w-rxBR, y+h,
5672 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR,
5673 Command.LineTo, x+w, y+ryTR,
5674 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y,
5675 Command.LineTo, x+rxTL, y,
5676 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL,
5677 Command.Close,
5683 /// Creates new ellipse shaped sub-path.
5684 /// Group: paths
5685 public void ellipse (NVGContext ctx, in float cx, in float cy, in float rx, in float ry) nothrow @trusted @nogc {
5686 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5687 Command.MoveTo, cx-rx, cy,
5688 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,
5689 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,
5690 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,
5691 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,
5692 Command.Close,
5696 /// Creates new ellipse shaped sub-path.
5697 /// Arguments: [cx, cy, rx, ry]*
5698 /// Group: paths
5699 public void ellipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5700 enum ArgC = 4;
5701 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [ellipse] call");
5702 if (args.length < ArgC) return;
5703 const(float)* aptr = args.ptr;
5704 foreach (immutable idx; 0..args.length/ArgC) {
5705 immutable cx = *aptr++;
5706 immutable cy = *aptr++;
5707 immutable rx = *aptr++;
5708 immutable ry = *aptr++;
5709 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5710 Command.MoveTo, cx-rx, cy,
5711 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,
5712 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,
5713 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,
5714 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,
5715 Command.Close,
5720 /// Creates new circle shaped sub-path.
5721 /// Group: paths
5722 public void circle (NVGContext ctx, in float cx, in float cy, in float r) nothrow @trusted @nogc {
5723 ctx.ellipse(cx, cy, r, r);
5726 /// Creates new circle shaped sub-path.
5727 /// Arguments: [cx, cy, r]*
5728 /// Group: paths
5729 public void circle (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5730 enum ArgC = 3;
5731 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [circle] call");
5732 if (args.length < ArgC) return;
5733 const(float)* aptr = args.ptr;
5734 foreach (immutable idx; 0..args.length/ArgC) {
5735 immutable cx = *aptr++;
5736 immutable cy = *aptr++;
5737 immutable r = *aptr++;
5738 ctx.ellipse(cx, cy, r, r);
5742 // Debug function to dump cached path data.
5743 debug public void debugDumpPathCache (NVGContext ctx) nothrow @trusted @nogc {
5744 import core.stdc.stdio : printf;
5745 const(NVGpath)* path;
5746 printf("Dumping %d cached paths\n", ctx.cache.npaths);
5747 for (int i = 0; i < ctx.cache.npaths; ++i) {
5748 path = &ctx.cache.paths[i];
5749 printf("-Path %d\n", i);
5750 if (path.nfill) {
5751 printf("-fill: %d\n", path.nfill);
5752 for (int j = 0; j < path.nfill; ++j) printf("%f\t%f\n", path.fill[j].x, path.fill[j].y);
5754 if (path.nstroke) {
5755 printf("-stroke: %d\n", path.nstroke);
5756 for (int j = 0; j < path.nstroke; ++j) printf("%f\t%f\n", path.stroke[j].x, path.stroke[j].y);
5761 private bool ngv_isAA (NVGContext ctx) nothrow @trusted @nogc {
5762 NVGstate* state = nvg__getState(ctx);
5763 return (ctx.params.edgeAntiAlias && state.shapeAntiAlias);
5766 private float nvg_getFringe (NVGContext ctx) nothrow @trusted @nogc {
5767 NVGstate* state = nvg__getState(ctx);
5768 return (ctx.params.edgeAntiAlias && state.shapeAntiAlias ? nvg__max(0.0f, ctx.fringeWidth) : 0.0f);
5771 private float nvg_getFringePixel (NVGContext ctx) nothrow @trusted @nogc {
5772 NVGstate* state = nvg__getState(ctx);
5773 if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) {
5774 immutable float fringe = nvg__max(0.0f, ctx.fringeWidth);
5775 return (fringe > 0.0f ? fringe : 1.0f); //HACK!
5776 } else {
5777 return 1.0f;
5781 // Flatten path, prepare it for fill operation.
5782 void nvg__prepareFill (NVGContext ctx) nothrow @trusted @nogc {
5783 NVGpathCache* cache = ctx.cache;
5784 NVGstate* state = nvg__getState(ctx);
5786 nvg__flattenPaths!false(ctx);
5788 immutable float fringe = nvg_getFringe(ctx);
5789 nvg__expandFill(ctx, fringe, NVGLineCap.Miter, 2.4f);
5791 cache.evenOddMode = state.evenOddMode;
5792 cache.fringeWidth = fringe/*ctx.fringeWidth*/;
5793 cache.fillReady = true;
5794 cache.strokeReady = false;
5795 cache.clipmode = NVGClipMode.None;
5798 // Flatten path, prepare it for stroke operation.
5799 void nvg__prepareStroke (NVGContext ctx) nothrow @trusted @nogc {
5800 NVGstate* state = nvg__getState(ctx);
5801 NVGpathCache* cache = ctx.cache;
5803 nvg__flattenPaths!true(ctx);
5805 immutable float fringe = nvg_getFringe(ctx);
5806 // this is used to check stroke width
5807 immutable float pixelFringe = nvg_getFringePixel(ctx);
5809 immutable float scale = nvg__getAverageScale(state.xform);
5810 float strokeWidth = nvg__clamp(state.strokeWidth*scale, 0.0f, 200.0f);
5812 if (strokeWidth < /*ctx.fringeWidth*/pixelFringe) {
5813 // If the stroke width is less than pixel size, use alpha to emulate coverage.
5814 // Since coverage is area, scale by alpha*alpha.
5815 immutable float alpha = nvg__clamp(strokeWidth/pixelFringe/*ctx.fringeWidth*/, 0.0f, 1.0f);
5816 cache.strokeAlphaMul = alpha*alpha;
5817 strokeWidth = /*ctx.fringeWidth*/pixelFringe;
5818 } else {
5819 cache.strokeAlphaMul = 1.0f;
5821 cache.strokeWidth = strokeWidth;
5823 nvg__expandStroke(ctx, strokeWidth*0.5f, fringe, state.lineCap, state.lineJoin, state.miterLimit);
5825 cache.fringeWidth = fringe/*ctx.fringeWidth*/;
5826 cache.fillReady = false;
5827 cache.strokeReady = true;
5828 cache.clipmode = NVGClipMode.None;
5831 /// Fills the current path with current fill style.
5832 /// Group: paths
5833 @scriptable
5834 public void fill (NVGContext ctx) nothrow @trusted @nogc {
5835 NVGstate* state = nvg__getState(ctx);
5837 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) {
5838 ctx.pathPickRegistered |= NVGPickKind.Fill<<16;
5839 ctx.currFillHitId = ctx.pathPickId;
5842 nvg__prepareFill(ctx);
5844 // apply global alpha
5845 NVGPaint fillPaint = state.fill;
5846 fillPaint.innerColor.a *= state.alpha;
5847 fillPaint.middleColor.a *= state.alpha;
5848 fillPaint.outerColor.a *= state.alpha;
5850 ctx.appendCurrentPathToCache(ctx.recset, state.fill);
5852 if (ctx.recblockdraw) return;
5854 ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &fillPaint, &state.scissor, nvg_getFringe(ctx)/*ctx.fringeWidth*/, ctx.cache.bounds.ptr, ctx.cache.paths, ctx.cache.npaths, state.evenOddMode);
5856 // count triangles
5857 foreach (int i; 0..ctx.cache.npaths) {
5858 NVGpath* path = &ctx.cache.paths[i];
5859 ctx.fillTriCount += path.nfill-2;
5860 ctx.fillTriCount += path.nstroke-2;
5861 ctx.drawCallCount += 2;
5865 /// Fills the current path with current stroke style.
5866 /// Group: paths
5867 @scriptable
5868 public void stroke (NVGContext ctx) nothrow @trusted @nogc {
5869 NVGstate* state = nvg__getState(ctx);
5871 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) {
5872 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16;
5873 ctx.currStrokeHitId = ctx.pathPickId;
5876 nvg__prepareStroke(ctx);
5878 NVGpathCache* cache = ctx.cache;
5880 NVGPaint strokePaint = state.stroke;
5881 strokePaint.innerColor.a *= cache.strokeAlphaMul;
5882 strokePaint.middleColor.a *= cache.strokeAlphaMul;
5883 strokePaint.outerColor.a *= cache.strokeAlphaMul;
5885 // apply global alpha
5886 strokePaint.innerColor.a *= state.alpha;
5887 strokePaint.middleColor.a *= state.alpha;
5888 strokePaint.outerColor.a *= state.alpha;
5890 ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
5892 if (ctx.recblockdraw) return;
5894 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &strokePaint, &state.scissor, nvg_getFringe(ctx)/*ctx.fringeWidth*/, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths);
5896 // count triangles
5897 foreach (int i; 0..ctx.cache.npaths) {
5898 NVGpath* path = &ctx.cache.paths[i];
5899 ctx.strokeTriCount += path.nstroke-2;
5900 ++ctx.drawCallCount;
5904 /// Sets current path as clipping region.
5905 /// Group: clipping
5906 public void clip (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc {
5907 NVGstate* state = nvg__getState(ctx);
5909 if (aclipmode == NVGClipMode.None) return;
5910 if (ctx.recblockdraw) return; //???
5912 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr);
5915 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) {
5916 ctx.pathPickRegistered |= NVGPickKind.Fill<<16;
5917 ctx.currFillHitId = ctx.pathPickId;
5921 nvg__prepareFill(ctx);
5923 // apply global alpha
5924 NVGPaint fillPaint = state.fill;
5925 fillPaint.innerColor.a *= state.alpha;
5926 fillPaint.middleColor.a *= state.alpha;
5927 fillPaint.outerColor.a *= state.alpha;
5929 //ctx.appendCurrentPathToCache(ctx.recset, state.fill);
5931 ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, aclipmode, &fillPaint, &state.scissor, nvg_getFringe(ctx)/*ctx.fringeWidth*/, ctx.cache.bounds.ptr, ctx.cache.paths, ctx.cache.npaths, state.evenOddMode);
5933 // count triangles
5934 foreach (int i; 0..ctx.cache.npaths) {
5935 NVGpath* path = &ctx.cache.paths[i];
5936 ctx.fillTriCount += path.nfill-2;
5937 ctx.fillTriCount += path.nstroke-2;
5938 ctx.drawCallCount += 2;
5942 /// Sets current path as clipping region.
5943 /// Group: clipping
5944 public alias clipFill = clip;
5946 /// Sets current path' stroke as clipping region.
5947 /// Group: clipping
5948 public void clipStroke (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc {
5949 NVGstate* state = nvg__getState(ctx);
5951 if (aclipmode == NVGClipMode.None) return;
5952 if (ctx.recblockdraw) return; //???
5954 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr);
5957 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) {
5958 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16;
5959 ctx.currStrokeHitId = ctx.pathPickId;
5963 nvg__prepareStroke(ctx);
5965 NVGpathCache* cache = ctx.cache;
5967 NVGPaint strokePaint = state.stroke;
5968 strokePaint.innerColor.a *= cache.strokeAlphaMul;
5969 strokePaint.middleColor.a *= cache.strokeAlphaMul;
5970 strokePaint.outerColor.a *= cache.strokeAlphaMul;
5972 // apply global alpha
5973 strokePaint.innerColor.a *= state.alpha;
5974 strokePaint.middleColor.a *= state.alpha;
5975 strokePaint.outerColor.a *= state.alpha;
5977 //ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
5979 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, aclipmode, &strokePaint, &state.scissor, nvg_getFringe(ctx)/*ctx.fringeWidth*/, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths);
5981 // count triangles
5982 foreach (int i; 0..ctx.cache.npaths) {
5983 NVGpath* path = &ctx.cache.paths[i];
5984 ctx.strokeTriCount += path.nstroke-2;
5985 ++ctx.drawCallCount;
5990 // ////////////////////////////////////////////////////////////////////////// //
5991 // Picking API
5993 // most of the code is by Michael Wynne <mike@mikesspace.net>
5994 // https://github.com/memononen/nanovg/pull/230
5995 // https://github.com/MikeWW/nanovg
5997 /// Pick type query. Used in [hitTest] and [hitTestAll].
5998 /// Group: picking_api
5999 public enum NVGPickKind : ubyte {
6000 Fill = 0x01, ///
6001 Stroke = 0x02, ///
6002 All = 0x03, ///
6005 /// Marks the fill of the current path as pickable with the specified id.
6006 /// Note that you can create and mark path without rasterizing it.
6007 /// Group: picking_api
6008 public void currFillHitId (NVGContext ctx, int id) nothrow @trusted @nogc {
6009 NVGpickScene* ps = nvg__pickSceneGet(ctx);
6010 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/false);
6011 nvg__pickSceneInsert(ps, pp);
6014 public alias currFillPickId = currFillHitId; /// Ditto.
6016 /// Marks the stroke of the current path as pickable with the specified id.
6017 /// Note that you can create and mark path without rasterizing it.
6018 /// Group: picking_api
6019 public void currStrokeHitId (NVGContext ctx, int id) nothrow @trusted @nogc {
6020 NVGpickScene* ps = nvg__pickSceneGet(ctx);
6021 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/true);
6022 nvg__pickSceneInsert(ps, pp);
6025 public alias currStrokePickId = currStrokeHitId; /// Ditto.
6027 // Marks the saved path set (fill) as pickable with the specified id.
6028 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!)
6029 // Group: picking_api
6031 public void pathSetFillHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc {
6032 if (svp is null) return;
6033 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context");
6034 foreach (ref cp; svp.caches[0..svp.ncaches]) {
6035 NVGpickScene* ps = nvg__pickSceneGet(ctx);
6036 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/false);
6037 nvg__pickSceneInsert(ps, pp);
6042 // Marks the saved path set (stroke) as pickable with the specified id.
6043 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!)
6044 // Group: picking_api
6046 public void pathSetStrokeHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc {
6047 if (svp is null) return;
6048 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context");
6049 foreach (ref cp; svp.caches[0..svp.ncaches]) {
6050 NVGpickScene* ps = nvg__pickSceneGet(ctx);
6051 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/true);
6052 nvg__pickSceneInsert(ps, pp);
6057 private template IsGoodHitTestDG(DG) {
6058 enum IsGoodHitTestDG =
6059 __traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); }) ||
6060 __traits(compiles, (){ DG dg; dg(cast(int)42, cast(int)666); });
6063 private template IsGoodHitTestInternalDG(DG) {
6064 enum IsGoodHitTestInternalDG =
6065 __traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); }) ||
6066 __traits(compiles, (){ DG dg; NVGpickPath* pp; dg(pp); });
6069 /// Call delegate [dg] for each path under the specified position (in no particular order).
6070 /// Returns the id of the path for which delegate [dg] returned true or [NVGNoPick].
6071 /// dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending).
6072 /// Group: picking_api
6073 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) {
6074 if (ctx.pickScene is null || ctx.pickScene.npaths == 0 || (kind&NVGPickKind.All) == 0) return -1;
6076 NVGpickScene* ps = ctx.pickScene;
6077 int levelwidth = 1<<(ps.nlevels-1);
6078 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth);
6079 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth);
6080 int npicked = 0;
6082 // if we are interested only in most-toplevel path, there is no reason to check paths with worser order.
6083 // but we cannot just get out on the first path found, 'cause we are using quad tree to speed up bounds
6084 // checking, so path walking order is not guaranteed.
6085 static if (bestOrder) {
6086 int lastBestOrder = int.min;
6089 //{ import core.stdc.stdio; printf("npaths=%d\n", ps.npaths); }
6090 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) {
6091 for (NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; pp !is null; pp = pp.next) {
6092 //{ 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); }
6093 static if (bestOrder) {
6094 // reject earlier paths
6095 if (pp.order <= lastBestOrder) continue; // not interesting
6097 immutable uint kpx = kind&pp.flags&3;
6098 if (kpx == 0) continue; // not interesting
6099 if (!nvg__pickPathTestBounds(ctx, ps, pp, x, y)) continue; // not interesting
6100 //{ import core.stdc.stdio; printf("in bounds!\n"); }
6101 int hit = 0;
6102 if (kpx&NVGPickKind.Stroke) hit = nvg__pickPathStroke(ps, pp, x, y);
6103 if (!hit && (kpx&NVGPickKind.Fill)) hit = nvg__pickPath(ps, pp, x, y);
6104 if (!hit) continue;
6105 //{ import core.stdc.stdio; printf(" HIT!\n"); }
6106 static if (bestOrder) lastBestOrder = pp.order;
6107 static if (IsGoodHitTestDG!DG) {
6108 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) {
6109 if (dg(pp.id, cast(int)pp.order)) return pp.id;
6110 } else {
6111 dg(pp.id, cast(int)pp.order);
6113 } else {
6114 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) {
6115 if (dg(pp)) return pp.id;
6116 } else {
6117 dg(pp);
6121 cellx >>= 1;
6122 celly >>= 1;
6123 levelwidth >>= 1;
6126 return -1;
6129 /// Fills ids with a list of the top most hit ids (from bottom to top) under the specified position.
6130 /// Returns the slice of [ids].
6131 /// Group: picking_api
6132 public int[] hitTestAll (NVGContext ctx, in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc {
6133 if (ctx.pickScene is null || ids.length == 0) return ids[0..0];
6135 int npicked = 0;
6136 NVGpickScene* ps = ctx.pickScene;
6138 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
6139 if (npicked == ps.cpicked) {
6140 int cpicked = ps.cpicked+ps.cpicked;
6141 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked);
6142 if (picked is null) return true; // abort
6143 ps.cpicked = cpicked;
6144 ps.picked = picked;
6146 ps.picked[npicked] = pp;
6147 ++npicked;
6148 return false; // go on
6151 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths);
6153 assert(npicked >= 0);
6154 if (npicked > ids.length) npicked = cast(int)ids.length;
6155 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id;
6157 return ids[0..npicked];
6160 /// Returns the id of the pickable shape containing x,y or [NVGNoPick] if no shape was found.
6161 /// Group: picking_api
6162 public int hitTest (NVGContext ctx, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc {
6163 if (ctx.pickScene is null) return -1;
6165 int bestOrder = int.min;
6166 int bestID = -1;
6168 ctx.hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) {
6169 if (pp.order > bestOrder) {
6170 bestOrder = pp.order;
6171 bestID = pp.id;
6175 return bestID;
6178 /// Returns `true` if the path with the given id contains x,y.
6179 /// Group: picking_api
6180 public bool hitTestForId (NVGContext ctx, in int id, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc {
6181 if (ctx.pickScene is null || id == NVGNoPick) return false;
6183 bool res = false;
6185 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) {
6186 if (pp.id == id) {
6187 res = true;
6188 return true; // stop
6190 return false; // continue
6193 return res;
6196 /// Returns `true` if the given point is within the fill of the currently defined path.
6197 /// This operation can be done before rasterizing the current path.
6198 /// Group: picking_api
6199 public bool hitTestCurrFill (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
6200 NVGpickScene* ps = nvg__pickSceneGet(ctx);
6201 int oldnpoints = ps.npoints;
6202 int oldnsegments = ps.nsegments;
6203 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/false);
6204 if (pp is null) return false; // oops
6205 scope(exit) {
6206 nvg__freePickPath(ps, pp);
6207 ps.npoints = oldnpoints;
6208 ps.nsegments = oldnsegments;
6210 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPath(ps, pp, x, y) : false);
6213 public alias isPointInPath = hitTestCurrFill; /// Ditto.
6215 /// Returns `true` if the given point is within the stroke of the currently defined path.
6216 /// This operation can be done before rasterizing the current path.
6217 /// Group: picking_api
6218 public bool hitTestCurrStroke (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
6219 NVGpickScene* ps = nvg__pickSceneGet(ctx);
6220 int oldnpoints = ps.npoints;
6221 int oldnsegments = ps.nsegments;
6222 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/true);
6223 if (pp is null) return false; // oops
6224 scope(exit) {
6225 nvg__freePickPath(ps, pp);
6226 ps.npoints = oldnpoints;
6227 ps.nsegments = oldnsegments;
6229 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPathStroke(ps, pp, x, y) : false);
6233 nothrow @trusted @nogc {
6234 extern(C) {
6235 private alias _compare_fp_t = int function (const void*, const void*) nothrow @nogc;
6236 private extern(C) void qsort (scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc;
6238 extern(C) int nvg__comparePaths (const void* a, const void* b) {
6239 return (*cast(const(NVGpickPath)**)b).order-(*cast(const(NVGpickPath)**)a).order;
6243 enum NVGPickEPS = 0.0001f;
6245 // Segment flags
6246 enum NVGSegmentFlags {
6247 Corner = 1,
6248 Bevel = 2,
6249 InnerBevel = 4,
6250 Cap = 8,
6251 Endcap = 16,
6254 // Path flags
6255 enum NVGPathFlags : ushort {
6256 Fill = NVGPickKind.Fill,
6257 Stroke = NVGPickKind.Stroke,
6258 Scissor = 0x80,
6261 struct NVGsegment {
6262 int firstPoint; // Index into NVGpickScene.points
6263 short type; // NVG_LINETO or NVG_BEZIERTO
6264 short flags; // Flags relate to the corner between the prev segment and this one.
6265 float[4] bounds;
6266 float[2] startDir; // Direction at t == 0
6267 float[2] endDir; // Direction at t == 1
6268 float[2] miterDir; // Direction of miter of corner between the prev segment and this one.
6271 struct NVGpickSubPath {
6272 short winding; // TODO: Merge to flag field
6273 bool closed; // TODO: Merge to flag field
6275 int firstSegment; // Index into NVGpickScene.segments
6276 int nsegments;
6278 float[4] bounds;
6280 NVGpickSubPath* next;
6283 struct NVGpickPath {
6284 int id;
6285 short flags;
6286 short order;
6287 float strokeWidth;
6288 float miterLimit;
6289 short lineCap;
6290 short lineJoin;
6291 bool evenOddMode;
6293 float[4] bounds;
6294 int scissor; // Indexes into ps->points and defines scissor rect as XVec, YVec and Center
6296 NVGpickSubPath* subPaths;
6297 NVGpickPath* next;
6298 NVGpickPath* cellnext;
6301 struct NVGpickScene {
6302 int npaths;
6304 NVGpickPath* paths; // Linked list of paths
6305 NVGpickPath* lastPath; // The last path in the paths linked list (the first path added)
6306 NVGpickPath* freePaths; // Linked list of free paths
6308 NVGpickSubPath* freeSubPaths; // Linked list of free sub paths
6310 int width;
6311 int height;
6313 // Points for all path sub paths.
6314 float* points;
6315 int npoints;
6316 int cpoints;
6318 // Segments for all path sub paths
6319 NVGsegment* segments;
6320 int nsegments;
6321 int csegments;
6323 // Implicit quadtree
6324 float xdim; // Width / (1 << nlevels)
6325 float ydim; // Height / (1 << nlevels)
6326 int ncells; // Total number of cells in all levels
6327 int nlevels;
6328 NVGpickPath*** levels; // Index: [Level][LevelY * LevelW + LevelX] Value: Linked list of paths
6330 // Temp storage for picking
6331 int cpicked;
6332 NVGpickPath** picked;
6336 // bounds utilities
6337 void nvg__initBounds (ref float[4] bounds) {
6338 bounds.ptr[0] = bounds.ptr[1] = float.max;
6339 bounds.ptr[2] = bounds.ptr[3] = -float.max;
6342 void nvg__expandBounds (ref float[4] bounds, const(float)* points, int npoints) {
6343 npoints *= 2;
6344 for (int i = 0; i < npoints; i += 2) {
6345 bounds.ptr[0] = nvg__min(bounds.ptr[0], points[i]);
6346 bounds.ptr[1] = nvg__min(bounds.ptr[1], points[i+1]);
6347 bounds.ptr[2] = nvg__max(bounds.ptr[2], points[i]);
6348 bounds.ptr[3] = nvg__max(bounds.ptr[3], points[i+1]);
6352 void nvg__unionBounds (ref float[4] bounds, in ref float[4] boundsB) {
6353 bounds.ptr[0] = nvg__min(bounds.ptr[0], boundsB.ptr[0]);
6354 bounds.ptr[1] = nvg__min(bounds.ptr[1], boundsB.ptr[1]);
6355 bounds.ptr[2] = nvg__max(bounds.ptr[2], boundsB.ptr[2]);
6356 bounds.ptr[3] = nvg__max(bounds.ptr[3], boundsB.ptr[3]);
6359 void nvg__intersectBounds (ref float[4] bounds, in ref float[4] boundsB) {
6360 bounds.ptr[0] = nvg__max(boundsB.ptr[0], bounds.ptr[0]);
6361 bounds.ptr[1] = nvg__max(boundsB.ptr[1], bounds.ptr[1]);
6362 bounds.ptr[2] = nvg__min(boundsB.ptr[2], bounds.ptr[2]);
6363 bounds.ptr[3] = nvg__min(boundsB.ptr[3], bounds.ptr[3]);
6365 bounds.ptr[2] = nvg__max(bounds.ptr[0], bounds.ptr[2]);
6366 bounds.ptr[3] = nvg__max(bounds.ptr[1], bounds.ptr[3]);
6369 bool nvg__pointInBounds (in float x, in float y, in ref float[4] bounds) {
6370 pragma(inline, true);
6371 return (x >= bounds.ptr[0] && x <= bounds.ptr[2] && y >= bounds.ptr[1] && y <= bounds.ptr[3]);
6374 // building paths & sub paths
6375 int nvg__pickSceneAddPoints (NVGpickScene* ps, const(float)* xy, int n) {
6376 import core.stdc.string : memcpy;
6377 if (ps.npoints+n > ps.cpoints) {
6378 import core.stdc.stdlib : realloc;
6379 int cpoints = ps.npoints+n+(ps.cpoints<<1);
6380 float* points = cast(float*)realloc(ps.points, float.sizeof*2*cpoints);
6381 if (points is null) assert(0, "NanoVega: out of memory");
6382 ps.points = points;
6383 ps.cpoints = cpoints;
6385 int i = ps.npoints;
6386 if (xy !is null) memcpy(&ps.points[i*2], xy, float.sizeof*2*n);
6387 ps.npoints += n;
6388 return i;
6391 void nvg__pickSubPathAddSegment (NVGpickScene* ps, NVGpickSubPath* psp, int firstPoint, int type, short flags) {
6392 NVGsegment* seg = null;
6393 if (ps.nsegments == ps.csegments) {
6394 int csegments = 1+ps.csegments+(ps.csegments<<1);
6395 NVGsegment* segments = cast(NVGsegment*)realloc(ps.segments, NVGsegment.sizeof*csegments);
6396 if (segments is null) assert(0, "NanoVega: out of memory");
6397 ps.segments = segments;
6398 ps.csegments = csegments;
6401 if (psp.firstSegment == -1) psp.firstSegment = ps.nsegments;
6403 seg = &ps.segments[ps.nsegments];
6404 ++ps.nsegments;
6405 seg.firstPoint = firstPoint;
6406 seg.type = cast(short)type;
6407 seg.flags = flags;
6408 ++psp.nsegments;
6410 nvg__segmentDir(ps, psp, seg, 0, seg.startDir);
6411 nvg__segmentDir(ps, psp, seg, 1, seg.endDir);
6414 void nvg__segmentDir (NVGpickScene* ps, NVGpickSubPath* psp, NVGsegment* seg, float t, ref float[2] d) {
6415 const(float)* points = &ps.points[seg.firstPoint*2];
6416 immutable float x0 = points[0*2+0], x1 = points[1*2+0];
6417 immutable float y0 = points[0*2+1], y1 = points[1*2+1];
6418 switch (seg.type) {
6419 case Command.LineTo:
6420 d.ptr[0] = x1-x0;
6421 d.ptr[1] = y1-y0;
6422 nvg__normalize(&d.ptr[0], &d.ptr[1]);
6423 break;
6424 case Command.BezierTo:
6425 immutable float x2 = points[2*2+0];
6426 immutable float y2 = points[2*2+1];
6427 immutable float x3 = points[3*2+0];
6428 immutable float y3 = points[3*2+1];
6430 immutable float omt = 1.0f-t;
6431 immutable float omt2 = omt*omt;
6432 immutable float t2 = t*t;
6434 d.ptr[0] =
6435 3.0f*omt2*(x1-x0)+
6436 6.0f*omt*t*(x2-x1)+
6437 3.0f*t2*(x3-x2);
6439 d.ptr[1] =
6440 3.0f*omt2*(y1-y0)+
6441 6.0f*omt*t*(y2-y1)+
6442 3.0f*t2*(y3-y2);
6444 nvg__normalize(&d.ptr[0], &d.ptr[1]);
6445 break;
6446 default:
6447 break;
6451 void nvg__pickSubPathAddFillSupports (NVGpickScene* ps, NVGpickSubPath* psp) {
6452 if (psp.firstSegment == -1) return;
6453 NVGsegment* segments = &ps.segments[psp.firstSegment];
6454 for (int s = 0; s < psp.nsegments; ++s) {
6455 NVGsegment* seg = &segments[s];
6456 const(float)* points = &ps.points[seg.firstPoint*2];
6457 if (seg.type == Command.LineTo) {
6458 nvg__initBounds(seg.bounds);
6459 nvg__expandBounds(seg.bounds, points, 2);
6460 } else {
6461 nvg__bezierBounds(points, seg.bounds);
6466 void nvg__pickSubPathAddStrokeSupports (NVGpickScene* ps, NVGpickSubPath* psp, float strokeWidth, int lineCap, int lineJoin, float miterLimit) {
6467 if (psp.firstSegment == -1) return;
6468 immutable bool closed = psp.closed;
6469 const(float)* points = ps.points;
6470 NVGsegment* seg = null;
6471 NVGsegment* segments = &ps.segments[psp.firstSegment];
6472 int nsegments = psp.nsegments;
6473 NVGsegment* prevseg = (closed ? &segments[psp.nsegments-1] : null);
6475 int ns = 0; // nsupports
6476 float[32] supportingPoints = void;
6477 int firstPoint, lastPoint;
6479 if (!closed) {
6480 segments[0].flags |= NVGSegmentFlags.Cap;
6481 segments[nsegments-1].flags |= NVGSegmentFlags.Endcap;
6484 for (int s = 0; s < nsegments; ++s) {
6485 seg = &segments[s];
6486 nvg__initBounds(seg.bounds);
6488 firstPoint = seg.firstPoint*2;
6489 lastPoint = firstPoint+(seg.type == Command.LineTo ? 2 : 6);
6491 ns = 0;
6493 // First two supporting points are either side of the start point
6494 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[1]*strokeWidth;
6495 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.startDir.ptr[0]*strokeWidth;
6497 supportingPoints.ptr[ns++] = points[firstPoint]+seg.startDir.ptr[1]*strokeWidth;
6498 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[0]*strokeWidth;
6500 // Second two supporting points are either side of the end point
6501 supportingPoints.ptr[ns++] = points[lastPoint]-seg.endDir.ptr[1]*strokeWidth;
6502 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[0]*strokeWidth;
6504 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[1]*strokeWidth;
6505 supportingPoints.ptr[ns++] = points[lastPoint+1]-seg.endDir.ptr[0]*strokeWidth;
6507 if ((seg.flags&NVGSegmentFlags.Corner) && prevseg !is null) {
6508 seg.miterDir.ptr[0] = 0.5f*(-prevseg.endDir.ptr[1]-seg.startDir.ptr[1]);
6509 seg.miterDir.ptr[1] = 0.5f*(prevseg.endDir.ptr[0]+seg.startDir.ptr[0]);
6511 immutable float M2 = seg.miterDir.ptr[0]*seg.miterDir.ptr[0]+seg.miterDir.ptr[1]*seg.miterDir.ptr[1];
6513 if (M2 > 0.000001f) {
6514 float scale = 1.0f/M2;
6515 if (scale > 600.0f) scale = 600.0f;
6516 seg.miterDir.ptr[0] *= scale;
6517 seg.miterDir.ptr[1] *= scale;
6520 //NVG_PICK_DEBUG_VECTOR_SCALE(&points[firstPoint], seg.miterDir, 10);
6522 // Add an additional support at the corner on the other line
6523 supportingPoints.ptr[ns++] = points[firstPoint]-prevseg.endDir.ptr[1]*strokeWidth;
6524 supportingPoints.ptr[ns++] = points[firstPoint+1]+prevseg.endDir.ptr[0]*strokeWidth;
6526 if (lineJoin == NVGLineCap.Miter || lineJoin == NVGLineCap.Bevel) {
6527 // Set a corner as beveled if the join type is bevel or mitered and
6528 // miterLimit is hit.
6529 if (lineJoin == NVGLineCap.Bevel || (M2*miterLimit*miterLimit) < 1.0f) {
6530 seg.flags |= NVGSegmentFlags.Bevel;
6531 } else {
6532 // Corner is mitered - add miter point as a support
6533 supportingPoints.ptr[ns++] = points[firstPoint]+seg.miterDir.ptr[0]*strokeWidth;
6534 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.miterDir.ptr[1]*strokeWidth;
6536 } else if (lineJoin == NVGLineCap.Round) {
6537 // ... and at the midpoint of the corner arc
6538 float[2] vertexN = [ -seg.startDir.ptr[0]+prevseg.endDir.ptr[0], -seg.startDir.ptr[1]+prevseg.endDir.ptr[1] ];
6539 nvg__normalize(&vertexN[0], &vertexN[1]);
6541 supportingPoints.ptr[ns++] = points[firstPoint]+vertexN[0]*strokeWidth;
6542 supportingPoints.ptr[ns++] = points[firstPoint+1]+vertexN[1]*strokeWidth;
6546 if (seg.flags&NVGSegmentFlags.Cap) {
6547 switch (lineCap) {
6548 case NVGLineCap.Butt:
6549 // supports for butt already added
6550 break;
6551 case NVGLineCap.Square:
6552 // square cap supports are just the original two supports moved out along the direction
6553 supportingPoints.ptr[ns++] = supportingPoints.ptr[0]-seg.startDir.ptr[0]*strokeWidth;
6554 supportingPoints.ptr[ns++] = supportingPoints.ptr[1]-seg.startDir.ptr[1]*strokeWidth;
6555 supportingPoints.ptr[ns++] = supportingPoints.ptr[2]-seg.startDir.ptr[0]*strokeWidth;
6556 supportingPoints.ptr[ns++] = supportingPoints.ptr[3]-seg.startDir.ptr[1]*strokeWidth;
6557 break;
6558 case NVGLineCap.Round:
6559 // add one additional support for the round cap along the dir
6560 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[0]*strokeWidth;
6561 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[1]*strokeWidth;
6562 break;
6563 default:
6564 break;
6568 if (seg.flags&NVGSegmentFlags.Endcap) {
6569 // end supporting points, either side of line
6570 int end = 4;
6571 switch(lineCap) {
6572 case NVGLineCap.Butt:
6573 // supports for butt already added
6574 break;
6575 case NVGLineCap.Square:
6576 // square cap supports are just the original two supports moved out along the direction
6577 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+0]+seg.endDir.ptr[0]*strokeWidth;
6578 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+1]+seg.endDir.ptr[1]*strokeWidth;
6579 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+2]+seg.endDir.ptr[0]*strokeWidth;
6580 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+3]+seg.endDir.ptr[1]*strokeWidth;
6581 break;
6582 case NVGLineCap.Round:
6583 // add one additional support for the round cap along the dir
6584 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[0]*strokeWidth;
6585 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[1]*strokeWidth;
6586 break;
6587 default:
6588 break;
6592 nvg__expandBounds(seg.bounds, supportingPoints.ptr, ns/2);
6594 prevseg = seg;
6598 NVGpickPath* nvg__pickPathCreate (NVGContext context, const(float)[] acommands, int id, bool forStroke) {
6599 NVGpickScene* ps = nvg__pickSceneGet(context);
6600 if (ps is null) return null;
6602 int i = 0;
6604 int ncommands = cast(int)acommands.length;
6605 const(float)* commands = acommands.ptr;
6607 NVGpickPath* pp = null;
6608 NVGpickSubPath* psp = null;
6609 float[2] start = void;
6610 int firstPoint;
6612 //bool hasHoles = false;
6613 NVGpickSubPath* prev = null;
6615 float[8] points = void;
6616 float[2] inflections = void;
6617 int ninflections = 0;
6619 NVGstate* state = nvg__getState(context);
6620 float[4] totalBounds = void;
6621 NVGsegment* segments = null;
6622 const(NVGsegment)* seg = null;
6623 NVGpickSubPath *curpsp;
6625 pp = nvg__allocPickPath(ps);
6626 if (pp is null) return null;
6628 pp.id = id;
6630 bool hasPoints = false;
6632 void closeIt () {
6633 if (psp is null || !hasPoints) return;
6634 if (ps.points[(ps.npoints-1)*2] != start.ptr[0] || ps.points[(ps.npoints-1)*2+1] != start.ptr[1]) {
6635 firstPoint = nvg__pickSceneAddPoints(ps, start.ptr, 1);
6636 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, Command.LineTo, NVGSegmentFlags.Corner);
6638 psp.closed = true;
6641 while (i < ncommands) {
6642 int cmd = cast(int)commands[i++];
6643 switch (cmd) {
6644 case Command.MoveTo: // one coordinate pair
6645 const(float)* tfxy = commands+i;
6646 i += 2;
6648 // new starting point
6649 start.ptr[0..2] = tfxy[0..2];
6651 // start a new path for each sub path to handle sub paths that intersect other sub paths
6652 prev = psp;
6653 psp = nvg__allocPickSubPath(ps);
6654 if (psp is null) { psp = prev; break; }
6655 psp.firstSegment = -1;
6656 psp.winding = NVGSolidity.Solid;
6657 psp.next = prev;
6659 nvg__pickSceneAddPoints(ps, tfxy, 1);
6660 hasPoints = true;
6661 break;
6662 case Command.LineTo: // one coordinate pair
6663 const(float)* tfxy = commands+i;
6664 i += 2;
6665 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 1);
6666 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner);
6667 hasPoints = true;
6668 break;
6669 case Command.BezierTo: // three coordinate pairs
6670 const(float)* tfxy = commands+i;
6671 i += 3*2;
6673 // Split the curve at it's dx==0 or dy==0 inflection points.
6674 // Thus:
6675 // A horizontal line only ever interects the curves once.
6676 // and
6677 // Finding the closest point on any curve converges more reliably.
6679 // NOTE: We could just split on dy==0 here.
6681 memcpy(&points.ptr[0], &ps.points[(ps.npoints-1)*2], float.sizeof*2);
6682 memcpy(&points.ptr[2], tfxy, float.sizeof*2*3);
6684 ninflections = 0;
6685 nvg__bezierInflections(points.ptr, 1, &ninflections, inflections.ptr);
6686 nvg__bezierInflections(points.ptr, 0, &ninflections, inflections.ptr);
6688 if (ninflections) {
6689 float previnfl = 0;
6690 float[8] pointsA = void, pointsB = void;
6692 nvg__smallsort(inflections.ptr, ninflections);
6694 for (int infl = 0; infl < ninflections; ++infl) {
6695 if (nvg__absf(inflections.ptr[infl]-previnfl) < NVGPickEPS) continue;
6697 immutable float t = (inflections.ptr[infl]-previnfl)*(1.0f/(1.0f-previnfl));
6699 previnfl = inflections.ptr[infl];
6701 nvg__splitBezier(points.ptr, t, pointsA.ptr, pointsB.ptr);
6703 firstPoint = nvg__pickSceneAddPoints(ps, &pointsA.ptr[2], 3);
6704 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, (infl == 0) ? NVGSegmentFlags.Corner : 0);
6706 memcpy(points.ptr, pointsB.ptr, float.sizeof*8);
6709 firstPoint = nvg__pickSceneAddPoints(ps, &pointsB.ptr[2], 3);
6710 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, 0);
6711 } else {
6712 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 3);
6713 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner);
6715 hasPoints = true;
6716 break;
6717 case Command.Close:
6718 closeIt();
6719 break;
6720 case Command.Winding:
6721 psp.winding = cast(short)cast(int)commands[i];
6722 //if (psp.winding == NVGSolidity.Hole) hasHoles = true;
6723 i += 1;
6724 break;
6725 default:
6726 break;
6730 // force-close filled paths
6731 if (psp !is null && !forStroke && hasPoints && !psp.closed) closeIt();
6733 pp.flags = (forStroke ? NVGPathFlags.Stroke : NVGPathFlags.Fill);
6734 pp.subPaths = psp;
6735 pp.strokeWidth = state.strokeWidth*0.5f;
6736 pp.miterLimit = state.miterLimit;
6737 pp.lineCap = cast(short)state.lineCap;
6738 pp.lineJoin = cast(short)state.lineJoin;
6739 pp.evenOddMode = nvg__getState(context).evenOddMode;
6741 nvg__initBounds(totalBounds);
6743 for (curpsp = psp; curpsp; curpsp = curpsp.next) {
6744 if (forStroke) {
6745 nvg__pickSubPathAddStrokeSupports(ps, curpsp, pp.strokeWidth, pp.lineCap, pp.lineJoin, pp.miterLimit);
6746 } else {
6747 nvg__pickSubPathAddFillSupports(ps, curpsp);
6750 if (curpsp.firstSegment == -1) continue;
6751 segments = &ps.segments[curpsp.firstSegment];
6752 nvg__initBounds(curpsp.bounds);
6753 for (int s = 0; s < curpsp.nsegments; ++s) {
6754 seg = &segments[s];
6755 //NVG_PICK_DEBUG_BOUNDS(seg.bounds);
6756 nvg__unionBounds(curpsp.bounds, seg.bounds);
6759 nvg__unionBounds(totalBounds, curpsp.bounds);
6762 // Store the scissor rect if present.
6763 if (state.scissor.extent.ptr[0] != -1.0f) {
6764 // Use points storage to store the scissor data
6765 pp.scissor = nvg__pickSceneAddPoints(ps, null, 4);
6766 float* scissor = &ps.points[pp.scissor*2];
6768 //memcpy(scissor, state.scissor.xform.ptr, 6*float.sizeof);
6769 scissor[0..6] = state.scissor.xform.mat[];
6770 memcpy(scissor+6, state.scissor.extent.ptr, 2*float.sizeof);
6772 pp.flags |= NVGPathFlags.Scissor;
6775 memcpy(pp.bounds.ptr, totalBounds.ptr, float.sizeof*4);
6777 return pp;
6781 // Struct management
6782 NVGpickPath* nvg__allocPickPath (NVGpickScene* ps) {
6783 NVGpickPath* pp = ps.freePaths;
6784 if (pp !is null) {
6785 ps.freePaths = pp.next;
6786 } else {
6787 pp = cast(NVGpickPath*)malloc(NVGpickPath.sizeof);
6789 memset(pp, 0, NVGpickPath.sizeof);
6790 return pp;
6793 // Put a pick path and any sub paths (back) to the free lists.
6794 void nvg__freePickPath (NVGpickScene* ps, NVGpickPath* pp) {
6795 // Add all sub paths to the sub path free list.
6796 // Finds the end of the path sub paths, links that to the current
6797 // sub path free list head and replaces the head ptr with the
6798 // head path sub path entry.
6799 NVGpickSubPath* psp = null;
6800 for (psp = pp.subPaths; psp !is null && psp.next !is null; psp = psp.next) {}
6802 if (psp) {
6803 psp.next = ps.freeSubPaths;
6804 ps.freeSubPaths = pp.subPaths;
6806 pp.subPaths = null;
6808 // Add the path to the path freelist
6809 pp.next = ps.freePaths;
6810 ps.freePaths = pp;
6811 if (pp.next is null) ps.lastPath = pp;
6814 NVGpickSubPath* nvg__allocPickSubPath (NVGpickScene* ps) {
6815 NVGpickSubPath* psp = ps.freeSubPaths;
6816 if (psp !is null) {
6817 ps.freeSubPaths = psp.next;
6818 } else {
6819 psp = cast(NVGpickSubPath*)malloc(NVGpickSubPath.sizeof);
6820 if (psp is null) return null;
6822 memset(psp, 0, NVGpickSubPath.sizeof);
6823 return psp;
6826 void nvg__returnPickSubPath (NVGpickScene* ps, NVGpickSubPath* psp) {
6827 psp.next = ps.freeSubPaths;
6828 ps.freeSubPaths = psp;
6831 NVGpickScene* nvg__allocPickScene () {
6832 NVGpickScene* ps = cast(NVGpickScene*)malloc(NVGpickScene.sizeof);
6833 if (ps is null) return null;
6834 memset(ps, 0, NVGpickScene.sizeof);
6835 ps.nlevels = 5;
6836 return ps;
6839 void nvg__deletePickScene (NVGpickScene* ps) {
6840 NVGpickPath* pp;
6841 NVGpickSubPath* psp;
6843 // Add all paths (and thus sub paths) to the free list(s).
6844 while (ps.paths !is null) {
6845 pp = ps.paths.next;
6846 nvg__freePickPath(ps, ps.paths);
6847 ps.paths = pp;
6850 // Delete all paths
6851 while (ps.freePaths !is null) {
6852 pp = ps.freePaths;
6853 ps.freePaths = pp.next;
6854 while (pp.subPaths !is null) {
6855 psp = pp.subPaths;
6856 pp.subPaths = psp.next;
6857 free(psp);
6859 free(pp);
6862 // Delete all sub paths
6863 while (ps.freeSubPaths !is null) {
6864 psp = ps.freeSubPaths.next;
6865 free(ps.freeSubPaths);
6866 ps.freeSubPaths = psp;
6869 ps.npoints = 0;
6870 ps.nsegments = 0;
6872 if (ps.levels !is null) {
6873 free(ps.levels[0]);
6874 free(ps.levels);
6877 if (ps.picked !is null) free(ps.picked);
6878 if (ps.points !is null) free(ps.points);
6879 if (ps.segments !is null) free(ps.segments);
6881 free(ps);
6884 NVGpickScene* nvg__pickSceneGet (NVGContext ctx) {
6885 if (ctx.pickScene is null) ctx.pickScene = nvg__allocPickScene();
6886 return ctx.pickScene;
6890 // Applies Casteljau's algorithm to a cubic bezier for a given parameter t
6891 // points is 4 points (8 floats)
6892 // lvl1 is 3 points (6 floats)
6893 // lvl2 is 2 points (4 floats)
6894 // lvl3 is 1 point (2 floats)
6895 void nvg__casteljau (const(float)* points, float t, float* lvl1, float* lvl2, float* lvl3) {
6896 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0;
6897 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1;
6899 // Level 1
6900 lvl1[x0] = (points[x1]-points[x0])*t+points[x0];
6901 lvl1[y0] = (points[y1]-points[y0])*t+points[y0];
6903 lvl1[x1] = (points[x2]-points[x1])*t+points[x1];
6904 lvl1[y1] = (points[y2]-points[y1])*t+points[y1];
6906 lvl1[x2] = (points[x3]-points[x2])*t+points[x2];
6907 lvl1[y2] = (points[y3]-points[y2])*t+points[y2];
6909 // Level 2
6910 lvl2[x0] = (lvl1[x1]-lvl1[x0])*t+lvl1[x0];
6911 lvl2[y0] = (lvl1[y1]-lvl1[y0])*t+lvl1[y0];
6913 lvl2[x1] = (lvl1[x2]-lvl1[x1])*t+lvl1[x1];
6914 lvl2[y1] = (lvl1[y2]-lvl1[y1])*t+lvl1[y1];
6916 // Level 3
6917 lvl3[x0] = (lvl2[x1]-lvl2[x0])*t+lvl2[x0];
6918 lvl3[y0] = (lvl2[y1]-lvl2[y0])*t+lvl2[y0];
6921 // Calculates a point on a bezier at point t.
6922 void nvg__bezierEval (const(float)* points, float t, ref float[2] tpoint) {
6923 immutable float omt = 1-t;
6924 immutable float omt3 = omt*omt*omt;
6925 immutable float omt2 = omt*omt;
6926 immutable float t3 = t*t*t;
6927 immutable float t2 = t*t;
6929 tpoint.ptr[0] =
6930 points[0]*omt3+
6931 points[2]*3.0f*omt2*t+
6932 points[4]*3.0f*omt*t2+
6933 points[6]*t3;
6935 tpoint.ptr[1] =
6936 points[1]*omt3+
6937 points[3]*3.0f*omt2*t+
6938 points[5]*3.0f*omt*t2+
6939 points[7]*t3;
6942 // Splits a cubic bezier curve into two parts at point t.
6943 void nvg__splitBezier (const(float)* points, float t, float* pointsA, float* pointsB) {
6944 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0;
6945 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1;
6947 float[6] lvl1 = void;
6948 float[4] lvl2 = void;
6949 float[2] lvl3 = void;
6951 nvg__casteljau(points, t, lvl1.ptr, lvl2.ptr, lvl3.ptr);
6953 // First half
6954 pointsA[x0] = points[x0];
6955 pointsA[y0] = points[y0];
6957 pointsA[x1] = lvl1.ptr[x0];
6958 pointsA[y1] = lvl1.ptr[y0];
6960 pointsA[x2] = lvl2.ptr[x0];
6961 pointsA[y2] = lvl2.ptr[y0];
6963 pointsA[x3] = lvl3.ptr[x0];
6964 pointsA[y3] = lvl3.ptr[y0];
6966 // Second half
6967 pointsB[x0] = lvl3.ptr[x0];
6968 pointsB[y0] = lvl3.ptr[y0];
6970 pointsB[x1] = lvl2.ptr[x1];
6971 pointsB[y1] = lvl2.ptr[y1];
6973 pointsB[x2] = lvl1.ptr[x2];
6974 pointsB[y2] = lvl1.ptr[y2];
6976 pointsB[x3] = points[x3];
6977 pointsB[y3] = points[y3];
6980 // Calculates the inflection points in coordinate coord (X = 0, Y = 1) of a cubic bezier.
6981 // Appends any found inflection points to the array inflections and increments *ninflections.
6982 // So finds the parameters where dx/dt or dy/dt is 0
6983 void nvg__bezierInflections (const(float)* points, int coord, int* ninflections, float* inflections) {
6984 immutable float v0 = points[0*2+coord], v1 = points[1*2+coord], v2 = points[2*2+coord], v3 = points[3*2+coord];
6985 float[2] t = void;
6986 int nvalid = *ninflections;
6988 immutable float a = 3.0f*( -v0+3.0f*v1-3.0f*v2+v3 );
6989 immutable float b = 6.0f*( v0-2.0f*v1+v2 );
6990 immutable float c = 3.0f*( v1-v0 );
6992 float d = b*b-4.0f*a*c;
6993 if (nvg__absf(d-0.0f) < NVGPickEPS) {
6994 // Zero or one root
6995 t.ptr[0] = -b/2.0f*a;
6996 if (t.ptr[0] > NVGPickEPS && t.ptr[0] < (1.0f-NVGPickEPS)) {
6997 inflections[nvalid] = t.ptr[0];
6998 ++nvalid;
7000 } else if (d > NVGPickEPS) {
7001 // zero, one or two roots
7002 d = nvg__sqrtf(d);
7004 t.ptr[0] = (-b+d)/(2.0f*a);
7005 t.ptr[1] = (-b-d)/(2.0f*a);
7007 for (int i = 0; i < 2; ++i) {
7008 if (t.ptr[i] > NVGPickEPS && t.ptr[i] < (1.0f-NVGPickEPS)) {
7009 inflections[nvalid] = t.ptr[i];
7010 ++nvalid;
7013 } else {
7014 // zero roots
7017 *ninflections = nvalid;
7020 // Sort a small number of floats in ascending order (0 < n < 6)
7021 void nvg__smallsort (float* values, int n) {
7022 bool bSwapped = true;
7023 for (int j = 0; j < n-1 && bSwapped; ++j) {
7024 bSwapped = false;
7025 for (int i = 0; i < n-1; ++i) {
7026 if (values[i] > values[i+1]) {
7027 auto tmp = values[i];
7028 values[i] = values[i+1];
7029 values[i+1] = tmp;
7035 // Calculates the bounding rect of a given cubic bezier curve.
7036 void nvg__bezierBounds (const(float)* points, ref float[4] bounds) {
7037 float[4] inflections = void;
7038 int ninflections = 0;
7039 float[2] tpoint = void;
7041 nvg__initBounds(bounds);
7043 // Include start and end points in bounds
7044 nvg__expandBounds(bounds, &points[0], 1);
7045 nvg__expandBounds(bounds, &points[6], 1);
7047 // Calculate dx==0 and dy==0 inflection points and add them to the bounds
7049 nvg__bezierInflections(points, 0, &ninflections, inflections.ptr);
7050 nvg__bezierInflections(points, 1, &ninflections, inflections.ptr);
7052 foreach (immutable int i; 0..ninflections) {
7053 nvg__bezierEval(points, inflections[i], tpoint);
7054 nvg__expandBounds(bounds, tpoint.ptr, 1);
7058 // Checks to see if a line originating from x,y along the +ve x axis
7059 // intersects the given line (points[0],points[1]) -> (points[2], points[3]).
7060 // Returns `true` on intersection.
7061 // Horizontal lines are never hit.
7062 bool nvg__intersectLine (const(float)* points, float x, float y) {
7063 immutable float x1 = points[0];
7064 immutable float y1 = points[1];
7065 immutable float x2 = points[2];
7066 immutable float y2 = points[3];
7067 immutable float d = y2-y1;
7068 if (d > NVGPickEPS || d < -NVGPickEPS) {
7069 immutable float s = (x2-x1)/d;
7070 immutable float lineX = x1+(y-y1)*s;
7071 return (lineX > x);
7072 } else {
7073 return false;
7077 // Checks to see if a line originating from x,y along the +ve x axis intersects the given bezier.
7078 // It is assumed that the line originates from within the bounding box of
7079 // the bezier and that the curve has no dy=0 inflection points.
7080 // Returns the number of intersections found (which is either 1 or 0).
7081 int nvg__intersectBezier (const(float)* points, float x, float y) {
7082 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0];
7083 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1];
7085 if (y0 == y1 && y1 == y2 && y2 == y3) return 0;
7087 // Initial t guess
7088 float t = void;
7089 if (y3 != y0) t = (y-y0)/(y3-y0);
7090 else if (x3 != x0) t = (x-x0)/(x3-x0);
7091 else t = 0.5f;
7093 // A few Newton iterations
7094 for (int iter = 0; iter < 6; ++iter) {
7095 immutable float omt = 1-t;
7096 immutable float omt2 = omt*omt;
7097 immutable float t2 = t*t;
7098 immutable float omt3 = omt2*omt;
7099 immutable float t3 = t2*t;
7101 immutable float ty = y0*omt3 +
7102 y1*3.0f*omt2*t +
7103 y2*3.0f*omt*t2 +
7104 y3*t3;
7106 // Newton iteration
7107 immutable float dty = 3.0f*omt2*(y1-y0) +
7108 6.0f*omt*t*(y2-y1) +
7109 3.0f*t2*(y3-y2);
7111 // dty will never == 0 since:
7112 // Either omt, omt2 are zero OR t2 is zero
7113 // y0 != y1 != y2 != y3 (checked above)
7114 t = t-(ty-y)/dty;
7118 immutable float omt = 1-t;
7119 immutable float omt2 = omt*omt;
7120 immutable float t2 = t*t;
7121 immutable float omt3 = omt2*omt;
7122 immutable float t3 = t2*t;
7124 immutable float tx =
7125 x0*omt3+
7126 x1*3.0f*omt2*t+
7127 x2*3.0f*omt*t2+
7128 x3*t3;
7130 return (tx > x ? 1 : 0);
7134 // Finds the closest point on a line to a given point
7135 void nvg__closestLine (const(float)* points, float x, float y, float* closest, float* ot) {
7136 immutable float x1 = points[0];
7137 immutable float y1 = points[1];
7138 immutable float x2 = points[2];
7139 immutable float y2 = points[3];
7140 immutable float pqx = x2-x1;
7141 immutable float pqz = y2-y1;
7142 immutable float dx = x-x1;
7143 immutable float dz = y-y1;
7144 immutable float d = pqx*pqx+pqz*pqz;
7145 float t = pqx*dx+pqz*dz;
7146 if (d > 0) t /= d;
7147 if (t < 0) t = 0; else if (t > 1) t = 1;
7148 closest[0] = x1+t*pqx;
7149 closest[1] = y1+t*pqz;
7150 *ot = t;
7153 // Finds the closest point on a curve for a given point (x,y).
7154 // Assumes that the curve has no dx==0 or dy==0 inflection points.
7155 void nvg__closestBezier (const(float)* points, float x, float y, float* closest, float *ot) {
7156 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0];
7157 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1];
7159 // This assumes that the curve has no dy=0 inflection points.
7161 // Initial t guess
7162 float t = 0.5f;
7164 // A few Newton iterations
7165 for (int iter = 0; iter < 6; ++iter) {
7166 immutable float omt = 1-t;
7167 immutable float omt2 = omt*omt;
7168 immutable float t2 = t*t;
7169 immutable float omt3 = omt2*omt;
7170 immutable float t3 = t2*t;
7172 immutable float ty =
7173 y0*omt3+
7174 y1*3.0f*omt2*t+
7175 y2*3.0f*omt*t2+
7176 y3*t3;
7178 immutable float tx =
7179 x0*omt3+
7180 x1*3.0f*omt2*t+
7181 x2*3.0f*omt*t2+
7182 x3*t3;
7184 // Newton iteration
7185 immutable float dty =
7186 3.0f*omt2*(y1-y0)+
7187 6.0f*omt*t*(y2-y1)+
7188 3.0f*t2*(y3-y2);
7190 immutable float ddty =
7191 6.0f*omt*(y2-2.0f*y1+y0)+
7192 6.0f*t*(y3-2.0f*y2+y1);
7194 immutable float dtx =
7195 3.0f*omt2*(x1-x0)+
7196 6.0f*omt*t*(x2-x1)+
7197 3.0f*t2*(x3-x2);
7199 immutable float ddtx =
7200 6.0f*omt*(x2-2.0f*x1+x0)+
7201 6.0f*t*(x3-2.0f*x2+x1);
7203 immutable float errorx = tx-x;
7204 immutable float errory = ty-y;
7206 immutable float n = errorx*dtx+errory*dty;
7207 if (n == 0) break;
7209 immutable float d = dtx*dtx+dty*dty+errorx*ddtx+errory*ddty;
7210 if (d != 0) t = t-n/d; else break;
7213 t = nvg__max(0, nvg__min(1.0, t));
7214 *ot = t;
7216 immutable float omt = 1-t;
7217 immutable float omt2 = omt*omt;
7218 immutable float t2 = t*t;
7219 immutable float omt3 = omt2*omt;
7220 immutable float t3 = t2*t;
7222 immutable float ty =
7223 y0*omt3+
7224 y1*3.0f*omt2*t+
7225 y2*3.0f*omt*t2+
7226 y3*t3;
7228 immutable float tx =
7229 x0*omt3+
7230 x1*3.0f*omt2*t+
7231 x2*3.0f*omt*t2+
7232 x3*t3;
7234 closest[0] = tx;
7235 closest[1] = ty;
7239 // Returns:
7240 // 1 If (x,y) is contained by the stroke of the path
7241 // 0 If (x,y) is not contained by the path.
7242 int nvg__pickSubPathStroke (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, float strokeWidth, int lineCap, int lineJoin) {
7243 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0;
7244 if (psp.firstSegment == -1) return 0;
7246 float[2] closest = void;
7247 float[2] d = void;
7248 float t = void;
7250 // trace a line from x,y out along the positive x axis and count the number of intersections
7251 int nsegments = psp.nsegments;
7252 const(NVGsegment)* seg = ps.segments+psp.firstSegment;
7253 const(NVGsegment)* prevseg = (psp.closed ? &ps.segments[psp.firstSegment+nsegments-1] : null);
7254 immutable float strokeWidthSqd = strokeWidth*strokeWidth;
7256 for (int s = 0; s < nsegments; ++s, prevseg = seg, ++seg) {
7257 if (nvg__pointInBounds(x, y, seg.bounds)) {
7258 // Line potentially hits stroke.
7259 switch (seg.type) {
7260 case Command.LineTo:
7261 nvg__closestLine(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t);
7262 break;
7263 case Command.BezierTo:
7264 nvg__closestBezier(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t);
7265 break;
7266 default:
7267 continue;
7270 d.ptr[0] = x-closest.ptr[0];
7271 d.ptr[1] = y-closest.ptr[1];
7273 if ((t >= NVGPickEPS && t <= 1.0f-NVGPickEPS) ||
7274 (seg.flags&(NVGSegmentFlags.Corner|NVGSegmentFlags.Cap|NVGSegmentFlags.Endcap)) == 0 ||
7275 (lineJoin == NVGLineCap.Round))
7277 // Closest point is in the middle of the line/curve, at a rounded join/cap
7278 // or at a smooth join
7279 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7280 if (distSqd < strokeWidthSqd) return 1;
7281 } else if ((t > 1.0f-NVGPickEPS && (seg.flags&NVGSegmentFlags.Endcap)) ||
7282 (t < NVGPickEPS && (seg.flags&NVGSegmentFlags.Cap))) {
7283 switch (lineCap) {
7284 case NVGLineCap.Butt:
7285 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7286 immutable float dirD = (t < NVGPickEPS ?
7287 -(d.ptr[0]*seg.startDir.ptr[0]+d.ptr[1]*seg.startDir.ptr[1]) :
7288 d.ptr[0]*seg.endDir.ptr[0]+d.ptr[1]*seg.endDir.ptr[1]);
7289 if (dirD < -NVGPickEPS && distSqd < strokeWidthSqd) return 1;
7290 break;
7291 case NVGLineCap.Square:
7292 if (nvg__absf(d.ptr[0]) < strokeWidth && nvg__absf(d.ptr[1]) < strokeWidth) return 1;
7293 break;
7294 case NVGLineCap.Round:
7295 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7296 if (distSqd < strokeWidthSqd) return 1;
7297 break;
7298 default:
7299 break;
7301 } else if (seg.flags&NVGSegmentFlags.Corner) {
7302 // Closest point is at a corner
7303 const(NVGsegment)* seg0, seg1;
7305 if (t < NVGPickEPS) {
7306 seg0 = prevseg;
7307 seg1 = seg;
7308 } else {
7309 seg0 = seg;
7310 seg1 = (s == nsegments-1 ? &ps.segments[psp.firstSegment] : seg+1);
7313 if (!(seg1.flags&NVGSegmentFlags.Bevel)) {
7314 immutable float prevNDist = -seg0.endDir.ptr[1]*d.ptr[0]+seg0.endDir.ptr[0]*d.ptr[1];
7315 immutable float curNDist = seg1.startDir.ptr[1]*d.ptr[0]-seg1.startDir.ptr[0]*d.ptr[1];
7316 if (nvg__absf(prevNDist) < strokeWidth && nvg__absf(curNDist) < strokeWidth) return 1;
7317 } else {
7318 d.ptr[0] -= -seg1.startDir.ptr[1]*strokeWidth;
7319 d.ptr[1] -= +seg1.startDir.ptr[0]*strokeWidth;
7320 if (seg1.miterDir.ptr[0]*d.ptr[0]+seg1.miterDir.ptr[1]*d.ptr[1] < 0) return 1;
7326 return 0;
7329 // Returns:
7330 // 1 If (x,y) is contained by the path and the path is solid.
7331 // -1 If (x,y) is contained by the path and the path is a hole.
7332 // 0 If (x,y) is not contained by the path.
7333 int nvg__pickSubPath (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, bool evenOddMode) {
7334 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0;
7335 if (psp.firstSegment == -1) return 0;
7337 const(NVGsegment)* seg = &ps.segments[psp.firstSegment];
7338 int nsegments = psp.nsegments;
7339 int nintersections = 0;
7341 // trace a line from x,y out along the positive x axis and count the number of intersections
7342 for (int s = 0; s < nsegments; ++s, ++seg) {
7343 if ((seg.bounds.ptr[1]-NVGPickEPS) < y &&
7344 (seg.bounds.ptr[3]-NVGPickEPS) > y &&
7345 seg.bounds.ptr[2] > x)
7347 // Line hits the box.
7348 switch (seg.type) {
7349 case Command.LineTo:
7350 if (seg.bounds.ptr[0] > x) {
7351 // line originates outside the box
7352 ++nintersections;
7353 } else {
7354 // line originates inside the box
7355 nintersections += nvg__intersectLine(&ps.points[seg.firstPoint*2], x, y);
7357 break;
7358 case Command.BezierTo:
7359 if (seg.bounds.ptr[0] > x) {
7360 // line originates outside the box
7361 ++nintersections;
7362 } else {
7363 // line originates inside the box
7364 nintersections += nvg__intersectBezier(&ps.points[seg.firstPoint*2], x, y);
7366 break;
7367 default:
7368 break;
7373 if (evenOddMode) {
7374 return nintersections;
7375 } else {
7376 return (nintersections&1 ? (psp.winding == NVGSolidity.Solid ? 1 : -1) : 0);
7380 bool nvg__pickPath (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) {
7381 int pickCount = 0;
7382 const(NVGpickSubPath)* psp = pp.subPaths;
7383 while (psp !is null) {
7384 pickCount += nvg__pickSubPath(ps, psp, x, y, pp.evenOddMode);
7385 psp = psp.next;
7387 return ((pp.evenOddMode ? pickCount&1 : pickCount) != 0);
7390 bool nvg__pickPathStroke (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) {
7391 const(NVGpickSubPath)* psp = pp.subPaths;
7392 while (psp !is null) {
7393 if (nvg__pickSubPathStroke(ps, psp, x, y, pp.strokeWidth, pp.lineCap, pp.lineJoin)) return true;
7394 psp = psp.next;
7396 return false;
7399 bool nvg__pickPathTestBounds (NVGContext ctx, const NVGpickScene* ps, const NVGpickPath* pp, float x, float y) {
7400 if (nvg__pointInBounds(x, y, pp.bounds)) {
7401 //{ import core.stdc.stdio; printf(" (0): in bounds!\n"); }
7402 if (pp.flags&NVGPathFlags.Scissor) {
7403 const(float)* scissor = &ps.points[pp.scissor*2];
7404 // untransform scissor translation
7405 float stx = void, sty = void;
7406 ctx.gpuUntransformPoint(&stx, &sty, scissor[4], scissor[5]);
7407 immutable float rx = x-stx;
7408 immutable float ry = y-sty;
7409 //{ 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]); }
7410 if (nvg__absf((scissor[0]*rx)+(scissor[1]*ry)) > scissor[6] ||
7411 nvg__absf((scissor[2]*rx)+(scissor[3]*ry)) > scissor[7])
7413 //{ import core.stdc.stdio; printf(" (1): scissor reject!\n"); }
7414 return false;
7417 return true;
7419 return false;
7422 int nvg__countBitsUsed (uint v) pure {
7423 pragma(inline, true);
7424 import core.bitop : bsr;
7425 return (v != 0 ? bsr(v)+1 : 0);
7428 void nvg__pickSceneInsert (NVGpickScene* ps, NVGpickPath* pp) {
7429 if (ps is null || pp is null) return;
7431 int[4] cellbounds;
7432 int base = ps.nlevels-1;
7433 int level;
7434 int levelwidth;
7435 int levelshift;
7436 int levelx;
7437 int levely;
7438 NVGpickPath** cell = null;
7440 // Bit tricks for inserting into an implicit quadtree.
7442 // Calc bounds of path in cells at the lowest level
7443 cellbounds.ptr[0] = cast(int)(pp.bounds.ptr[0]/ps.xdim);
7444 cellbounds.ptr[1] = cast(int)(pp.bounds.ptr[1]/ps.ydim);
7445 cellbounds.ptr[2] = cast(int)(pp.bounds.ptr[2]/ps.xdim);
7446 cellbounds.ptr[3] = cast(int)(pp.bounds.ptr[3]/ps.ydim);
7448 // Find which bits differ between the min/max x/y coords
7449 cellbounds.ptr[0] ^= cellbounds.ptr[2];
7450 cellbounds.ptr[1] ^= cellbounds.ptr[3];
7452 // Use the number of bits used (countBitsUsed(x) == sizeof(int) * 8 - clz(x);
7453 // to calculate the level to insert at (the level at which the bounds fit in a single cell)
7454 level = nvg__min(base-nvg__countBitsUsed(cellbounds.ptr[0]), base-nvg__countBitsUsed(cellbounds.ptr[1]));
7455 if (level < 0) level = 0;
7456 //{ 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]); }
7457 //level = 0;
7459 // Find the correct cell in the chosen level, clamping to the edges.
7460 levelwidth = 1<<level;
7461 levelshift = (ps.nlevels-level)-1;
7462 levelx = nvg__clamp(cellbounds.ptr[2]>>levelshift, 0, levelwidth-1);
7463 levely = nvg__clamp(cellbounds.ptr[3]>>levelshift, 0, levelwidth-1);
7465 // Insert the path into the linked list at that cell.
7466 cell = &ps.levels[level][levely*levelwidth+levelx];
7468 pp.cellnext = *cell;
7469 *cell = pp;
7471 if (ps.paths is null) ps.lastPath = pp;
7472 pp.next = ps.paths;
7473 ps.paths = pp;
7475 // Store the order (depth) of the path for picking ops.
7476 pp.order = cast(short)ps.npaths;
7477 ++ps.npaths;
7480 void nvg__pickBeginFrame (NVGContext ctx, int width, int height) {
7481 NVGpickScene* ps = nvg__pickSceneGet(ctx);
7483 //NVG_PICK_DEBUG_NEWFRAME();
7485 // Return all paths & sub paths from last frame to the free list
7486 while (ps.paths !is null) {
7487 NVGpickPath* pp = ps.paths.next;
7488 nvg__freePickPath(ps, ps.paths);
7489 ps.paths = pp;
7492 ps.paths = null;
7493 ps.npaths = 0;
7495 // Store the screen metrics for the quadtree
7496 ps.width = width;
7497 ps.height = height;
7499 immutable float lowestSubDiv = cast(float)(1<<(ps.nlevels-1));
7500 ps.xdim = cast(float)width/lowestSubDiv;
7501 ps.ydim = cast(float)height/lowestSubDiv;
7503 // Allocate the quadtree if required.
7504 if (ps.levels is null) {
7505 int ncells = 1;
7507 ps.levels = cast(NVGpickPath***)malloc((NVGpickPath**).sizeof*ps.nlevels);
7508 for (int l = 0; l < ps.nlevels; ++l) {
7509 int leveldim = 1<<l;
7510 ncells += leveldim*leveldim;
7513 ps.levels[0] = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ncells);
7515 int cell = 1;
7516 for (int l = 1; l < ps.nlevels; ++l) {
7517 ps.levels[l] = &ps.levels[0][cell];
7518 int leveldim = 1<<l;
7519 cell += leveldim*leveldim;
7522 ps.ncells = ncells;
7524 memset(ps.levels[0], 0, ps.ncells*(NVGpickPath*).sizeof);
7526 // Allocate temporary storage for nvgHitTestAll results if required.
7527 if (ps.picked is null) {
7528 ps.cpicked = 16;
7529 ps.picked = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ps.cpicked);
7532 ps.npoints = 0;
7533 ps.nsegments = 0;
7535 } // nothrow @trusted @nogc
7538 /// Return outline of the current path. Returned outline is not flattened.
7539 /// Group: paths
7540 public NVGPathOutline getCurrPathOutline (NVGContext ctx) nothrow @trusted @nogc {
7541 if (ctx is null || !ctx.contextAlive || ctx.ncommands == 0) return NVGPathOutline.init;
7543 auto res = NVGPathOutline.createNew();
7545 const(float)[] acommands = ctx.commands[0..ctx.ncommands];
7546 int ncommands = cast(int)acommands.length;
7547 const(float)* commands = acommands.ptr;
7549 float cx = 0, cy = 0;
7550 float[2] start = void;
7551 float[4] totalBounds = [float.max, float.max, -float.max, -float.max];
7552 float[8] bcp = void; // bezier curve points; used to calculate bounds
7554 void addToBounds (in float x, in float y) nothrow @trusted @nogc {
7555 totalBounds.ptr[0] = nvg__min(totalBounds.ptr[0], x);
7556 totalBounds.ptr[1] = nvg__min(totalBounds.ptr[1], y);
7557 totalBounds.ptr[2] = nvg__max(totalBounds.ptr[2], x);
7558 totalBounds.ptr[3] = nvg__max(totalBounds.ptr[3], y);
7561 bool hasPoints = false;
7563 void closeIt () nothrow @trusted @nogc {
7564 if (!hasPoints) return;
7565 if (cx != start.ptr[0] || cy != start.ptr[1]) {
7566 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo);
7567 res.ds.putArgs(start[]);
7568 cx = start.ptr[0];
7569 cy = start.ptr[1];
7570 addToBounds(cx, cy);
7574 int i = 0;
7575 while (i < ncommands) {
7576 int cmd = cast(int)commands[i++];
7577 switch (cmd) {
7578 case Command.MoveTo: // one coordinate pair
7579 const(float)* tfxy = commands+i;
7580 i += 2;
7581 // add command
7582 res.ds.putCommand(NVGPathOutline.Command.Kind.MoveTo);
7583 res.ds.putArgs(tfxy[0..2]);
7584 // new starting point
7585 start.ptr[0..2] = tfxy[0..2];
7586 cx = tfxy[0];
7587 cy = tfxy[0];
7588 addToBounds(cx, cy);
7589 hasPoints = true;
7590 break;
7591 case Command.LineTo: // one coordinate pair
7592 const(float)* tfxy = commands+i;
7593 i += 2;
7594 // add command
7595 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo);
7596 res.ds.putArgs(tfxy[0..2]);
7597 cx = tfxy[0];
7598 cy = tfxy[0];
7599 addToBounds(cx, cy);
7600 hasPoints = true;
7601 break;
7602 case Command.BezierTo: // three coordinate pairs
7603 const(float)* tfxy = commands+i;
7604 i += 3*2;
7605 // add command
7606 res.ds.putCommand(NVGPathOutline.Command.Kind.BezierTo);
7607 res.ds.putArgs(tfxy[0..6]);
7608 // bounds
7609 bcp.ptr[0] = cx;
7610 bcp.ptr[1] = cy;
7611 bcp.ptr[2..8] = tfxy[0..6];
7612 nvg__bezierBounds(bcp.ptr, totalBounds);
7613 cx = tfxy[4];
7614 cy = tfxy[5];
7615 hasPoints = true;
7616 break;
7617 case Command.Close:
7618 closeIt();
7619 hasPoints = false;
7620 break;
7621 case Command.Winding:
7622 //psp.winding = cast(short)cast(int)commands[i];
7623 i += 1;
7624 break;
7625 default:
7626 break;
7630 res.ds.bounds[] = totalBounds[];
7631 return res;
7635 // ////////////////////////////////////////////////////////////////////////// //
7636 // Text
7638 /** Creates font by loading it from the disk from specified file name.
7639 * Returns handle to the font or FONS_INVALID (aka -1) on error.
7640 * Use "fontname:noaa" as [name] to turn off antialiasing (if font driver supports that).
7642 * On POSIX systems it is possible to use fontconfig font names too.
7643 * `:noaa` in font path is still allowed, but it must be the last option.
7645 * Group: text_api
7647 public int createFont (NVGContext ctx, const(char)[] name, const(char)[] path) nothrow @trusted {
7648 return ctx.fs.addFont(name, path, ctx.params.fontAA);
7651 /** Creates font by loading it from the specified memory chunk.
7652 * Returns handle to the font or FONS_INVALID (aka -1) on error.
7653 * Won't free data on error.
7655 * Group: text_api
7657 public int createFontMem (NVGContext ctx, const(char)[] name, ubyte* data, int ndata, bool freeData) nothrow @trusted @nogc {
7658 return ctx.fs.addFontMem(name, data, ndata, freeData, ctx.params.fontAA);
7661 /// Add fonts from another context.
7662 /// This is more effective than reloading fonts, 'cause font data will be shared.
7663 /// Group: text_api
7664 public void addFontsFrom (NVGContext ctx, NVGContext source) nothrow @trusted @nogc {
7665 if (ctx is null || source is null) return;
7666 ctx.fs.addFontsFrom(source.fs);
7669 /// Finds a loaded font of specified name, and returns handle to it, or FONS_INVALID (aka -1) if the font is not found.
7670 /// Group: text_api
7671 public int findFont (NVGContext ctx, const(char)[] name) nothrow @trusted @nogc {
7672 pragma(inline, true);
7673 return (name.length == 0 ? FONS_INVALID : ctx.fs.getFontByName(name));
7676 /// Sets the font size of current text style.
7677 /// Group: text_api
7678 public void fontSize (NVGContext ctx, float size) nothrow @trusted @nogc {
7679 pragma(inline, true);
7680 nvg__getState(ctx).fontSize = size;
7683 /// Gets the font size of current text style.
7684 /// Group: text_api
7685 public float fontSize (NVGContext ctx) nothrow @trusted @nogc {
7686 pragma(inline, true);
7687 return nvg__getState(ctx).fontSize;
7690 /// Sets the blur of current text style.
7691 /// Group: text_api
7692 public void fontBlur (NVGContext ctx, float blur) nothrow @trusted @nogc {
7693 pragma(inline, true);
7694 nvg__getState(ctx).fontBlur = blur;
7697 /// Gets the blur of current text style.
7698 /// Group: text_api
7699 public float fontBlur (NVGContext ctx) nothrow @trusted @nogc {
7700 pragma(inline, true);
7701 return nvg__getState(ctx).fontBlur;
7704 /// Sets the letter spacing of current text style.
7705 /// Group: text_api
7706 public void textLetterSpacing (NVGContext ctx, float spacing) nothrow @trusted @nogc {
7707 pragma(inline, true);
7708 nvg__getState(ctx).letterSpacing = spacing;
7711 /// Gets the letter spacing of current text style.
7712 /// Group: text_api
7713 public float textLetterSpacing (NVGContext ctx) nothrow @trusted @nogc {
7714 pragma(inline, true);
7715 return nvg__getState(ctx).letterSpacing;
7718 /// Sets the proportional line height of current text style. The line height is specified as multiple of font size.
7719 /// Group: text_api
7720 public void textLineHeight (NVGContext ctx, float lineHeight) nothrow @trusted @nogc {
7721 pragma(inline, true);
7722 nvg__getState(ctx).lineHeight = lineHeight;
7725 /// Gets the proportional line height of current text style. The line height is specified as multiple of font size.
7726 /// Group: text_api
7727 public float textLineHeight (NVGContext ctx) nothrow @trusted @nogc {
7728 pragma(inline, true);
7729 return nvg__getState(ctx).lineHeight;
7732 /// Sets the text align of current text style, see [NVGTextAlign] for options.
7733 /// Group: text_api
7734 public void textAlign (NVGContext ctx, NVGTextAlign talign) nothrow @trusted @nogc {
7735 pragma(inline, true);
7736 nvg__getState(ctx).textAlign = talign;
7739 /// Ditto.
7740 public void textAlign (NVGContext ctx, NVGTextAlign.H h) nothrow @trusted @nogc {
7741 pragma(inline, true);
7742 nvg__getState(ctx).textAlign.horizontal = h;
7745 /// Ditto.
7746 public void textAlign (NVGContext ctx, NVGTextAlign.V v) nothrow @trusted @nogc {
7747 pragma(inline, true);
7748 nvg__getState(ctx).textAlign.vertical = v;
7751 /// Ditto.
7752 public void textAlign (NVGContext ctx, NVGTextAlign.H h, NVGTextAlign.V v) nothrow @trusted @nogc {
7753 pragma(inline, true);
7754 nvg__getState(ctx).textAlign.reset(h, v);
7757 /// Ditto.
7758 public void textAlign (NVGContext ctx, NVGTextAlign.V v, NVGTextAlign.H h) nothrow @trusted @nogc {
7759 pragma(inline, true);
7760 nvg__getState(ctx).textAlign.reset(h, v);
7763 /// Gets the text align of current text style, see [NVGTextAlign] for options.
7764 /// Group: text_api
7765 public NVGTextAlign textAlign (NVGContext ctx) nothrow @trusted @nogc {
7766 pragma(inline, true);
7767 return nvg__getState(ctx).textAlign;
7770 /// Sets the font face based on specified id of current text style.
7771 /// Group: text_api
7772 public void fontFaceId (NVGContext ctx, int font) nothrow @trusted @nogc {
7773 pragma(inline, true);
7774 nvg__getState(ctx).fontId = font;
7777 /// Gets the font face based on specified id of current text style.
7778 /// Group: text_api
7779 public int fontFaceId (NVGContext ctx) nothrow @trusted @nogc {
7780 pragma(inline, true);
7781 return nvg__getState(ctx).fontId;
7784 /** Sets the font face based on specified name of current text style.
7786 * The underlying implementation is using O(1) data structure to lookup
7787 * font names, so you probably should use this function instead of [fontFaceId]
7788 * to make your code more robust and less error-prone.
7790 * Group: text_api
7792 public void fontFace (NVGContext ctx, const(char)[] font) nothrow @trusted @nogc {
7793 pragma(inline, true);
7794 nvg__getState(ctx).fontId = ctx.fs.getFontByName(font);
7797 static if (is(typeof(&fons__nvg__toPath))) {
7798 public enum NanoVegaHasCharToPath = true; ///
7799 } else {
7800 public enum NanoVegaHasCharToPath = false; ///
7803 /// Adds glyph outlines to the current path. Vertical 0 is baseline.
7804 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead.
7805 /// Returns `false` if there is no such glyph, or current font is not scalable.
7806 /// Group: text_api
7807 public bool charToPath (NVGContext ctx, dchar dch, float[] bounds=null) nothrow @trusted @nogc {
7808 NVGstate* state = nvg__getState(ctx);
7809 ctx.fs.fontId = state.fontId;
7810 return ctx.fs.toPath(ctx, dch, bounds);
7813 static if (is(typeof(&fons__nvg__bounds))) {
7814 public enum NanoVegaHasCharPathBounds = true; ///
7815 } else {
7816 public enum NanoVegaHasCharPathBounds = false; ///
7819 /// Returns bounds of the glyph outlines. Vertical 0 is baseline.
7820 /// The glyph is not scaled in any way.
7821 /// Returns `false` if there is no such glyph, or current font is not scalable.
7822 /// Group: text_api
7823 public bool charPathBounds (NVGContext ctx, dchar dch, float[] bounds) nothrow @trusted @nogc {
7824 NVGstate* state = nvg__getState(ctx);
7825 ctx.fs.fontId = state.fontId;
7826 return ctx.fs.getPathBounds(dch, bounds);
7829 /** [charOutline] will return [NVGPathOutline].
7831 some usage samples:
7834 float[4] bounds = void;
7836 nvg.scale(0.5, 0.5);
7837 nvg.translate(500, 800);
7838 nvg.evenOddFill;
7840 nvg.newPath();
7841 nvg.charToPath('&', bounds[]);
7842 conwriteln(bounds[]);
7843 nvg.fillPaint(nvg.linearGradient(0, 0, 600, 600, NVGColor("#f70"), NVGColor("#ff0")));
7844 nvg.strokeColor(NVGColor("#0f0"));
7845 nvg.strokeWidth = 3;
7846 nvg.fill();
7847 nvg.stroke();
7848 // glyph bounds
7849 nvg.newPath();
7850 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
7851 nvg.strokeColor(NVGColor("#00f"));
7852 nvg.stroke();
7854 nvg.newPath();
7855 nvg.charToPath('g', bounds[]);
7856 conwriteln(bounds[]);
7857 nvg.fill();
7858 nvg.strokeColor(NVGColor("#0f0"));
7859 nvg.stroke();
7860 // glyph bounds
7861 nvg.newPath();
7862 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
7863 nvg.strokeColor(NVGColor("#00f"));
7864 nvg.stroke();
7866 nvg.newPath();
7867 nvg.moveTo(0, 0);
7868 nvg.lineTo(600, 0);
7869 nvg.strokeColor(NVGColor("#0ff"));
7870 nvg.stroke();
7872 if (auto ol = nvg.charOutline('Q')) {
7873 scope(exit) ol.kill();
7874 nvg.newPath();
7875 conwriteln("==== length: ", ol.length, " ====");
7876 foreach (const ref cmd; ol.commands) {
7877 //conwriteln(" ", cmd.code, ": ", cmd.args[]);
7878 assert(cmd.valid);
7879 final switch (cmd.code) {
7880 case cmd.Kind.MoveTo: nvg.moveTo(cmd.args[0], cmd.args[1]); break;
7881 case cmd.Kind.LineTo: nvg.lineTo(cmd.args[0], cmd.args[1]); break;
7882 case cmd.Kind.QuadTo: nvg.quadTo(cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]); break;
7883 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;
7886 nvg.strokeColor(NVGColor("#f00"));
7887 nvg.stroke();
7891 Group: text_api
7893 public struct NVGPathOutline {
7894 private nothrow @trusted @nogc:
7895 struct DataStore {
7896 uint rc; // refcount
7897 ubyte* data;
7898 uint used;
7899 uint size;
7900 uint ccount; // number of commands
7901 float[4] bounds = 0; /// outline bounds
7902 nothrow @trusted @nogc:
7903 void putBytes (const(void)[] b) {
7904 if (b.length == 0) return;
7905 if (b.length >= int.max/8) assert(0, "NanoVega: out of memory");
7906 if (int.max/8-used < b.length) assert(0, "NanoVega: out of memory");
7907 if (used+cast(uint)b.length > size) {
7908 import core.stdc.stdlib : realloc;
7909 uint newsz = size;
7910 while (newsz < used+cast(uint)b.length) newsz = (newsz == 0 ? 1024 : newsz < 32768 ? newsz*2 : newsz+8192);
7911 assert(used+cast(uint)b.length <= newsz);
7912 data = cast(ubyte*)realloc(data, newsz);
7913 if (data is null) assert(0, "NanoVega: out of memory");
7914 size = newsz;
7916 import core.stdc.string : memcpy;
7917 memcpy(data+used, b.ptr, b.length);
7918 used += cast(uint)b.length;
7920 void putCommand (ubyte cmd) { pragma(inline, true); ++ccount; putBytes((&cmd)[0..1]); }
7921 void putArgs (const(float)[] f...) { pragma(inline, true); putBytes(f[]); }
7924 static void incRef (DataStore* ds) {
7925 pragma(inline, true);
7926 if (ds !is null) {
7927 ++ds.rc;
7928 //{ import core.stdc.stdio; printf("ods(%p): incref: newrc=%u\n", ds, ds.rc); }
7932 static void decRef (DataStore* ds) {
7933 version(aliced) pragma(inline, true);
7934 if (ds !is null) {
7935 //{ import core.stdc.stdio; printf("ods(%p): decref: newrc=%u\n", ds, ds.rc-1); }
7936 if (--ds.rc == 0) {
7937 import core.stdc.stdlib : free;
7938 import core.stdc.string : memset;
7939 if (ds.data !is null) free(ds.data);
7940 memset(ds, 0, DataStore.sizeof); // just in case
7941 free(ds);
7942 //{ import core.stdc.stdio; printf(" ods(%p): killed.\n"); }
7947 private:
7948 static NVGPathOutline createNew () {
7949 import core.stdc.stdlib : malloc;
7950 import core.stdc.string : memset;
7951 auto ds = cast(DataStore*)malloc(DataStore.sizeof);
7952 if (ds is null) assert(0, "NanoVega: out of memory");
7953 memset(ds, 0, DataStore.sizeof);
7954 ds.rc = 1;
7955 NVGPathOutline res;
7956 res.dsaddr = cast(usize)ds;
7957 return res;
7960 private:
7961 usize dsaddr; // fool GC
7963 @property inout(DataStore)* ds () inout pure { pragma(inline, true); return cast(DataStore*)dsaddr; }
7965 public:
7966 /// commands
7967 static struct Command {
7969 enum Kind : ubyte {
7970 MoveTo, ///
7971 LineTo, ///
7972 QuadTo, ///
7973 BezierTo, ///
7974 End, /// no more commands (this command is not `valid`!)
7977 Kind code; ///
7978 const(float)[] args; ///
7979 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (code >= Kind.min && code < Kind.End && args.length >= 2); } ///
7981 static uint arglen (Kind code) pure nothrow @safe @nogc {
7982 pragma(inline, true);
7983 return
7984 code == Kind.MoveTo || code == Kind.LineTo ? 2 :
7985 code == Kind.QuadTo ? 4 :
7986 code == Kind.BezierTo ? 6 :
7990 /// perform NanoVega command with stored data.
7991 void perform (NVGContext ctx) const nothrow @trusted @nogc {
7992 if (ctx is null) return;
7993 final switch (code) {
7994 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(args.ptr[0..2]); break;
7995 case Kind.LineTo: if (args.length > 1) ctx.lineTo(args.ptr[0..2]); break;
7996 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(args.ptr[0..4]); break;
7997 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(args.ptr[0..6]); break;
7998 case Kind.End: break;
8002 /// perform NanoVega command with stored data, transforming points with [xform] transformation matrix.
8003 void perform() (NVGContext ctx, in auto ref NVGMatrix xform) const nothrow @trusted @nogc {
8004 if (ctx is null || !valid) return;
8005 float[6] pts = void;
8006 pts[0..args.length] = args[];
8007 foreach (immutable pidx; 0..args.length/2) xform.point(pts.ptr[pidx*2+0], pts.ptr[pidx*2+1]);
8008 final switch (code) {
8009 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(pts.ptr[0..2]); break;
8010 case Kind.LineTo: if (args.length > 1) ctx.lineTo(pts.ptr[0..2]); break;
8011 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(pts.ptr[0..4]); break;
8012 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(pts.ptr[0..6]); break;
8013 case Kind.End: break;
8018 public:
8019 /// Create new path with quadratic bezier (first command is MoveTo, second command is QuadTo).
8020 static NVGPathOutline createNewQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) {
8021 auto res = createNew();
8022 res.ds.putCommand(Command.Kind.MoveTo);
8023 res.ds.putArgs(x0, y0);
8024 res.ds.putCommand(Command.Kind.QuadTo);
8025 res.ds.putArgs(cx, cy, x, y);
8026 return res;
8029 /// Create new path with cubic bezier (first command is MoveTo, second command is BezierTo).
8030 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) {
8031 auto res = createNew();
8032 res.ds.putCommand(Command.Kind.MoveTo);
8033 res.ds.putArgs(x1, y1);
8034 res.ds.putCommand(Command.Kind.BezierTo);
8035 res.ds.putArgs(x2, y2, x3, y3, x4, y4);
8036 return res;
8039 public:
8040 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); }
8041 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); }
8043 void opAssign() (in auto ref NVGPathOutline a) {
8044 incRef(cast(DataStore*)a.dsaddr);
8045 decRef(cast(DataStore*)dsaddr);
8046 dsaddr = a.dsaddr;
8049 /// Clear storage.
8050 void clear () {
8051 pragma(inline, true);
8052 decRef(ds);
8053 dsaddr = 0;
8056 /// Is this outline empty?
8057 @property empty () const pure { pragma(inline, true); return (dsaddr == 0 || ds.ccount == 0); }
8059 /// Returns number of commands in outline.
8060 @property int length () const pure { pragma(inline, true); return (dsaddr ? ds.ccount : 0); }
8062 /// Returns "flattened" path. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8063 NVGPathOutline flatten () const { pragma(inline, true); return flattenInternal(null); }
8065 /// Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8066 NVGPathOutline flatten() (in auto ref NVGMatrix mt) const { pragma(inline, true); return flattenInternal(&mt); }
8068 // Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8069 private NVGPathOutline flattenInternal (scope NVGMatrix* tfm) const {
8070 import core.stdc.string : memset;
8072 NVGPathOutline res;
8073 if (dsaddr == 0 || ds.ccount == 0) { res = this; return res; } // nothing to do
8075 // check if we need to flatten the path
8076 if (tfm is null) {
8077 bool dowork = false;
8078 foreach (const ref cs; commands) {
8079 if (cs.code != Command.Kind.MoveTo && cs.code != Command.Kind.LineTo) {
8080 dowork = true;
8081 break;
8084 if (!dowork) { res = this; return res; } // nothing to do
8087 NVGcontextinternal ctx;
8088 memset(&ctx, 0, ctx.sizeof);
8089 ctx.cache = nvg__allocPathCache();
8090 scope(exit) {
8091 import core.stdc.stdlib : free;
8092 nvg__deletePathCache(ctx.cache);
8095 ctx.tessTol = 0.25f;
8096 ctx.angleTol = 0; // 0 -- angle tolerance for McSeem Bezier rasterizer
8097 ctx.cuspLimit = 0; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps)
8098 ctx.distTol = 0.01f;
8099 ctx.tesselatortype = NVGTesselation.DeCasteljau;
8101 nvg__addPath(&ctx); // we need this for `nvg__addPoint()`
8103 // has some curves or transformations, convert path
8104 res = createNew();
8105 float[8] args = void;
8107 res.ds.bounds = [float.max, float.max, -float.max, -float.max];
8109 float lastX = float.max, lastY = float.max;
8110 bool lastWasMove = false;
8112 void addPoint (float x, float y, Command.Kind cmd=Command.Kind.LineTo) nothrow @trusted @nogc {
8113 if (tfm !is null) tfm.point(x, y);
8114 bool isMove = (cmd == Command.Kind.MoveTo);
8115 if (isMove) {
8116 // moveto
8117 if (lastWasMove && nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return;
8118 } else {
8119 // lineto
8120 if (nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return;
8122 lastWasMove = isMove;
8123 lastX = x;
8124 lastY = y;
8125 res.ds.putCommand(cmd);
8126 res.ds.putArgs(x, y);
8127 res.ds.bounds.ptr[0] = nvg__min(res.ds.bounds.ptr[0], x);
8128 res.ds.bounds.ptr[1] = nvg__min(res.ds.bounds.ptr[1], y);
8129 res.ds.bounds.ptr[2] = nvg__max(res.ds.bounds.ptr[2], x);
8130 res.ds.bounds.ptr[3] = nvg__max(res.ds.bounds.ptr[3], y);
8133 // sorry for this pasta
8134 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 {
8135 ctx.cache.npoints = 0;
8136 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) {
8137 nvg__tesselateBezier(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner);
8138 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) {
8139 nvg__tesselateBezierMcSeem(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner);
8140 } else {
8141 nvg__tesselateBezierAFD(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, PointFlag.Corner);
8143 // add generated points
8144 foreach (const ref pt; ctx.cache.points[0..ctx.cache.npoints]) addPoint(pt.x, pt.y);
8147 void flattenQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) {
8148 flattenBezier(
8149 x0, y0,
8150 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
8151 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
8152 x, y,
8157 float cx = 0, cy = 0;
8158 foreach (const ref cs; commands) {
8159 switch (cs.code) {
8160 case Command.Kind.LineTo:
8161 case Command.Kind.MoveTo:
8162 addPoint(cs.args[0], cs.args[1], cs.code);
8163 cx = cs.args[0];
8164 cy = cs.args[1];
8165 break;
8166 case Command.Kind.QuadTo:
8167 flattenQuad(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3]);
8168 cx = cs.args[2];
8169 cy = cs.args[3];
8170 break;
8171 case Command.Kind.BezierTo:
8172 flattenBezier(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3], cs.args[4], cs.args[5], 0);
8173 cx = cs.args[4];
8174 cy = cs.args[5];
8175 break;
8176 default:
8177 break;
8181 return res;
8184 /// Returns forward range with all glyph commands.
8185 auto commands () const nothrow @trusted @nogc {
8186 static struct Range {
8187 private nothrow @trusted @nogc:
8188 usize dsaddr;
8189 uint cpos; // current position in data
8190 uint cleft; // number of commands left
8191 @property const(ubyte)* data () inout pure { pragma(inline, true); return (dsaddr ? (cast(DataStore*)dsaddr).data : null); }
8192 public:
8193 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); }
8194 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); }
8195 void opAssign() (in auto ref Range a) {
8196 incRef(cast(DataStore*)a.dsaddr);
8197 decRef(cast(DataStore*)dsaddr);
8198 dsaddr = a.dsaddr;
8199 cpos = a.cpos;
8200 cleft = a.cleft;
8202 float[4] bounds () const pure { float[4] res = 0; pragma(inline, true); if (dsaddr) res[] = (cast(DataStore*)dsaddr).bounds[]; return res; } /// outline bounds
8203 @property bool empty () const pure { pragma(inline, true); return (cleft == 0); }
8204 @property int length () const pure { pragma(inline, true); return cleft; }
8205 @property Range save () const { pragma(inline, true); Range res = this; return res; }
8206 @property Command front () const {
8207 Command res = void;
8208 if (cleft > 0) {
8209 res.code = cast(Command.Kind)data[cpos];
8210 switch (res.code) {
8211 case Command.Kind.MoveTo:
8212 case Command.Kind.LineTo:
8213 res.args = (cast(const(float*))(data+cpos+1))[0..1*2];
8214 break;
8215 case Command.Kind.QuadTo:
8216 res.args = (cast(const(float*))(data+cpos+1))[0..2*2];
8217 break;
8218 case Command.Kind.BezierTo:
8219 res.args = (cast(const(float*))(data+cpos+1))[0..3*2];
8220 break;
8221 default:
8222 res.code = Command.Kind.End;
8223 res.args = null;
8224 break;
8226 } else {
8227 res.code = Command.Kind.End;
8228 res.args = null;
8230 return res;
8232 void popFront () {
8233 if (cleft <= 1) { cleft = 0; return; } // don't waste time skipping last command
8234 --cleft;
8235 switch (data[cpos]) {
8236 case Command.Kind.MoveTo:
8237 case Command.Kind.LineTo:
8238 cpos += 1+1*2*cast(uint)float.sizeof;
8239 break;
8240 case Command.Kind.QuadTo:
8241 cpos += 1+2*2*cast(uint)float.sizeof;
8242 break;
8243 case Command.Kind.BezierTo:
8244 cpos += 1+3*2*cast(uint)float.sizeof;
8245 break;
8246 default:
8247 cleft = 0;
8248 break;
8252 if (dsaddr) {
8253 incRef(cast(DataStore*)dsaddr); // range anchors it
8254 return Range(dsaddr, 0, ds.ccount);
8255 } else {
8256 return Range.init;
8261 public alias NVGGlyphOutline = NVGPathOutline; /// For backwards compatibility.
8263 /// Destroy glyph outiline and free allocated memory.
8264 /// Group: text_api
8265 public void kill (ref NVGPathOutline ol) nothrow @trusted @nogc {
8266 pragma(inline, true);
8267 ol.clear();
8270 static if (is(typeof(&fons__nvg__toOutline))) {
8271 public enum NanoVegaHasCharOutline = true; ///
8272 } else {
8273 public enum NanoVegaHasCharOutline = false; ///
8276 /// Returns glyph outlines as array of commands. Vertical 0 is baseline.
8277 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead.
8278 /// Returns `null` if there is no such glyph, or current font is not scalable.
8279 /// Group: text_api
8280 public NVGPathOutline charOutline (NVGContext ctx, dchar dch) nothrow @trusted @nogc {
8281 import core.stdc.stdlib : malloc;
8282 import core.stdc.string : memcpy;
8283 NVGstate* state = nvg__getState(ctx);
8284 ctx.fs.fontId = state.fontId;
8285 auto oline = NVGPathOutline.createNew();
8286 if (!ctx.fs.toOutline(dch, oline.ds)) oline.clear();
8287 return oline;
8291 float nvg__quantize (float a, float d) pure nothrow @safe @nogc {
8292 pragma(inline, true);
8293 return (cast(int)(a/d+0.5f))*d;
8296 float nvg__getFontScale (NVGstate* state) /*pure*/ nothrow @safe @nogc {
8297 pragma(inline, true);
8298 return nvg__min(nvg__quantize(nvg__getAverageScale(state.xform), 0.01f), 4.0f);
8301 void nvg__flushTextTexture (NVGContext ctx) nothrow @trusted @nogc {
8302 int[4] dirty = void;
8303 if (ctx.fs.validateTexture(dirty.ptr)) {
8304 auto fontImage = &ctx.fontImages[ctx.fontImageIdx];
8305 // Update texture
8306 if (fontImage.valid) {
8307 int iw, ih;
8308 const(ubyte)* data = ctx.fs.getTextureData(&iw, &ih);
8309 int x = dirty[0];
8310 int y = dirty[1];
8311 int w = dirty[2]-dirty[0];
8312 int h = dirty[3]-dirty[1];
8313 ctx.params.renderUpdateTexture(ctx.params.userPtr, fontImage.id, x, y, w, h, data);
8318 bool nvg__allocTextAtlas (NVGContext ctx) nothrow @trusted @nogc {
8319 int iw, ih;
8320 nvg__flushTextTexture(ctx);
8321 if (ctx.fontImageIdx >= NVG_MAX_FONTIMAGES-1) return false;
8322 // if next fontImage already have a texture
8323 if (ctx.fontImages[ctx.fontImageIdx+1].valid) {
8324 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx+1], iw, ih);
8325 } else {
8326 // calculate the new font image size and create it
8327 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx], iw, ih);
8328 if (iw > ih) ih *= 2; else iw *= 2;
8329 if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE;
8330 ctx.fontImages[ctx.fontImageIdx+1].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, iw, ih, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null);
8331 if (ctx.fontImages[ctx.fontImageIdx+1].id > 0) {
8332 ctx.fontImages[ctx.fontImageIdx+1].ctx = ctx;
8333 ctx.nvg__imageIncRef(ctx.fontImages[ctx.fontImageIdx+1].id, false); // don't increment driver refcount
8336 ++ctx.fontImageIdx;
8337 ctx.fs.resetAtlas(iw, ih);
8338 return true;
8341 void nvg__renderText (NVGContext ctx, NVGVertex* verts, int nverts) nothrow @trusted @nogc {
8342 NVGstate* state = nvg__getState(ctx);
8343 NVGPaint paint = state.fill;
8345 // Render triangles.
8346 paint.image = ctx.fontImages[ctx.fontImageIdx];
8348 // Apply global alpha
8349 paint.innerColor.a *= state.alpha;
8350 paint.middleColor.a *= state.alpha;
8351 paint.outerColor.a *= state.alpha;
8353 ctx.params.renderTriangles(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &paint, &state.scissor, verts, nverts, nvg_getFringe(ctx)/*ctx.fringeWidth*/);
8355 ++ctx.drawCallCount;
8356 ctx.textTriCount += nverts/3;
8359 /// Draws text string at specified location. Returns next x position.
8360 /// Group: text_api
8361 public float text(T) (NVGContext ctx, float x, float y, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8362 NVGstate* state = nvg__getState(ctx);
8363 FONSTextIter!T iter, prevIter;
8364 FONSQuad q;
8365 NVGVertex* verts;
8366 float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8367 float invscale = 1.0f/scale;
8368 int cverts = 0;
8369 int nverts = 0;
8371 if (state.fontId == FONS_INVALID) return x;
8372 if (str.length == 0) return x;
8374 ctx.fs.size = state.fontSize*scale;
8375 ctx.fs.spacing = state.letterSpacing*scale;
8376 ctx.fs.blur = state.fontBlur*scale;
8377 ctx.fs.textAlign = state.textAlign;
8378 ctx.fs.fontId = state.fontId;
8380 cverts = nvg__max(2, cast(int)(str.length))*6; // conservative estimate
8381 verts = nvg__allocTempVerts(ctx, cverts);
8382 if (verts is null) return x;
8384 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Required)) return x;
8385 prevIter = iter;
8386 while (iter.next(q)) {
8387 float[4*2] c = void;
8388 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8389 if (nverts != 0) {
8390 // TODO: add back-end bit to do this just once per frame
8391 nvg__flushTextTexture(ctx);
8392 nvg__renderText(ctx, verts, nverts);
8393 nverts = 0;
8395 if (!nvg__allocTextAtlas(ctx)) break; // no memory :(
8396 iter = prevIter;
8397 iter.next(q); // try again
8398 if (iter.prevGlyphIndex < 0) {
8399 // still can not find glyph, try replacement
8400 iter = prevIter;
8401 if (!iter.getDummyChar(q)) break;
8404 prevIter = iter;
8405 // transform corners
8406 state.xform.point(&c[0], &c[1], q.x0*invscale, q.y0*invscale);
8407 state.xform.point(&c[2], &c[3], q.x1*invscale, q.y0*invscale);
8408 state.xform.point(&c[4], &c[5], q.x1*invscale, q.y1*invscale);
8409 state.xform.point(&c[6], &c[7], q.x0*invscale, q.y1*invscale);
8410 // create triangles
8411 if (nverts+6 <= cverts) {
8412 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts;
8413 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts;
8414 nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); ++nverts;
8415 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts;
8416 nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); ++nverts;
8417 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts;
8421 // TODO: add back-end bit to do this just once per frame
8422 if (nverts > 0) {
8423 nvg__flushTextTexture(ctx);
8424 nvg__renderText(ctx, verts, nverts);
8427 return iter.nextx/scale;
8430 /** Draws multi-line text string at specified location wrapped at the specified width.
8431 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8432 * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8434 * Group: text_api
8436 public void textBox(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8437 NVGstate* state = nvg__getState(ctx);
8438 if (state.fontId == FONS_INVALID) return;
8440 NVGTextRow!T[2] rows;
8441 auto oldAlign = state.textAlign;
8442 scope(exit) state.textAlign = oldAlign;
8443 auto halign = state.textAlign.horizontal;
8444 float lineh = 0;
8446 ctx.textMetrics(null, null, &lineh);
8447 state.textAlign.horizontal = NVGTextAlign.H.Left;
8448 for (;;) {
8449 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]);
8450 //{ import core.stdc.stdio : printf; printf("slen=%u; rlen=%u; bw=%f\n", cast(uint)str.length, cast(uint)rres.length, cast(double)breakRowWidth); }
8451 if (rres.length == 0) break;
8452 foreach (ref row; rres) {
8453 final switch (halign) {
8454 case NVGTextAlign.H.Left: ctx.text(x, y, row.row); break;
8455 case NVGTextAlign.H.Center: ctx.text(x+breakRowWidth*0.5f-row.width*0.5f, y, row.row); break;
8456 case NVGTextAlign.H.Right: ctx.text(x+breakRowWidth-row.width, y, row.row); break;
8458 y += lineh*state.lineHeight;
8460 str = rres[$-1].rest;
8464 private template isGoodPositionDelegate(DG) {
8465 private DG dg;
8466 static if (is(typeof({ NVGGlyphPosition pos; bool res = dg(pos); })) ||
8467 is(typeof({ NVGGlyphPosition pos; dg(pos); })))
8468 enum isGoodPositionDelegate = true;
8469 else
8470 enum isGoodPositionDelegate = false;
8473 /** Calculates the glyph x positions of the specified text.
8474 * Measured values are returned in local coordinate space.
8476 * Group: text_api
8478 public NVGGlyphPosition[] textGlyphPositions(T) (NVGContext ctx, float x, float y, const(T)[] str, NVGGlyphPosition[] positions) nothrow @trusted @nogc
8479 if (isAnyCharType!T)
8481 if (str.length == 0 || positions.length == 0) return positions[0..0];
8482 usize posnum;
8483 auto len = ctx.textGlyphPositions(x, y, str, (in ref NVGGlyphPosition pos) {
8484 positions.ptr[posnum++] = pos;
8485 return (posnum < positions.length);
8487 return positions[0..len];
8490 /// Ditto.
8491 public int textGlyphPositions(T, DG) (NVGContext ctx, float x, float y, const(T)[] str, scope DG dg)
8492 if (isAnyCharType!T && isGoodPositionDelegate!DG)
8494 import std.traits : ReturnType;
8495 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true;
8497 NVGstate* state = nvg__getState(ctx);
8498 float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8499 float invscale = 1.0f/scale;
8500 FONSTextIter!T iter, prevIter;
8501 FONSQuad q;
8502 int npos = 0;
8504 if (str.length == 0) return 0;
8506 ctx.fs.size = state.fontSize*scale;
8507 ctx.fs.spacing = state.letterSpacing*scale;
8508 ctx.fs.blur = state.fontBlur*scale;
8509 ctx.fs.textAlign = state.textAlign;
8510 ctx.fs.fontId = state.fontId;
8512 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Optional)) return npos;
8513 prevIter = iter;
8514 while (iter.next(q)) {
8515 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8516 if (!nvg__allocTextAtlas(ctx)) break; // no memory
8517 iter = prevIter;
8518 iter.next(q); // try again
8519 if (iter.prevGlyphIndex < 0) {
8520 // still can not find glyph, try replacement
8521 iter = prevIter;
8522 if (!iter.getDummyChar(q)) break;
8525 prevIter = iter;
8526 NVGGlyphPosition position = void; //WARNING!
8527 position.strpos = cast(usize)(iter.stringp-str.ptr);
8528 position.x = iter.x*invscale;
8529 position.minx = nvg__min(iter.x, q.x0)*invscale;
8530 position.maxx = nvg__max(iter.nextx, q.x1)*invscale;
8531 ++npos;
8532 static if (RetBool) { if (!dg(position)) return npos; } else dg(position);
8535 return npos;
8538 private template isGoodRowDelegate(CT, DG) {
8539 private DG dg;
8540 static if (is(typeof({ NVGTextRow!CT row; bool res = dg(row); })) ||
8541 is(typeof({ NVGTextRow!CT row; dg(row); })))
8542 enum isGoodRowDelegate = true;
8543 else
8544 enum isGoodRowDelegate = false;
8547 /** Breaks the specified text into lines.
8548 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8549 * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8551 * Group: text_api
8553 public NVGTextRow!T[] textBreakLines(T) (NVGContext ctx, const(T)[] str, float breakRowWidth, NVGTextRow!T[] rows) nothrow @trusted @nogc
8554 if (isAnyCharType!T)
8556 if (rows.length == 0) return rows;
8557 if (rows.length > int.max-1) rows = rows[0..int.max-1];
8558 int nrow = 0;
8559 auto count = ctx.textBreakLines(str, breakRowWidth, (in ref NVGTextRow!T row) {
8560 rows[nrow++] = row;
8561 return (nrow < rows.length);
8563 return rows[0..count];
8566 /** Breaks the specified text into lines.
8567 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8568 * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8569 * Returns number of rows.
8571 * Group: text_api
8573 public int textBreakLines(T, DG) (NVGContext ctx, const(T)[] str, float breakRowWidth, scope DG dg)
8574 if (isAnyCharType!T && isGoodRowDelegate!(T, DG))
8576 import std.traits : ReturnType;
8577 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true;
8579 enum NVGcodepointType : int {
8580 Space,
8581 NewLine,
8582 Char,
8585 NVGstate* state = nvg__getState(ctx);
8586 float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8587 float invscale = 1.0f/scale;
8588 FONSTextIter!T iter, prevIter;
8589 FONSQuad q;
8590 int nrows = 0;
8591 float rowStartX = 0;
8592 float rowWidth = 0;
8593 float rowMinX = 0;
8594 float rowMaxX = 0;
8595 int rowStart = 0;
8596 int rowEnd = 0;
8597 int wordStart = 0;
8598 float wordStartX = 0;
8599 float wordMinX = 0;
8600 int breakEnd = 0;
8601 float breakWidth = 0;
8602 float breakMaxX = 0;
8603 int type = NVGcodepointType.Space, ptype = NVGcodepointType.Space;
8604 uint pcodepoint = 0;
8606 if (state.fontId == FONS_INVALID) return 0;
8607 if (str.length == 0 || dg is null) return 0;
8609 ctx.fs.size = state.fontSize*scale;
8610 ctx.fs.spacing = state.letterSpacing*scale;
8611 ctx.fs.blur = state.fontBlur*scale;
8612 ctx.fs.textAlign = state.textAlign;
8613 ctx.fs.fontId = state.fontId;
8615 breakRowWidth *= scale;
8617 enum Phase {
8618 Normal, // searching for breaking point
8619 SkipBlanks, // skip leading blanks
8621 Phase phase = Phase.SkipBlanks; // don't skip blanks on first line
8623 if (!iter.setup(ctx.fs, 0, 0, str, FONSBitmapFlag.Optional)) return 0;
8624 prevIter = iter;
8625 while (iter.next(q)) {
8626 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8627 if (!nvg__allocTextAtlas(ctx)) break; // no memory
8628 iter = prevIter;
8629 iter.next(q); // try again
8630 if (iter.prevGlyphIndex < 0) {
8631 // still can not find glyph, try replacement
8632 iter = prevIter;
8633 if (!iter.getDummyChar(q)) break;
8636 prevIter = iter;
8637 switch (iter.codepoint) {
8638 case 9: // \t
8639 case 11: // \v
8640 case 12: // \f
8641 case 32: // space
8642 case 0x00a0: // NBSP
8643 type = NVGcodepointType.Space;
8644 break;
8645 case 10: // \n
8646 type = (pcodepoint == 13 ? NVGcodepointType.Space : NVGcodepointType.NewLine);
8647 break;
8648 case 13: // \r
8649 type = (pcodepoint == 10 ? NVGcodepointType.Space : NVGcodepointType.NewLine);
8650 break;
8651 case 0x0085: // NEL
8652 case 0x2028: // Line Separator
8653 case 0x2029: // Paragraph Separator
8654 type = NVGcodepointType.NewLine;
8655 break;
8656 default:
8657 type = NVGcodepointType.Char;
8658 break;
8660 if (phase == Phase.SkipBlanks) {
8661 // fix row start
8662 rowStart = cast(int)(iter.stringp-str.ptr);
8663 rowEnd = rowStart;
8664 rowStartX = iter.x;
8665 rowWidth = iter.nextx-rowStartX; // q.x1-rowStartX;
8666 rowMinX = q.x0-rowStartX;
8667 rowMaxX = q.x1-rowStartX;
8668 wordStart = rowStart;
8669 wordStartX = iter.x;
8670 wordMinX = q.x0-rowStartX;
8671 breakEnd = rowStart;
8672 breakWidth = 0.0;
8673 breakMaxX = 0.0;
8674 if (type == NVGcodepointType.Space) continue;
8675 phase = Phase.Normal;
8678 if (type == NVGcodepointType.NewLine) {
8679 // always handle new lines
8680 NVGTextRow!T row;
8681 row.string = str;
8682 row.start = rowStart;
8683 row.end = rowEnd;
8684 row.width = rowWidth*invscale;
8685 row.minx = rowMinX*invscale;
8686 row.maxx = rowMaxX*invscale;
8687 ++nrows;
8688 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8689 phase = Phase.SkipBlanks;
8690 } else {
8691 float nextWidth = iter.nextx-rowStartX;
8692 // track last non-white space character
8693 if (type == NVGcodepointType.Char) {
8694 rowEnd = cast(int)(iter.nextp-str.ptr);
8695 rowWidth = iter.nextx-rowStartX;
8696 rowMaxX = q.x1-rowStartX;
8698 // track last end of a word
8699 if (ptype == NVGcodepointType.Char && type == NVGcodepointType.Space) {
8700 breakEnd = cast(int)(iter.stringp-str.ptr);
8701 breakWidth = rowWidth;
8702 breakMaxX = rowMaxX;
8704 // track last beginning of a word
8705 if (ptype == NVGcodepointType.Space && type == NVGcodepointType.Char) {
8706 wordStart = cast(int)(iter.stringp-str.ptr);
8707 wordStartX = iter.x;
8708 wordMinX = q.x0-rowStartX;
8710 // break to new line when a character is beyond break width
8711 if (type == NVGcodepointType.Char && nextWidth > breakRowWidth) {
8712 // the run length is too long, need to break to new line
8713 NVGTextRow!T row;
8714 row.string = str;
8715 if (breakEnd == rowStart) {
8716 // the current word is longer than the row length, just break it from here
8717 row.start = rowStart;
8718 row.end = cast(int)(iter.stringp-str.ptr);
8719 row.width = rowWidth*invscale;
8720 row.minx = rowMinX*invscale;
8721 row.maxx = rowMaxX*invscale;
8722 ++nrows;
8723 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8724 rowStartX = iter.x;
8725 rowStart = cast(int)(iter.stringp-str.ptr);
8726 rowEnd = cast(int)(iter.nextp-str.ptr);
8727 rowWidth = iter.nextx-rowStartX;
8728 rowMinX = q.x0-rowStartX;
8729 rowMaxX = q.x1-rowStartX;
8730 wordStart = rowStart;
8731 wordStartX = iter.x;
8732 wordMinX = q.x0-rowStartX;
8733 } else {
8734 // break the line from the end of the last word, and start new line from the beginning of the new
8735 //{ import core.stdc.stdio : printf; printf("rowStart=%u; rowEnd=%u; breakEnd=%u; len=%u\n", rowStart, rowEnd, breakEnd, cast(uint)str.length); }
8736 row.start = rowStart;
8737 row.end = breakEnd;
8738 row.width = breakWidth*invscale;
8739 row.minx = rowMinX*invscale;
8740 row.maxx = breakMaxX*invscale;
8741 ++nrows;
8742 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8743 rowStartX = wordStartX;
8744 rowStart = wordStart;
8745 rowEnd = cast(int)(iter.nextp-str.ptr);
8746 rowWidth = iter.nextx-rowStartX;
8747 rowMinX = wordMinX;
8748 rowMaxX = q.x1-rowStartX;
8749 // no change to the word start
8751 // set null break point
8752 breakEnd = rowStart;
8753 breakWidth = 0.0;
8754 breakMaxX = 0.0;
8758 pcodepoint = iter.codepoint;
8759 ptype = type;
8762 // break the line from the end of the last word, and start new line from the beginning of the new
8763 if (phase != Phase.SkipBlanks && rowStart < str.length) {
8764 //{ import core.stdc.stdio : printf; printf(" rowStart=%u; len=%u\n", rowStart, cast(uint)str.length); }
8765 NVGTextRow!T row;
8766 row.string = str;
8767 row.start = rowStart;
8768 row.end = cast(int)str.length;
8769 row.width = rowWidth*invscale;
8770 row.minx = rowMinX*invscale;
8771 row.maxx = rowMaxX*invscale;
8772 ++nrows;
8773 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8776 return nrows;
8779 /** Returns iterator which you can use to calculate text bounds and advancement.
8780 * This is usable when you need to do some text layouting with wrapping, to avoid
8781 * guesswork ("will advancement for this space stay the same?"), and Schlemiel's
8782 * algorithm. Note that you can copy the returned struct to save iterator state.
8784 * You can check if iterator is valid with [valid] property, put new chars with
8785 * [put] method, get current advance with [advance] property, and current
8786 * bounds with `getBounds(ref float[4] bounds)` method.
8788 * $(WARNING Don't change font parameters while iterating! Or use [restoreFont] method.)
8790 * Group: text_api
8792 public struct TextBoundsIterator {
8793 private:
8794 NVGContext ctx;
8795 FONSTextBoundsIterator fsiter; // fontstash iterator
8796 float scale, invscale, xscaled, yscaled;
8797 // font settings
8798 float fsSize, fsSpacing, fsBlur;
8799 int fsFontId;
8800 NVGTextAlign fsAlign;
8802 public:
8803 /// Setups iteration. Takes current font parameters from the given NanoVega context.
8804 this (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { reset(actx, ax, ay); }
8806 /// Resets iteration. Takes current font parameters from the given NanoVega context.
8807 void reset (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc {
8808 fsiter = fsiter.init;
8809 this = this.init;
8810 if (actx is null) return;
8811 NVGstate* state = nvg__getState(actx);
8812 if (state is null) return;
8813 if (state.fontId == FONS_INVALID) { ctx = null; return; }
8815 ctx = actx;
8816 scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8817 invscale = 1.0f/scale;
8819 fsSize = state.fontSize*scale;
8820 fsSpacing = state.letterSpacing*scale;
8821 fsBlur = state.fontBlur*scale;
8822 fsAlign = state.textAlign;
8823 fsFontId = state.fontId;
8824 restoreFont();
8826 xscaled = ax*scale;
8827 yscaled = ay*scale;
8828 fsiter.reset(ctx.fs, xscaled, yscaled);
8831 /// Restart iteration. Will not restore font.
8832 void restart () nothrow @trusted @nogc {
8833 if (ctx !is null) fsiter.reset(ctx.fs, xscaled, yscaled);
8836 /// Restore font settings for the context.
8837 void restoreFont () nothrow @trusted @nogc {
8838 if (ctx !is null) {
8839 ctx.fs.size = fsSize;
8840 ctx.fs.spacing = fsSpacing;
8841 ctx.fs.blur = fsBlur;
8842 ctx.fs.textAlign = fsAlign;
8843 ctx.fs.fontId = fsFontId;
8847 /// Is this iterator valid?
8848 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null); }
8850 /// Add chars.
8851 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { pragma(inline, true); if (ctx !is null) fsiter.put(str[]); }
8853 /// Returns current advance
8854 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null ? fsiter.advance*invscale : 0); }
8856 /// Returns current text bounds.
8857 void getBounds (ref float[4] bounds) nothrow @trusted @nogc {
8858 if (ctx !is null) {
8859 fsiter.getBounds(bounds);
8860 ctx.fs.getLineBounds(yscaled, &bounds[1], &bounds[3]);
8861 bounds[0] *= invscale;
8862 bounds[1] *= invscale;
8863 bounds[2] *= invscale;
8864 bounds[3] *= invscale;
8865 } else {
8866 bounds[] = 0;
8870 /// Returns current horizontal text bounds.
8871 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc {
8872 if (ctx !is null) {
8873 fsiter.getHBounds(xmin, xmax);
8874 xmin *= invscale;
8875 xmax *= invscale;
8879 /// Returns current vertical text bounds.
8880 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc {
8881 if (ctx !is null) {
8882 //fsiter.getVBounds(ymin, ymax);
8883 ctx.fs.getLineBounds(yscaled, &ymin, &ymax);
8884 ymin *= invscale;
8885 ymax *= invscale;
8890 /// Returns font line height (without line spacing), measured in local coordinate space.
8891 /// Group: text_api
8892 public float textFontHeight (NVGContext ctx) nothrow @trusted @nogc {
8893 float res = void;
8894 ctx.textMetrics(null, null, &res);
8895 return res;
8898 /// Returns font ascender (positive), measured in local coordinate space.
8899 /// Group: text_api
8900 public float textFontAscender (NVGContext ctx) nothrow @trusted @nogc {
8901 float res = void;
8902 ctx.textMetrics(&res, null, null);
8903 return res;
8906 /// Returns font descender (negative), measured in local coordinate space.
8907 /// Group: text_api
8908 public float textFontDescender (NVGContext ctx) nothrow @trusted @nogc {
8909 float res = void;
8910 ctx.textMetrics(null, &res, null);
8911 return res;
8914 /** Measures the specified text string. Returns horizontal and vertical sizes of the measured text.
8915 * Measured values are returned in local coordinate space.
8917 * Group: text_api
8919 public void textExtents(T) (NVGContext ctx, const(T)[] str, float *w, float *h) nothrow @trusted @nogc if (isAnyCharType!T) {
8920 float[4] bnd = void;
8921 ctx.textBounds(0, 0, str, bnd[]);
8922 if (!ctx.fs.getFontAA(nvg__getState(ctx).fontId)) {
8923 if (w !is null) *w = nvg__lrintf(bnd.ptr[2]-bnd.ptr[0]);
8924 if (h !is null) *h = nvg__lrintf(bnd.ptr[3]-bnd.ptr[1]);
8925 } else {
8926 if (w !is null) *w = bnd.ptr[2]-bnd.ptr[0];
8927 if (h !is null) *h = bnd.ptr[3]-bnd.ptr[1];
8931 /** Measures the specified text string. Returns horizontal size of the measured text.
8932 * Measured values are returned in local coordinate space.
8934 * Group: text_api
8936 public float textWidth(T) (NVGContext ctx, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8937 float w = void;
8938 ctx.textExtents(str, &w, null);
8939 return w;
8942 /** Measures the specified text string. Parameter bounds should be a float[4],
8943 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax]
8944 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
8945 * Measured values are returned in local coordinate space.
8947 * Group: text_api
8949 public float textBounds(T) (NVGContext ctx, float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc
8950 if (isAnyCharType!T)
8952 NVGstate* state = nvg__getState(ctx);
8954 if (state.fontId == FONS_INVALID) {
8955 bounds[] = 0;
8956 return 0;
8959 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8960 ctx.fs.size = state.fontSize*scale;
8961 ctx.fs.spacing = state.letterSpacing*scale;
8962 ctx.fs.blur = state.fontBlur*scale;
8963 ctx.fs.textAlign = state.textAlign;
8964 ctx.fs.fontId = state.fontId;
8966 float[4] b = void;
8967 immutable float width = ctx.fs.getTextBounds(x*scale, y*scale, str, b[]);
8968 immutable float invscale = 1.0f/scale;
8969 if (bounds.length) {
8970 // use line bounds for height
8971 ctx.fs.getLineBounds(y*scale, b.ptr+1, b.ptr+3);
8972 if (bounds.length > 0) bounds.ptr[0] = b.ptr[0]*invscale;
8973 if (bounds.length > 1) bounds.ptr[1] = b.ptr[1]*invscale;
8974 if (bounds.length > 2) bounds.ptr[2] = b.ptr[2]*invscale;
8975 if (bounds.length > 3) bounds.ptr[3] = b.ptr[3]*invscale;
8977 return width*invscale;
8980 /// Ditto.
8981 public void textBoxBounds(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str, float[] bounds) if (isAnyCharType!T) {
8982 NVGstate* state = nvg__getState(ctx);
8983 NVGTextRow!T[2] rows;
8984 float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8985 float invscale = 1.0f/scale;
8986 float lineh = 0, rminy = 0, rmaxy = 0;
8987 float minx, miny, maxx, maxy;
8989 if (state.fontId == FONS_INVALID) {
8990 bounds[] = 0;
8991 return;
8994 auto oldAlign = state.textAlign;
8995 scope(exit) state.textAlign = oldAlign;
8996 auto halign = state.textAlign.horizontal;
8998 ctx.textMetrics(null, null, &lineh);
8999 state.textAlign.horizontal = NVGTextAlign.H.Left;
9001 minx = maxx = x;
9002 miny = maxy = y;
9004 ctx.fs.size = state.fontSize*scale;
9005 ctx.fs.spacing = state.letterSpacing*scale;
9006 ctx.fs.blur = state.fontBlur*scale;
9007 ctx.fs.textAlign = state.textAlign;
9008 ctx.fs.fontId = state.fontId;
9009 ctx.fs.getLineBounds(0, &rminy, &rmaxy);
9010 rminy *= invscale;
9011 rmaxy *= invscale;
9013 for (;;) {
9014 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]);
9015 if (rres.length == 0) break;
9016 foreach (ref row; rres) {
9017 float rminx, rmaxx, dx = 0;
9018 // horizontal bounds
9019 final switch (halign) {
9020 case NVGTextAlign.H.Left: dx = 0; break;
9021 case NVGTextAlign.H.Center: dx = breakRowWidth*0.5f-row.width*0.5f; break;
9022 case NVGTextAlign.H.Right: dx = breakRowWidth-row.width; break;
9024 rminx = x+row.minx+dx;
9025 rmaxx = x+row.maxx+dx;
9026 minx = nvg__min(minx, rminx);
9027 maxx = nvg__max(maxx, rmaxx);
9028 // vertical bounds
9029 miny = nvg__min(miny, y+rminy);
9030 maxy = nvg__max(maxy, y+rmaxy);
9031 y += lineh*state.lineHeight;
9033 str = rres[$-1].rest;
9036 if (bounds.length) {
9037 if (bounds.length > 0) bounds.ptr[0] = minx;
9038 if (bounds.length > 1) bounds.ptr[1] = miny;
9039 if (bounds.length > 2) bounds.ptr[2] = maxx;
9040 if (bounds.length > 3) bounds.ptr[3] = maxy;
9044 /// Returns the vertical metrics based on the current text style. Measured values are returned in local coordinate space.
9045 /// Group: text_api
9046 public void textMetrics (NVGContext ctx, float* ascender, float* descender, float* lineh) nothrow @trusted @nogc {
9047 NVGstate* state = nvg__getState(ctx);
9049 if (state.fontId == FONS_INVALID) {
9050 if (ascender !is null) *ascender *= 0;
9051 if (descender !is null) *descender *= 0;
9052 if (lineh !is null) *lineh *= 0;
9053 return;
9056 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9057 immutable float invscale = 1.0f/scale;
9059 ctx.fs.size = state.fontSize*scale;
9060 ctx.fs.spacing = state.letterSpacing*scale;
9061 ctx.fs.blur = state.fontBlur*scale;
9062 ctx.fs.textAlign = state.textAlign;
9063 ctx.fs.fontId = state.fontId;
9065 ctx.fs.getVertMetrics(ascender, descender, lineh);
9066 if (ascender !is null) *ascender *= invscale;
9067 if (descender !is null) *descender *= invscale;
9068 if (lineh !is null) *lineh *= invscale;
9072 // ////////////////////////////////////////////////////////////////////////// //
9073 // fontstash
9074 // ////////////////////////////////////////////////////////////////////////// //
9075 import core.stdc.stdlib : malloc, realloc, free;
9076 import core.stdc.string : memset, memcpy, strncpy, strcmp, strlen;
9077 import core.stdc.stdio : FILE, fopen, fclose, fseek, ftell, fread, SEEK_END, SEEK_SET;
9079 public:
9080 // welcome to version hell!
9081 version(nanovg_force_stb_ttf) {
9082 } else {
9083 version(nanovg_force_detect) {} else version(nanovg_use_freetype) { version = nanovg_use_freetype_ii; }
9085 version(nanovg_ignore_iv_stb_ttf) enum nanovg_ignore_iv_stb_ttf = true; else enum nanovg_ignore_iv_stb_ttf = false;
9086 //version(nanovg_ignore_mono);
9088 version(nanovg_force_stb_ttf) {
9089 private enum NanoVegaForceFreeType = false;
9090 } else {
9091 version (nanovg_builtin_freetype_bindings) {
9092 version(Posix) {
9093 private enum NanoVegaForceFreeType = true;
9094 } else {
9095 private enum NanoVegaForceFreeType = false;
9097 } else {
9098 version(Posix) {
9099 private enum NanoVegaForceFreeType = true;
9100 } else {
9101 private enum NanoVegaForceFreeType = false;
9106 version(nanovg_use_freetype_ii) {
9107 enum NanoVegaIsUsingSTBTTF = false;
9108 //pragma(msg, "iv.freetype: forced");
9109 } else {
9110 static if (NanoVegaForceFreeType) {
9111 enum NanoVegaIsUsingSTBTTF = false;
9112 } else {
9113 static if (!nanovg_ignore_iv_stb_ttf && __traits(compiles, { import iv.stb.ttf; })) {
9114 import iv.stb.ttf;
9115 enum NanoVegaIsUsingSTBTTF = true;
9116 version(nanovg_report_stb_ttf) pragma(msg, "iv.stb.ttf");
9117 } else static if (__traits(compiles, { import arsd.ttf; })) {
9118 import arsd.ttf;
9119 enum NanoVegaIsUsingSTBTTF = true;
9120 version(nanovg_report_stb_ttf) pragma(msg, "arsd.ttf");
9121 } else static if (__traits(compiles, { import stb_truetype; })) {
9122 import stb_truetype;
9123 enum NanoVegaIsUsingSTBTTF = true;
9124 version(nanovg_report_stb_ttf) pragma(msg, "stb_truetype");
9125 } else static if (__traits(compiles, { import iv.freetype; })) {
9126 version (nanovg_builtin_freetype_bindings) {
9127 enum NanoVegaIsUsingSTBTTF = false;
9128 version = nanovg_builtin_freetype_bindings;
9129 } else {
9130 import iv.freetype;
9131 enum NanoVegaIsUsingSTBTTF = false;
9133 version(nanovg_report_stb_ttf) pragma(msg, "freetype");
9134 } else {
9135 static assert(0, "no stb_ttf/iv.freetype found!");
9141 // ////////////////////////////////////////////////////////////////////////// //
9142 //version = nanovg_ft_mono;
9144 /// Invald font id.
9145 /// Group: font_stash
9146 public enum FONS_INVALID = -1;
9148 public enum FONSBitmapFlag : uint {
9149 Required = 0,
9150 Optional = 1,
9153 public enum FONSError : int {
9154 NoError = 0,
9155 AtlasFull = 1, // Font atlas is full.
9156 StatesOverflow = 2, // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES.
9157 StatesUnderflow = 3, // Trying to pop too many states fonsPopState().
9160 /// Initial parameters for new FontStash.
9161 /// Group: font_stash
9162 public struct FONSParams {
9163 enum Flag : uint {
9164 ZeroTopLeft = 0U, // default
9165 ZeroBottomLeft = 1U,
9167 int width, height;
9168 Flag flags = Flag.ZeroTopLeft;
9169 void* userPtr;
9170 bool function (void* uptr, int width, int height) nothrow @trusted @nogc renderCreate;
9171 int function (void* uptr, int width, int height) nothrow @trusted @nogc renderResize;
9172 void function (void* uptr, int* rect, const(ubyte)* data) nothrow @trusted @nogc renderUpdate;
9173 void function (void* uptr) nothrow @trusted @nogc renderDelete;
9174 @property bool isZeroTopLeft () const pure nothrow @trusted @nogc { pragma(inline, true); return ((flags&Flag.ZeroBottomLeft) == 0); }
9177 //TODO: document this
9178 public struct FONSQuad {
9179 float x0=0, y0=0, s0=0, t0=0;
9180 float x1=0, y1=0, s1=0, t1=0;
9183 //TODO: document this
9184 public struct FONSTextIter(CT) if (isAnyCharType!CT) {
9185 alias CharType = CT;
9186 float x=0, y=0, nextx=0, nexty=0, scale=0, spacing=0;
9187 uint codepoint;
9188 short isize, iblur;
9189 FONSContext stash;
9190 FONSfont* font;
9191 int prevGlyphIndex;
9192 const(CT)* s; // string
9193 const(CT)* n; // next
9194 const(CT)* e; // end
9195 FONSBitmapFlag bitmapOption;
9196 static if (is(CT == char)) {
9197 uint utf8state;
9200 this (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { setup(astash, ax, ay, astr, abitmapOption); }
9201 ~this () nothrow @trusted @nogc { pragma(inline, true); static if (is(CT == char)) utf8state = 0; s = n = e = null; }
9203 @property const(CT)* stringp () const pure nothrow @trusted @nogc { pragma(inline, true); return s; }
9204 @property const(CT)* nextp () const pure nothrow @trusted @nogc { pragma(inline, true); return n; }
9205 @property const(CT)* endp () const pure nothrow @trusted @nogc { pragma(inline, true); return e; }
9207 bool setup (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc {
9208 import core.stdc.string : memset;
9210 memset(&this, 0, this.sizeof);
9211 if (astash is null) return false;
9213 FONSstate* state = astash.getState;
9215 if (state.font < 0 || state.font >= astash.nfonts) return false;
9216 font = astash.fonts[state.font];
9217 if (font is null || font.fdata is null) return false;
9219 isize = cast(short)(state.size*10.0f);
9220 iblur = cast(short)state.blur;
9221 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
9223 // align horizontally
9224 if (state.talign.left) {
9225 // empty
9226 } else if (state.talign.right) {
9227 immutable float width = astash.getTextBounds(ax, ay, astr, null);
9228 ax -= width;
9229 } else if (state.talign.center) {
9230 immutable float width = astash.getTextBounds(ax, ay, astr, null);
9231 ax -= width*0.5f;
9234 // align vertically
9235 ay += astash.getVertAlign(font, state.talign, isize);
9237 x = nextx = ax;
9238 y = nexty = ay;
9239 spacing = state.spacing;
9241 if (astr.ptr is null) {
9242 static if (is(CharType == char)) astr = "";
9243 else static if (is(CharType == wchar)) astr = ""w;
9244 else static if (is(CharType == dchar)) astr = ""d;
9245 else static assert(0, "wtf?!");
9247 s = astr.ptr;
9248 n = astr.ptr;
9249 e = astr.ptr+astr.length;
9251 codepoint = 0;
9252 prevGlyphIndex = -1;
9253 bitmapOption = abitmapOption;
9254 stash = astash;
9256 return true;
9259 bool getDummyChar (ref FONSQuad quad) nothrow @trusted @nogc {
9260 if (stash is null || font is null) return false;
9261 // get glyph and quad
9262 x = nextx;
9263 y = nexty;
9264 FONSglyph* glyph = stash.getGlyph(font, 0xFFFD, isize, iblur, bitmapOption);
9265 if (glyph !is null) {
9266 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9267 prevGlyphIndex = glyph.index;
9268 return true;
9269 } else {
9270 prevGlyphIndex = -1;
9271 return false;
9275 bool next (ref FONSQuad quad) nothrow @trusted @nogc {
9276 if (stash is null || font is null) return false;
9277 FONSglyph* glyph = null;
9278 static if (is(CharType == char)) {
9279 const(char)* str = this.n;
9280 this.s = this.n;
9281 if (str is this.e) return false;
9282 const(char)* e = this.e;
9283 for (; str !is e; ++str) {
9284 /*if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;*/
9285 mixin(DecUtfMixin!("this.utf8state", "this.codepoint", "*cast(const(ubyte)*)str"));
9286 if (utf8state) continue;
9287 ++str; // 'cause we'll break anyway
9288 // get glyph and quad
9289 x = nextx;
9290 y = nexty;
9291 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption);
9292 if (glyph !is null) {
9293 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9294 prevGlyphIndex = glyph.index;
9295 } else {
9296 prevGlyphIndex = -1;
9298 break;
9300 this.n = str;
9301 } else {
9302 const(CharType)* str = this.n;
9303 this.s = this.n;
9304 if (str is this.e) return false;
9305 codepoint = cast(uint)(*str++);
9306 if (codepoint > dchar.max) codepoint = 0xFFFD;
9307 // get glyph and quad
9308 x = nextx;
9309 y = nexty;
9310 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption);
9311 if (glyph !is null) {
9312 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9313 prevGlyphIndex = glyph.index;
9314 } else {
9315 prevGlyphIndex = -1;
9317 this.n = str;
9319 return true;
9324 // ////////////////////////////////////////////////////////////////////////// //
9325 //static if (!HasAST) version = nanovg_use_freetype_ii_x;
9327 /*version(nanovg_use_freetype_ii_x)*/ static if (!NanoVegaIsUsingSTBTTF) {
9328 version(nanovg_builtin_freetype_bindings) {
9329 pragma(lib, "freetype");
9330 private extern(C) nothrow @trusted @nogc {
9331 private import core.stdc.config : c_long, c_ulong;
9332 alias FT_Pos = c_long;
9333 // config/ftconfig.h
9334 alias FT_Int16 = short;
9335 alias FT_UInt16 = ushort;
9336 alias FT_Int32 = int;
9337 alias FT_UInt32 = uint;
9338 alias FT_Fast = int;
9339 alias FT_UFast = uint;
9340 alias FT_Int64 = long;
9341 alias FT_Uint64 = ulong;
9342 // fttypes.h
9343 alias FT_Bool = ubyte;
9344 alias FT_FWord = short;
9345 alias FT_UFWord = ushort;
9346 alias FT_Char = char;
9347 alias FT_Byte = ubyte;
9348 alias FT_Bytes = FT_Byte*;
9349 alias FT_Tag = FT_UInt32;
9350 alias FT_String = char;
9351 alias FT_Short = short;
9352 alias FT_UShort = ushort;
9353 alias FT_Int = int;
9354 alias FT_UInt = uint;
9355 alias FT_Long = c_long;
9356 alias FT_ULong = c_ulong;
9357 alias FT_F2Dot14 = short;
9358 alias FT_F26Dot6 = c_long;
9359 alias FT_Fixed = c_long;
9360 alias FT_Error = int;
9361 alias FT_Pointer = void*;
9362 alias FT_Offset = usize;
9363 alias FT_PtrDist = ptrdiff_t;
9365 struct FT_UnitVector {
9366 FT_F2Dot14 x;
9367 FT_F2Dot14 y;
9370 struct FT_Matrix {
9371 FT_Fixed xx, xy;
9372 FT_Fixed yx, yy;
9375 struct FT_Data {
9376 const(FT_Byte)* pointer;
9377 FT_Int length;
9379 alias FT_Face = FT_FaceRec*;
9380 struct FT_FaceRec {
9381 FT_Long num_faces;
9382 FT_Long face_index;
9383 FT_Long face_flags;
9384 FT_Long style_flags;
9385 FT_Long num_glyphs;
9386 FT_String* family_name;
9387 FT_String* style_name;
9388 FT_Int num_fixed_sizes;
9389 FT_Bitmap_Size* available_sizes;
9390 FT_Int num_charmaps;
9391 FT_CharMap* charmaps;
9392 FT_Generic generic;
9393 FT_BBox bbox;
9394 FT_UShort units_per_EM;
9395 FT_Short ascender;
9396 FT_Short descender;
9397 FT_Short height;
9398 FT_Short max_advance_width;
9399 FT_Short max_advance_height;
9400 FT_Short underline_position;
9401 FT_Short underline_thickness;
9402 FT_GlyphSlot glyph;
9403 FT_Size size;
9404 FT_CharMap charmap;
9405 FT_Driver driver;
9406 FT_Memory memory;
9407 FT_Stream stream;
9408 FT_ListRec sizes_list;
9409 FT_Generic autohint;
9410 void* extensions;
9411 FT_Face_Internal internal;
9413 struct FT_Bitmap_Size {
9414 FT_Short height;
9415 FT_Short width;
9416 FT_Pos size;
9417 FT_Pos x_ppem;
9418 FT_Pos y_ppem;
9420 alias FT_CharMap = FT_CharMapRec*;
9421 struct FT_CharMapRec {
9422 FT_Face face;
9423 FT_Encoding encoding;
9424 FT_UShort platform_id;
9425 FT_UShort encoding_id;
9427 extern(C) nothrow @nogc { alias FT_Generic_Finalizer = void function (void* object); }
9428 struct FT_Generic {
9429 void* data;
9430 FT_Generic_Finalizer finalizer;
9432 struct FT_Vector {
9433 FT_Pos x;
9434 FT_Pos y;
9436 struct FT_BBox {
9437 FT_Pos xMin, yMin;
9438 FT_Pos xMax, yMax;
9440 alias FT_Pixel_Mode = int;
9441 enum {
9442 FT_PIXEL_MODE_NONE = 0,
9443 FT_PIXEL_MODE_MONO,
9444 FT_PIXEL_MODE_GRAY,
9445 FT_PIXEL_MODE_GRAY2,
9446 FT_PIXEL_MODE_GRAY4,
9447 FT_PIXEL_MODE_LCD,
9448 FT_PIXEL_MODE_LCD_V,
9449 FT_PIXEL_MODE_MAX
9451 struct FT_Bitmap {
9452 uint rows;
9453 uint width;
9454 int pitch;
9455 ubyte* buffer;
9456 ushort num_grays;
9457 ubyte pixel_mode;
9458 ubyte palette_mode;
9459 void* palette;
9461 struct FT_Outline {
9462 short n_contours;
9463 short n_points;
9464 FT_Vector* points;
9465 byte* tags;
9466 short* contours;
9467 int flags;
9469 alias FT_GlyphSlot = FT_GlyphSlotRec*;
9470 struct FT_GlyphSlotRec {
9471 FT_Library library;
9472 FT_Face face;
9473 FT_GlyphSlot next;
9474 FT_UInt reserved;
9475 FT_Generic generic;
9476 FT_Glyph_Metrics metrics;
9477 FT_Fixed linearHoriAdvance;
9478 FT_Fixed linearVertAdvance;
9479 FT_Vector advance;
9480 FT_Glyph_Format format;
9481 FT_Bitmap bitmap;
9482 FT_Int bitmap_left;
9483 FT_Int bitmap_top;
9484 FT_Outline outline;
9485 FT_UInt num_subglyphs;
9486 FT_SubGlyph subglyphs;
9487 void* control_data;
9488 c_long control_len;
9489 FT_Pos lsb_delta;
9490 FT_Pos rsb_delta;
9491 void* other;
9492 FT_Slot_Internal internal;
9494 alias FT_Size = FT_SizeRec*;
9495 struct FT_SizeRec {
9496 FT_Face face;
9497 FT_Generic generic;
9498 FT_Size_Metrics metrics;
9499 FT_Size_Internal internal;
9501 alias FT_Encoding = FT_Tag;
9502 alias FT_Face_Internal = void*;
9503 alias FT_Driver = void*;
9504 alias FT_Memory = void*;
9505 alias FT_Stream = void*;
9506 alias FT_Library = void*;
9507 alias FT_SubGlyph = void*;
9508 alias FT_Slot_Internal = void*;
9509 alias FT_Size_Internal = void*;
9510 alias FT_ListNode = FT_ListNodeRec*;
9511 alias FT_List = FT_ListRec*;
9512 struct FT_ListNodeRec {
9513 FT_ListNode prev;
9514 FT_ListNode next;
9515 void* data;
9517 struct FT_ListRec {
9518 FT_ListNode head;
9519 FT_ListNode tail;
9521 struct FT_Glyph_Metrics {
9522 FT_Pos width;
9523 FT_Pos height;
9524 FT_Pos horiBearingX;
9525 FT_Pos horiBearingY;
9526 FT_Pos horiAdvance;
9527 FT_Pos vertBearingX;
9528 FT_Pos vertBearingY;
9529 FT_Pos vertAdvance;
9531 alias FT_Glyph_Format = FT_Tag;
9532 FT_Tag FT_MAKE_TAG (char x1, char x2, char x3, char x4) pure nothrow @safe @nogc {
9533 pragma(inline, true);
9534 return cast(FT_UInt32)((x1<<24)|(x2<<16)|(x3<<8)|x4);
9536 enum : FT_Tag {
9537 FT_GLYPH_FORMAT_NONE = 0,
9538 FT_GLYPH_FORMAT_COMPOSITE = FT_MAKE_TAG('c','o','m','p'),
9539 FT_GLYPH_FORMAT_BITMAP = FT_MAKE_TAG('b','i','t','s'),
9540 FT_GLYPH_FORMAT_OUTLINE = FT_MAKE_TAG('o','u','t','l'),
9541 FT_GLYPH_FORMAT_PLOTTER = FT_MAKE_TAG('p','l','o','t'),
9543 struct FT_Size_Metrics {
9544 FT_UShort x_ppem;
9545 FT_UShort y_ppem;
9547 FT_Fixed x_scale;
9548 FT_Fixed y_scale;
9550 FT_Pos ascender;
9551 FT_Pos descender;
9552 FT_Pos height;
9553 FT_Pos max_advance;
9555 enum FT_LOAD_DEFAULT = 0x0U;
9556 enum FT_LOAD_NO_SCALE = 1U<<0;
9557 enum FT_LOAD_NO_HINTING = 1U<<1;
9558 enum FT_LOAD_RENDER = 1U<<2;
9559 enum FT_LOAD_NO_BITMAP = 1U<<3;
9560 enum FT_LOAD_VERTICAL_LAYOUT = 1U<<4;
9561 enum FT_LOAD_FORCE_AUTOHINT = 1U<<5;
9562 enum FT_LOAD_CROP_BITMAP = 1U<<6;
9563 enum FT_LOAD_PEDANTIC = 1U<<7;
9564 enum FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1U<<9;
9565 enum FT_LOAD_NO_RECURSE = 1U<<10;
9566 enum FT_LOAD_IGNORE_TRANSFORM = 1U<<11;
9567 enum FT_LOAD_MONOCHROME = 1U<<12;
9568 enum FT_LOAD_LINEAR_DESIGN = 1U<<13;
9569 enum FT_LOAD_NO_AUTOHINT = 1U<<15;
9570 enum FT_LOAD_COLOR = 1U<<20;
9571 enum FT_LOAD_COMPUTE_METRICS = 1U<<21;
9572 enum FT_FACE_FLAG_KERNING = 1U<<6;
9573 alias FT_Kerning_Mode = int;
9574 enum /*FT_Kerning_Mode*/ {
9575 FT_KERNING_DEFAULT = 0,
9576 FT_KERNING_UNFITTED,
9577 FT_KERNING_UNSCALED
9579 extern(C) nothrow @nogc {
9580 alias FT_Outline_MoveToFunc = int function (const(FT_Vector)*, void*);
9581 alias FT_Outline_LineToFunc = int function (const(FT_Vector)*, void*);
9582 alias FT_Outline_ConicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, void*);
9583 alias FT_Outline_CubicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, const(FT_Vector)*, void*);
9585 struct FT_Outline_Funcs {
9586 FT_Outline_MoveToFunc move_to;
9587 FT_Outline_LineToFunc line_to;
9588 FT_Outline_ConicToFunc conic_to;
9589 FT_Outline_CubicToFunc cubic_to;
9590 int shift;
9591 FT_Pos delta;
9594 FT_Error FT_Init_FreeType (FT_Library*);
9595 FT_Error FT_New_Memory_Face (FT_Library, const(FT_Byte)*, FT_Long, FT_Long, FT_Face*);
9596 FT_UInt FT_Get_Char_Index (FT_Face, FT_ULong);
9597 FT_Error FT_Set_Pixel_Sizes (FT_Face, FT_UInt, FT_UInt);
9598 FT_Error FT_Load_Glyph (FT_Face, FT_UInt, FT_Int32);
9599 FT_Error FT_Get_Advance (FT_Face, FT_UInt, FT_Int32, FT_Fixed*);
9600 FT_Error FT_Get_Kerning (FT_Face, FT_UInt, FT_UInt, FT_UInt, FT_Vector*);
9601 void FT_Outline_Get_CBox (const(FT_Outline)*, FT_BBox*);
9602 FT_Error FT_Outline_Decompose (FT_Outline*, const(FT_Outline_Funcs)*, void*);
9604 } else {
9605 import iv.freetype;
9608 struct FONSttFontImpl {
9609 FT_Face font;
9610 bool mono; // no aa?
9613 __gshared FT_Library ftLibrary = null;
9614 __gshared int ftLibraryInited = -1;
9616 int fons__tt_init (FONSContext context) nothrow @trusted @nogc {
9617 //FONS_NOTUSED(context);
9618 if (ftLibraryInited < 0) {
9619 FT_Error ftError;
9620 ftError = FT_Init_FreeType(&ftLibrary);
9621 ftLibraryInited = (ftError == 0);
9623 return ftLibraryInited;
9626 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc {
9627 font.mono = v;
9630 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc {
9631 return font.mono;
9634 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc {
9635 FT_Error ftError;
9636 //font.font.userdata = stash;
9637 ftError = FT_New_Memory_Face(ftLibrary, cast(const(FT_Byte)*)data, dataSize, 0, &font.font);
9638 return ftError == 0;
9641 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc {
9642 *ascent = font.font.ascender;
9643 *descent = font.font.descender;
9644 *lineGap = font.font.height-(*ascent - *descent);
9647 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc {
9648 version(nanovg_use_em_font_sizes) {
9649 return size/font.font.units_per_EM;
9650 } else {
9651 return size/(font.font.ascender-font.font.descender);
9655 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc {
9656 return FT_Get_Char_Index(font.font, codepoint);
9659 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 {
9660 FT_Error ftError;
9661 FT_GlyphSlot ftGlyph;
9662 //version(nanovg_ignore_mono) enum exflags = 0;
9663 //else version(nanovg_ft_mono) enum exflags = FT_LOAD_MONOCHROME; else enum exflags = 0;
9664 uint exflags = (font.mono ? FT_LOAD_MONOCHROME : 0);
9665 version(nanovg_use_em_font_sizes) {
9666 ftError = FT_Set_Pixel_Sizes(font.font, 0, cast(FT_UInt)size);
9667 } else {
9668 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)));
9670 if (ftError) return 0;
9671 ftError = FT_Load_Glyph(font.font, glyph, FT_LOAD_RENDER|/*FT_LOAD_NO_AUTOHINT|*/exflags);
9672 if (ftError) return 0;
9673 ftError = FT_Get_Advance(font.font, glyph, FT_LOAD_NO_SCALE|/*FT_LOAD_NO_AUTOHINT|*/exflags, cast(FT_Fixed*)advance);
9674 if (ftError) return 0;
9675 ftGlyph = font.font.glyph;
9676 *lsb = cast(int)ftGlyph.metrics.horiBearingX;
9677 *x0 = ftGlyph.bitmap_left;
9678 *x1 = *x0+ftGlyph.bitmap.width;
9679 *y0 = -ftGlyph.bitmap_top;
9680 *y1 = *y0+ftGlyph.bitmap.rows;
9681 return 1;
9684 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc {
9685 FT_GlyphSlot ftGlyph = font.font.glyph;
9686 //FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap
9687 //version(nanovg_ignore_mono) enum RenderAA = true;
9688 //else version(nanovg_ft_mono) enum RenderAA = false;
9689 //else enum RenderAA = true;
9690 if (font.mono) {
9691 auto src = ftGlyph.bitmap.buffer;
9692 auto dst = output;
9693 auto spt = ftGlyph.bitmap.pitch;
9694 if (spt < 0) spt = -spt;
9695 foreach (int y; 0..ftGlyph.bitmap.rows) {
9696 ubyte count = 0, b = 0;
9697 auto s = src;
9698 auto d = dst;
9699 foreach (int x; 0..ftGlyph.bitmap.width) {
9700 if (count-- == 0) { count = 7; b = *s++; } else b <<= 1;
9701 *d++ = (b&0x80 ? 255 : 0);
9703 src += spt;
9704 dst += outStride;
9706 } else {
9707 auto src = ftGlyph.bitmap.buffer;
9708 auto dst = output;
9709 auto spt = ftGlyph.bitmap.pitch;
9710 if (spt < 0) spt = -spt;
9711 foreach (int y; 0..ftGlyph.bitmap.rows) {
9712 import core.stdc.string : memcpy;
9713 //dst[0..ftGlyph.bitmap.width] = src[0..ftGlyph.bitmap.width];
9714 memcpy(dst, src, ftGlyph.bitmap.width);
9715 src += spt;
9716 dst += outStride;
9721 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc {
9722 FT_Vector ftKerning;
9723 version(none) {
9724 // fitted kerning
9725 FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning);
9726 //{ import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d\n", glyph1, glyph2, ftKerning.x, ftKerning.y); }
9727 return cast(int)ftKerning.x; // round up and convert to integer
9728 } else {
9729 // unfitted kerning
9730 //FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNFITTED, &ftKerning);
9731 if (glyph1 <= 0 || glyph2 <= 0 || (font.font.face_flags&FT_FACE_FLAG_KERNING) == 0) return 0;
9732 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;
9733 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning)) return 0;
9734 version(none) {
9735 if (ftKerning.x) {
9736 //{ import core.stdc.stdio : printf; printf("has kerning: %u\n", cast(uint)(font.font.face_flags&FT_FACE_FLAG_KERNING)); }
9737 { import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d (size=%g)\n", glyph1, glyph2, ftKerning.x, ftKerning.y, cast(double)size); }
9740 version(none) {
9741 FT_Vector kk;
9742 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNSCALED, &kk)) assert(0, "wtf?!");
9743 auto kadvfrac = FT_MulFix(kk.x, font.font.size.metrics.x_scale); // 1/64 of pixel
9744 //return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
9745 //assert(ftKerning.x == kadvfrac);
9746 if (ftKerning.x || kadvfrac) {
9747 { 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); }
9749 //return cast(int)(kadvfrac+(kadvfrac < 0 ? -31 : 32)>>6); // round up and convert to integer
9750 return kadvfrac/64.0f;
9752 //return cast(int)(ftKerning.x+(ftKerning.x < 0 ? -31 : 32)>>6); // round up and convert to integer
9753 return ftKerning.x/64.0f;
9757 extern(C) nothrow @trusted @nogc {
9758 static struct OutlinerData {
9759 @disable this (this);
9760 void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); }
9761 NVGContext vg;
9762 NVGPathOutline.DataStore* ol;
9763 FT_BBox outlineBBox;
9764 nothrow @trusted @nogc:
9765 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; }
9766 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
9769 int fons__nvg__moveto_cb (const(FT_Vector)* to, void* user) {
9770 auto odata = cast(OutlinerData*)user;
9771 if (odata.vg !is null) odata.vg.moveTo(odata.transx(to.x), odata.transy(to.y));
9772 if (odata.ol !is null) {
9773 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo);
9774 odata.ol.putArgs(odata.transx(to.x));
9775 odata.ol.putArgs(odata.transy(to.y));
9777 return 0;
9780 int fons__nvg__lineto_cb (const(FT_Vector)* to, void* user) {
9781 auto odata = cast(OutlinerData*)user;
9782 if (odata.vg !is null) odata.vg.lineTo(odata.transx(to.x), odata.transy(to.y));
9783 if (odata.ol !is null) {
9784 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo);
9785 odata.ol.putArgs(odata.transx(to.x));
9786 odata.ol.putArgs(odata.transy(to.y));
9788 return 0;
9791 int fons__nvg__quadto_cb (const(FT_Vector)* c1, const(FT_Vector)* to, void* user) {
9792 auto odata = cast(OutlinerData*)user;
9793 if (odata.vg !is null) odata.vg.quadTo(odata.transx(c1.x), odata.transy(c1.y), odata.transx(to.x), odata.transy(to.y));
9794 if (odata.ol !is null) {
9795 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo);
9796 odata.ol.putArgs(odata.transx(c1.x));
9797 odata.ol.putArgs(odata.transy(c1.y));
9798 odata.ol.putArgs(odata.transx(to.x));
9799 odata.ol.putArgs(odata.transy(to.y));
9801 return 0;
9804 int fons__nvg__cubicto_cb (const(FT_Vector)* c1, const(FT_Vector)* c2, const(FT_Vector)* to, void* user) {
9805 auto odata = cast(OutlinerData*)user;
9806 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));
9807 if (odata.ol !is null) {
9808 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo);
9809 odata.ol.putArgs(odata.transx(c1.x));
9810 odata.ol.putArgs(odata.transy(c1.y));
9811 odata.ol.putArgs(odata.transx(c2.x));
9812 odata.ol.putArgs(odata.transy(c2.y));
9813 odata.ol.putArgs(odata.transx(to.x));
9814 odata.ol.putArgs(odata.transy(to.y));
9816 return 0;
9820 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc {
9821 if (bounds.length > 4) bounds = bounds.ptr[0..4];
9823 FT_Outline_Funcs funcs;
9824 funcs.move_to = &fons__nvg__moveto_cb;
9825 funcs.line_to = &fons__nvg__lineto_cb;
9826 funcs.conic_to = &fons__nvg__quadto_cb;
9827 funcs.cubic_to = &fons__nvg__cubicto_cb;
9829 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
9830 if (err) { bounds[] = 0; return false; }
9831 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; }
9833 FT_Outline outline = font.font.glyph.outline;
9835 OutlinerData odata;
9836 odata.vg = vg;
9837 FT_Outline_Get_CBox(&outline, &odata.outlineBBox);
9839 err = FT_Outline_Decompose(&outline, &funcs, &odata);
9840 if (err) { bounds[] = 0; return false; }
9841 if (bounds.length > 0) bounds.ptr[0] = odata.outlineBBox.xMin;
9842 if (bounds.length > 1) bounds.ptr[1] = -odata.outlineBBox.yMax;
9843 if (bounds.length > 2) bounds.ptr[2] = odata.outlineBBox.xMax;
9844 if (bounds.length > 3) bounds.ptr[3] = -odata.outlineBBox.yMin;
9845 return true;
9848 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
9849 FT_Outline_Funcs funcs;
9850 funcs.move_to = &fons__nvg__moveto_cb;
9851 funcs.line_to = &fons__nvg__lineto_cb;
9852 funcs.conic_to = &fons__nvg__quadto_cb;
9853 funcs.cubic_to = &fons__nvg__cubicto_cb;
9855 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
9856 if (err) return false;
9857 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) return false;
9859 FT_Outline outline = font.font.glyph.outline;
9861 OutlinerData odata;
9862 odata.ol = ol;
9863 FT_Outline_Get_CBox(&outline, &odata.outlineBBox);
9865 err = FT_Outline_Decompose(&outline, &funcs, &odata);
9866 if (err) return false;
9867 ol.bounds.ptr[0] = odata.outlineBBox.xMin;
9868 ol.bounds.ptr[1] = -odata.outlineBBox.yMax;
9869 ol.bounds.ptr[2] = odata.outlineBBox.xMax;
9870 ol.bounds.ptr[3] = -odata.outlineBBox.yMin;
9871 return true;
9874 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc {
9875 if (bounds.length > 4) bounds = bounds.ptr[0..4];
9877 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
9878 if (err) return false;
9879 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; }
9881 FT_Outline outline = font.font.glyph.outline;
9882 FT_BBox outlineBBox;
9883 FT_Outline_Get_CBox(&outline, &outlineBBox);
9884 if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin;
9885 if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax;
9886 if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax;
9887 if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin;
9888 return true;
9892 } else {
9893 // ////////////////////////////////////////////////////////////////////////// //
9894 // sorry
9895 import std.traits : isFunctionPointer, isDelegate;
9896 private auto assumeNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) {
9897 import std.traits;
9898 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
9899 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
9902 private auto forceNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) {
9903 try {
9904 return assumeNoThrowNoGC(t)();
9905 } catch (Exception e) {
9906 assert(0, "OOPS!");
9910 struct FONSttFontImpl {
9911 stbtt_fontinfo font;
9912 bool mono; // no aa?
9915 int fons__tt_init (FONSContext context) nothrow @trusted @nogc {
9916 return 1;
9919 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc {
9920 font.mono = v;
9923 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc {
9924 return font.mono;
9927 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc {
9928 int stbError;
9929 font.font.userdata = context;
9930 forceNoThrowNoGC({ stbError = stbtt_InitFont(&font.font, data, 0); });
9931 return stbError;
9934 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc {
9935 forceNoThrowNoGC({ stbtt_GetFontVMetrics(&font.font, ascent, descent, lineGap); });
9938 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc {
9939 float res = void;
9940 version(nanovg_use_em_font_sizes) {
9941 forceNoThrowNoGC({ res = stbtt_ScaleForMappingEmToPixels(&font.font, size); });
9942 } else {
9943 forceNoThrowNoGC({ res = stbtt_ScaleForPixelHeight(&font.font, size); });
9945 return res;
9948 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc {
9949 int res;
9950 forceNoThrowNoGC({ res = stbtt_FindGlyphIndex(&font.font, codepoint); });
9951 return res;
9954 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 {
9955 forceNoThrowNoGC({ stbtt_GetGlyphHMetrics(&font.font, glyph, advance, lsb); });
9956 forceNoThrowNoGC({ stbtt_GetGlyphBitmapBox(&font.font, glyph, scale, scale, x0, y0, x1, y1); });
9957 return 1;
9960 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc {
9961 forceNoThrowNoGC({ stbtt_MakeGlyphBitmap(&font.font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); });
9964 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc {
9965 // FUnits -> pixels: pointSize * resolution / (72 points per inch * units_per_em)
9966 // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#converting
9967 float res = void;
9968 forceNoThrowNoGC({
9969 res = stbtt_GetGlyphKernAdvance(&font.font, glyph1, glyph2);
9970 res *= stbtt_ScaleForPixelHeight(&font.font, size);
9973 if (res != 0) {
9974 { 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)); }
9977 //k8: dunno if this is right; i guess it isn't but...
9978 return res;
9981 // old arsd.ttf sux! ;-)
9982 static if (is(typeof(STBTT_vcubic))) {
9984 static struct OutlinerData {
9985 @disable this (this);
9986 void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); }
9987 NVGPathOutline.DataStore* ol;
9988 nothrow @trusted @nogc:
9989 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; }
9990 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
9994 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc {
9995 if (bounds.length > 4) bounds = bounds.ptr[0..4];
9997 bool okflag = false;
9999 forceNoThrowNoGC({
10000 int x0, y0, x1, y1;
10001 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10002 bounds[] = 0;
10003 return;
10006 if (bounds.length > 0) bounds.ptr[0] = x0;
10007 if (bounds.length > 1) bounds.ptr[1] = -y1;
10008 if (bounds.length > 2) bounds.ptr[2] = x1;
10009 if (bounds.length > 3) bounds.ptr[3] = -y0;
10011 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
10013 stbtt_vertex* verts = null;
10014 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); }
10015 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts);
10016 if (vcount < 1) return;
10018 foreach (const ref vt; verts[0..vcount]) {
10019 switch (vt.type) {
10020 case STBTT_vmove: vg.moveTo(vt.x, transy(vt.y)); break;
10021 case STBTT_vline: vg.lineTo(vt.x, transy(vt.y)); break;
10022 case STBTT_vcurve: vg.quadTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy)); break;
10023 case STBTT_vcubic: vg.bezierTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy), vt.cx1, transy(vt.cy1)); break;
10024 default:
10028 okflag = true;
10031 return okflag;
10034 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
10035 bool okflag = false;
10037 forceNoThrowNoGC({
10038 int x0, y0, x1, y1;
10040 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10041 ol.bounds[] = 0;
10042 return;
10045 ol.bounds.ptr[0] = x0;
10046 ol.bounds.ptr[1] = -y1;
10047 ol.bounds.ptr[2] = x1;
10048 ol.bounds.ptr[3] = -y0;
10050 stbtt_vertex* verts = null;
10051 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); }
10052 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts);
10053 if (vcount < 1) return;
10055 OutlinerData odata;
10056 odata.ol = ol;
10058 foreach (const ref vt; verts[0..vcount]) {
10059 switch (vt.type) {
10060 case STBTT_vmove:
10061 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo);
10062 odata.ol.putArgs(odata.transx(vt.x));
10063 odata.ol.putArgs(odata.transy(vt.y));
10064 break;
10065 case STBTT_vline:
10066 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo);
10067 odata.ol.putArgs(odata.transx(vt.x));
10068 odata.ol.putArgs(odata.transy(vt.y));
10069 break;
10070 case STBTT_vcurve:
10071 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo);
10072 odata.ol.putArgs(odata.transx(vt.x));
10073 odata.ol.putArgs(odata.transy(vt.y));
10074 odata.ol.putArgs(odata.transx(vt.cx));
10075 odata.ol.putArgs(odata.transy(vt.cy));
10076 break;
10077 case STBTT_vcubic:
10078 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo);
10079 odata.ol.putArgs(odata.transx(vt.x));
10080 odata.ol.putArgs(odata.transy(vt.y));
10081 odata.ol.putArgs(odata.transx(vt.cx));
10082 odata.ol.putArgs(odata.transy(vt.cy));
10083 odata.ol.putArgs(odata.transx(vt.cx1));
10084 odata.ol.putArgs(odata.transy(vt.cy1));
10085 break;
10086 default:
10090 okflag = true;
10093 return okflag;
10096 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc {
10097 if (bounds.length > 4) bounds = bounds.ptr[0..4];
10099 bool okflag = false;
10101 forceNoThrowNoGC({
10102 int x0, y0, x1, y1;
10103 if (stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10104 if (bounds.length > 0) bounds.ptr[0] = x0;
10105 if (bounds.length > 1) bounds.ptr[1] = -y1;
10106 if (bounds.length > 2) bounds.ptr[2] = x1;
10107 if (bounds.length > 3) bounds.ptr[3] = -y0;
10108 okflag = true;
10109 } else {
10110 bounds[] = 0;
10114 return okflag;
10117 } // check for old stb_ttf
10120 } // version
10123 // ////////////////////////////////////////////////////////////////////////// //
10124 private:
10125 enum FONS_HASH_LUT_SIZE = 256;
10126 enum FONS_INIT_FONTS = 4;
10127 enum FONS_INIT_GLYPHS = 256;
10128 enum FONS_INIT_ATLAS_NODES = 256;
10129 enum FONS_VERTEX_COUNT = 1024;
10130 enum FONS_MAX_STATES = 20;
10131 enum FONS_MAX_FALLBACKS = 20;
10134 struct FONSglyph {
10135 uint codepoint;
10136 int index;
10137 int next;
10138 short size, blur;
10139 short x0, y0, x1, y1;
10140 short xadv, xoff, yoff;
10143 // refcounted
10144 struct FONSfontData {
10145 ubyte* data;
10146 int dataSize;
10147 bool freeData;
10148 int rc;
10150 @disable this (this); // no copies
10151 void opAssign() (in auto ref FONSfontData a) { static assert(0, "no copies!"); }
10154 // won't set rc to 1
10155 FONSfontData* fons__createFontData (ubyte* adata, int asize, bool afree) nothrow @trusted @nogc {
10156 import core.stdc.stdlib : malloc;
10157 assert(adata !is null);
10158 assert(asize > 0);
10159 auto res = cast(FONSfontData*)malloc(FONSfontData.sizeof);
10160 if (res is null) assert(0, "FONS: out of memory");
10161 res.data = adata;
10162 res.dataSize = asize;
10163 res.freeData = afree;
10164 res.rc = 0;
10165 return res;
10168 void incref (FONSfontData* fd) pure nothrow @trusted @nogc {
10169 pragma(inline, true);
10170 if (fd !is null) ++fd.rc;
10173 void decref (ref FONSfontData* fd) nothrow @trusted @nogc {
10174 if (fd !is null) {
10175 if (--fd.rc == 0) {
10176 import core.stdc.stdlib : free;
10177 if (fd.freeData && fd.data !is null) {
10178 free(fd.data);
10179 fd.data = null;
10181 free(fd);
10182 fd = null;
10187 // as creating and destroying fonts is a rare operation, malloc some data
10188 struct FONSfont {
10189 FONSttFontImpl font;
10190 char* name; // malloced, strz, always lowercase
10191 uint namelen;
10192 uint namehash;
10193 char* path; // malloced, strz
10194 FONSfontData* fdata;
10195 float ascender;
10196 float descender;
10197 float lineh;
10198 FONSglyph* glyphs;
10199 int cglyphs;
10200 int nglyphs;
10201 int[FONS_HASH_LUT_SIZE] lut;
10202 int[FONS_MAX_FALLBACKS] fallbacks;
10203 int nfallbacks;
10205 @disable this (this);
10206 void opAssign() (in auto ref FONSfont a) { static assert(0, "no copies"); }
10208 static uint djbhash (const(void)[] s) pure nothrow @safe @nogc {
10209 uint hash = 5381;
10210 foreach (ubyte b; cast(const(ubyte)[])s) {
10211 if (b >= 'A' && b <= 'Z') b += 32; // poor man's tolower
10212 hash = ((hash<<5)+hash)+b;
10214 return hash;
10217 // except glyphs
10218 void freeMemory () nothrow @trusted @nogc {
10219 import core.stdc.stdlib : free;
10220 if (name !is null) { free(name); name = null; }
10221 namelen = namehash = 0;
10222 if (path !is null) { free(path); path = null; }
10223 fdata.decref();
10226 // this also calcs name hash
10227 void setName (const(char)[] aname) nothrow @trusted @nogc {
10228 //{ import core.stdc.stdio; printf("setname: [%.*s]\n", cast(uint)aname.length, aname.ptr); }
10229 import core.stdc.stdlib : realloc;
10230 if (aname.length > int.max/32) assert(0, "FONS: invalid font name");
10231 namelen = cast(uint)aname.length;
10232 name = cast(char*)realloc(name, namelen+1);
10233 if (name is null) assert(0, "FONS: out of memory");
10234 if (aname.length) name[0..aname.length] = aname[];
10235 name[namelen] = 0;
10236 // lowercase it
10237 foreach (ref char ch; name[0..namelen]) if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
10238 namehash = djbhash(name[0..namelen]);
10239 //{ import core.stdc.stdio; printf(" [%s] [%.*s] [0x%08x]\n", name, namelen, name, namehash); }
10242 void setPath (const(char)[] apath) nothrow @trusted @nogc {
10243 import core.stdc.stdlib : realloc;
10244 if (apath.length > int.max/32) assert(0, "FONS: invalid font path");
10245 path = cast(char*)realloc(path, apath.length+1);
10246 if (path is null) assert(0, "FONS: out of memory");
10247 if (apath.length) path[0..apath.length] = apath[];
10248 path[apath.length] = 0;
10251 // this won't check hash
10252 bool nameEqu (const(char)[] aname) const pure nothrow @trusted @nogc {
10253 //{ import core.stdc.stdio; printf("nameEqu: aname=[%.*s]; namelen=%u; aslen=%u\n", cast(uint)aname.length, aname.ptr, namelen, cast(uint)aname.length); }
10254 if (namelen != aname.length) return false;
10255 const(char)* ns = name;
10256 // name part
10257 foreach (char ch; aname) {
10258 if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
10259 if (ch != *ns++) return false;
10261 // done (length was checked earlier)
10262 return true;
10265 void clear () nothrow @trusted @nogc {
10266 import core.stdc.stdlib : free;
10267 import core.stdc.string : memset;
10268 if (glyphs !is null) free(glyphs);
10269 freeMemory();
10270 memset(&this, 0, this.sizeof);
10273 FONSglyph* allocGlyph () nothrow @trusted @nogc {
10274 if (nglyphs+1 > cglyphs) {
10275 import core.stdc.stdlib : realloc;
10276 cglyphs = (cglyphs == 0 ? 8 : cglyphs*2);
10277 glyphs = cast(FONSglyph*)realloc(glyphs, FONSglyph.sizeof*cglyphs);
10278 if (glyphs is null) assert(0, "FontStash: out of memory");
10280 ++nglyphs;
10281 return &glyphs[nglyphs-1];
10285 void kill (ref FONSfont* font) nothrow @trusted @nogc {
10286 if (font !is null) {
10287 import core.stdc.stdlib : free;
10288 font.clear();
10289 free(font);
10290 font = null;
10295 // ////////////////////////////////////////////////////////////////////////// //
10296 struct FONSstate {
10297 int font;
10298 NVGTextAlign talign;
10299 float size = 0;
10300 float blur = 0;
10301 float spacing = 0;
10305 // ////////////////////////////////////////////////////////////////////////// //
10306 // atlas based on Skyline Bin Packer by Jukka Jylänki
10307 alias FONSAtlas = FONSatlasInternal*;
10309 struct FONSatlasInternal {
10310 static struct Node {
10311 short x, y, width;
10314 int width, height;
10315 Node* nodes;
10316 int nnodes;
10317 int cnodes;
10319 @disable this (this);
10320 void opAssign() (in auto ref FONSatlasInternal a) { static assert(0, "no copies"); }
10322 nothrow @trusted @nogc:
10323 static FONSAtlas create (int w, int h, int nnodes) {
10324 import core.stdc.stdlib : malloc;
10325 import core.stdc.string : memset;
10327 FONSAtlas atlas = cast(FONSAtlas)malloc(FONSatlasInternal.sizeof);
10328 if (atlas is null) assert(0, "FontStash: out of memory");
10329 memset(atlas, 0, FONSatlasInternal.sizeof);
10331 atlas.width = w;
10332 atlas.height = h;
10334 // allocate space for skyline nodes
10335 atlas.nodes = cast(Node*)malloc(Node.sizeof*nnodes);
10336 if (atlas.nodes is null) assert(0, "FontStash: out of memory");
10337 memset(atlas.nodes, 0, Node.sizeof*nnodes);
10338 atlas.nnodes = 0;
10339 atlas.cnodes = nnodes;
10341 // init root node
10342 atlas.nodes[0].x = 0;
10343 atlas.nodes[0].y = 0;
10344 atlas.nodes[0].width = cast(short)w;
10345 ++atlas.nnodes;
10347 return atlas;
10350 void clear () {
10351 import core.stdc.stdlib : free;
10352 import core.stdc.string : memset;
10354 if (nodes !is null) free(nodes);
10355 memset(&this, 0, this.sizeof);
10358 void insertNode (int idx, int x, int y, int w) {
10359 if (nnodes+1 > cnodes) {
10360 import core.stdc.stdlib : realloc;
10361 cnodes = (cnodes == 0 ? 8 : cnodes*2);
10362 nodes = cast(Node*)realloc(nodes, Node.sizeof*cnodes);
10363 if (nodes is null) assert(0, "FontStash: out of memory");
10365 for (int i = nnodes; i > idx; --i) nodes[i] = nodes[i-1];
10366 nodes[idx].x = cast(short)x;
10367 nodes[idx].y = cast(short)y;
10368 nodes[idx].width = cast(short)w;
10369 ++nnodes;
10372 void removeNode (int idx) {
10373 if (nnodes == 0) return;
10374 foreach (immutable int i; idx+1..nnodes) nodes[i-1] = nodes[i];
10375 --nnodes;
10378 // insert node for empty space
10379 void expand (int w, int h) {
10380 if (w > width) insertNode(nnodes, width, 0, w-width);
10381 width = w;
10382 height = h;
10385 void reset (int w, int h) {
10386 width = w;
10387 height = h;
10388 nnodes = 0;
10389 // init root node
10390 nodes[0].x = 0;
10391 nodes[0].y = 0;
10392 nodes[0].width = cast(short)w;
10393 ++nnodes;
10396 void addSkylineLevel (int idx, int x, int y, int w, int h) {
10397 insertNode(idx, x, y+h, w);
10399 // delete skyline segments that fall under the shadow of the new segment
10400 for (int i = idx+1; i < nnodes; ++i) {
10401 if (nodes[i].x < nodes[i-1].x+nodes[i-1].width) {
10402 int shrink = nodes[i-1].x+nodes[i-1].width-nodes[i].x;
10403 nodes[i].x += cast(short)shrink;
10404 nodes[i].width -= cast(short)shrink;
10405 if (nodes[i].width <= 0) {
10406 removeNode(i);
10407 --i;
10408 } else {
10409 break;
10411 } else {
10412 break;
10416 // Merge same height skyline segments that are next to each other
10417 for (int i = 0; i < nnodes-1; ++i) {
10418 if (nodes[i].y == nodes[i+1].y) {
10419 nodes[i].width += nodes[i+1].width;
10420 removeNode(i+1);
10421 --i;
10426 // checks if there is enough space at the location of skyline span 'i',
10427 // and return the max height of all skyline spans under that at that location,
10428 // (think tetris block being dropped at that position); or -1 if no space found
10429 int rectFits (int i, int w, int h) {
10430 int x = nodes[i].x;
10431 int y = nodes[i].y;
10432 if (x+w > width) return -1;
10433 int spaceLeft = w;
10434 while (spaceLeft > 0) {
10435 if (i == nnodes) return -1;
10436 y = nvg__max(y, nodes[i].y);
10437 if (y+h > height) return -1;
10438 spaceLeft -= nodes[i].width;
10439 ++i;
10441 return y;
10444 bool addRect (int rw, int rh, int* rx, int* ry) {
10445 int besth = height, bestw = width, besti = -1;
10446 int bestx = -1, besty = -1;
10448 // Bottom left fit heuristic.
10449 for (int i = 0; i < nnodes; ++i) {
10450 int y = rectFits(i, rw, rh);
10451 if (y != -1) {
10452 if (y+rh < besth || (y+rh == besth && nodes[i].width < bestw)) {
10453 besti = i;
10454 bestw = nodes[i].width;
10455 besth = y+rh;
10456 bestx = nodes[i].x;
10457 besty = y;
10462 if (besti == -1) return false;
10464 // perform the actual packing
10465 addSkylineLevel(besti, bestx, besty, rw, rh);
10467 *rx = bestx;
10468 *ry = besty;
10470 return true;
10474 void kill (ref FONSAtlas atlas) nothrow @trusted @nogc {
10475 if (atlas !is null) {
10476 import core.stdc.stdlib : free;
10477 atlas.clear();
10478 free(atlas);
10479 atlas = null;
10484 // ////////////////////////////////////////////////////////////////////////// //
10485 /// FontStash context (internal definition). Don't use it derectly, it was made public only to generate documentation.
10486 /// Group: font_stash
10487 public struct FONScontextInternal {
10488 private:
10489 FONSParams params;
10490 float itw, ith;
10491 ubyte* texData;
10492 int[4] dirtyRect;
10493 FONSfont** fonts; // actually, a simple hash table; can't grow yet
10494 int cfonts; // allocated
10495 int nfonts; // used (so we can track hash table stats)
10496 int* hashidx; // [hsize] items; holds indicies in [fonts] array
10497 int hused, hsize;// used items and total items in [hashidx]
10498 FONSAtlas atlas;
10499 FONSstate[FONS_MAX_STATES] states;
10500 int nstates;
10502 void delegate (FONSError error, int val) nothrow @trusted @nogc handleError;
10504 @disable this (this);
10505 void opAssign() (in auto ref FONScontextInternal ctx) { static assert(0, "FONS copying is not allowed"); }
10507 private:
10508 static bool strequci (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
10509 if (s0.length != s1.length) return false;
10510 const(char)* sp0 = s0.ptr;
10511 const(char)* sp1 = s1.ptr;
10512 foreach (immutable _; 0..s0.length) {
10513 char c0 = *sp0++;
10514 char c1 = *sp1++;
10515 if (c0 != c1) {
10516 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man tolower
10517 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man tolower
10518 if (c0 != c1) return false;
10521 return true;
10524 inout(FONSstate)* getState () inout pure nothrow @trusted @nogc {
10525 pragma(inline, true);
10526 return cast(inout)(&states[(nstates > 0 ? nstates-1 : 0)]);
10529 // simple linear probing; returns [FONS_INVALID] if not found
10530 int findNameInHash (const(char)[] name) const pure nothrow @trusted @nogc {
10531 if (nfonts == 0) return FONS_INVALID;
10532 auto nhash = FONSfont.djbhash(name);
10533 //{ import core.stdc.stdio; printf("findinhash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10534 auto res = nhash%hsize;
10535 // hash will never be 100% full, so this loop is safe
10536 for (;;) {
10537 int idx = hashidx[res];
10538 if (idx == -1) break;
10539 auto font = fonts[idx];
10540 if (font is null) assert(0, "FONS internal error");
10541 if (font.namehash == nhash && font.nameEqu(name)) return idx;
10542 //{ import core.stdc.stdio; printf("findinhash chained: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10543 res = (res+1)%hsize;
10545 return FONS_INVALID;
10548 // should be called $(B before) freeing `fonts[fidx]`
10549 void removeIndexFromHash (int fidx) nothrow @trusted @nogc {
10550 if (fidx < 0 || fidx >= nfonts) assert(0, "FONS internal error");
10551 if (fonts[fidx] is null) assert(0, "FONS internal error");
10552 if (hused != nfonts) assert(0, "FONS internal error");
10553 auto nhash = fonts[fidx].namehash;
10554 auto res = nhash%hsize;
10555 // hash will never be 100% full, so this loop is safe
10556 for (;;) {
10557 int idx = hashidx[res];
10558 if (idx == -1) assert(0, "FONS INTERNAL ERROR");
10559 if (idx == fidx) {
10560 // i found her! copy rest here
10561 int nidx = (res+1)%hsize;
10562 for (;;) {
10563 if ((hashidx[res] = hashidx[nidx]) == -1) break; // so it will copy `-1` too
10564 res = nidx;
10565 nidx = (nidx+1)%hsize;
10567 return;
10569 res = (res+1)%hsize;
10573 // add font with the given index to hash
10574 // prerequisite: font should not exists in hash
10575 void addIndexToHash (int idx) nothrow @trusted @nogc {
10576 if (idx < 0 || idx >= nfonts) assert(0, "FONS internal error");
10577 if (fonts[idx] is null) assert(0, "FONS internal error");
10578 import core.stdc.stdlib : realloc;
10579 auto nhash = fonts[idx].namehash;
10580 //{ import core.stdc.stdio; printf("addtohash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10581 // allocate new hash table if there was none
10582 if (hsize == 0) {
10583 enum InitSize = 256;
10584 auto newlist = cast(int*)realloc(null, InitSize*hashidx[0].sizeof);
10585 if (newlist is null) assert(0, "FONS: out of memory");
10586 newlist[0..InitSize] = -1;
10587 hsize = InitSize;
10588 hused = 0;
10589 hashidx = newlist;
10591 int res = cast(int)(nhash%hsize);
10592 // need to rehash? we want our hash table 50% full at max
10593 if (hashidx[res] != -1 && hused >= hsize/2) {
10594 uint nsz = hsize*2;
10595 if (nsz > 1024*1024) assert(0, "FONS: out of memory for fonts");
10596 auto newlist = cast(int*)realloc(fonts, nsz*hashidx[0].sizeof);
10597 if (newlist is null) assert(0, "FONS: out of memory");
10598 newlist[0..nsz] = -1;
10599 hused = 0;
10600 // rehash
10601 foreach (immutable fidx, FONSfont* ff; fonts[0..nfonts]) {
10602 if (ff is null) continue;
10603 // find slot for this font (guaranteed to have one)
10604 uint newslot = ff.namehash%nsz;
10605 while (newlist[newslot] != -1) newslot = (newslot+1)%nsz;
10606 newlist[newslot] = cast(int)fidx;
10607 ++hused;
10609 hsize = nsz;
10610 hashidx = newlist;
10611 // we added everything, including [idx], so nothing more to do here
10612 } else {
10613 // find slot (guaranteed to have one)
10614 while (hashidx[res] != -1) res = (res+1)%hsize;
10615 // i found her!
10616 hashidx[res] = idx;
10617 ++hused;
10621 void addWhiteRect (int w, int h) nothrow @trusted @nogc {
10622 int gx, gy;
10623 ubyte* dst;
10625 if (!atlas.addRect(w, h, &gx, &gy)) return;
10627 // Rasterize
10628 dst = &texData[gx+gy*params.width];
10629 foreach (int y; 0..h) {
10630 foreach (int x; 0..w) {
10631 dst[x] = 0xff;
10633 dst += params.width;
10636 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], gx);
10637 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], gy);
10638 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], gx+w);
10639 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], gy+h);
10642 // returns fid, not hash slot
10643 int allocFontAt (int atidx) nothrow @trusted @nogc {
10644 if (atidx >= 0 && atidx >= nfonts) assert(0, "internal NanoVega fontstash error");
10646 if (atidx < 0) {
10647 if (nfonts >= cfonts) {
10648 import core.stdc.stdlib : realloc;
10649 import core.stdc.string : memset;
10650 assert(nfonts == cfonts);
10651 int newsz = cfonts+64;
10652 if (newsz > 65535) assert(0, "FONS: too many fonts");
10653 auto newlist = cast(FONSfont**)realloc(fonts, newsz*(FONSfont*).sizeof);
10654 if (newlist is null) assert(0, "FONS: out of memory");
10655 memset(newlist+cfonts, 0, (newsz-cfonts)*(FONSfont*).sizeof);
10656 fonts = newlist;
10657 cfonts = newsz;
10659 assert(nfonts < cfonts);
10662 FONSfont* font = cast(FONSfont*)malloc(FONSfont.sizeof);
10663 if (font is null) assert(0, "FONS: out of memory");
10664 memset(font, 0, FONSfont.sizeof);
10666 font.glyphs = cast(FONSglyph*)malloc(FONSglyph.sizeof*FONS_INIT_GLYPHS);
10667 if (font.glyphs is null) assert(0, "FONS: out of memory");
10668 font.cglyphs = FONS_INIT_GLYPHS;
10669 font.nglyphs = 0;
10671 if (atidx < 0) {
10672 fonts[nfonts] = font;
10673 return nfonts++;
10674 } else {
10675 fonts[atidx] = font;
10676 return atidx;
10680 // 0: ooops
10681 int findGlyphForCP (FONSfont *font, dchar dch, FONSfont** renderfont) nothrow @trusted @nogc {
10682 if (renderfont !is null) *renderfont = font;
10683 if (font is null || font.fdata is null) return 0;
10684 auto g = fons__tt_getGlyphIndex(&font.font, cast(uint)dch);
10685 // try to find the glyph in fallback fonts
10686 if (g == 0) {
10687 foreach (immutable i; 0..font.nfallbacks) {
10688 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]];
10689 if (fallbackFont !is null) {
10690 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, cast(uint)dch);
10691 if (fallbackIndex != 0) {
10692 if (renderfont !is null) *renderfont = fallbackFont;
10693 return g;
10697 // no char, try to find replacement one
10698 if (dch != 0xFFFD) {
10699 g = fons__tt_getGlyphIndex(&font.font, 0xFFFD);
10700 if (g == 0) {
10701 foreach (immutable i; 0..font.nfallbacks) {
10702 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]];
10703 if (fallbackFont !is null) {
10704 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, 0xFFFD);
10705 if (fallbackIndex != 0) {
10706 if (renderfont !is null) *renderfont = fallbackFont;
10707 return g;
10714 return g;
10717 void clear () nothrow @trusted @nogc {
10718 import core.stdc.stdlib : free;
10720 if (params.renderDelete !is null) params.renderDelete(params.userPtr);
10721 foreach (immutable int i; 0..nfonts) fonts[i].kill();
10723 if (atlas !is null) atlas.kill();
10724 if (fonts !is null) free(fonts);
10725 if (texData !is null) free(texData);
10726 if (hashidx !is null) free(hashidx);
10729 // add font from another fontstash
10730 int addCookedFont (FONSfont* font) nothrow @trusted @nogc {
10731 if (font is null || font.fdata is null) return FONS_INVALID;
10732 font.fdata.incref();
10733 auto res = addFontWithData(font.name[0..font.namelen], font.fdata, !font.font.mono);
10734 if (res == FONS_INVALID) font.fdata.decref(); // oops
10735 return res;
10738 // fdata refcount must be already increased; it won't be changed
10739 int addFontWithData (const(char)[] name, FONSfontData* fdata, bool defAA) nothrow @trusted @nogc {
10740 int i, ascent, descent, fh, lineGap;
10742 if (name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID;
10743 if (name.length > 32767) return FONS_INVALID;
10744 if (fdata is null) return FONS_INVALID;
10746 // find a font with the given name
10747 int newidx;
10748 FONSfont* oldfont = null;
10749 int oldidx = findNameInHash(name);
10750 if (oldidx != FONS_INVALID) {
10751 // replacement font
10752 oldfont = fonts[oldidx];
10753 newidx = oldidx;
10754 } else {
10755 // new font, allocate new bucket
10756 newidx = -1;
10759 newidx = allocFontAt(newidx);
10760 FONSfont* font = fonts[newidx];
10761 font.setName(name);
10762 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; // init hash lookup
10763 font.fdata = fdata; // set the font data (don't change reference count)
10764 fons__tt_setMono(&this, &font.font, !defAA);
10766 // init font
10767 if (!fons__tt_loadFont(&this, &font.font, fdata.data, fdata.dataSize)) {
10768 // we promised to not free data on error, so just clear the data store (it will be freed by the caller)
10769 font.fdata = null;
10770 font.kill();
10771 if (oldidx != FONS_INVALID) {
10772 assert(oldidx == newidx);
10773 fonts[oldidx] = oldfont;
10774 } else {
10775 assert(newidx == nfonts-1);
10776 fonts[newidx] = null;
10777 --nfonts;
10779 return FONS_INVALID;
10780 } else {
10781 // free old font data, if any
10782 if (oldfont !is null) oldfont.kill();
10785 // add font to name hash
10786 if (oldidx == FONS_INVALID) addIndexToHash(newidx);
10788 // store normalized line height
10789 // the real line height is got by multiplying the lineh by font size
10790 fons__tt_getFontVMetrics(&font.font, &ascent, &descent, &lineGap);
10791 fh = ascent-descent;
10792 font.ascender = cast(float)ascent/cast(float)fh;
10793 font.descender = cast(float)descent/cast(float)fh;
10794 font.lineh = cast(float)(fh+lineGap)/cast(float)fh;
10796 //{ import core.stdc.stdio; printf("created font [%.*s] (idx=%d)...\n", cast(uint)name.length, name.ptr, idx); }
10797 return newidx;
10800 // isize: size*10
10801 float getVertAlign (FONSfont* font, NVGTextAlign talign, short isize) pure nothrow @trusted @nogc {
10802 if (params.isZeroTopLeft) {
10803 final switch (talign.vertical) {
10804 case NVGTextAlign.V.Top: return font.ascender*cast(float)isize/10.0f;
10805 case NVGTextAlign.V.Middle: return (font.ascender+font.descender)/2.0f*cast(float)isize/10.0f;
10806 case NVGTextAlign.V.Baseline: return 0.0f;
10807 case NVGTextAlign.V.Bottom: return font.descender*cast(float)isize/10.0f;
10809 } else {
10810 final switch (talign.vertical) {
10811 case NVGTextAlign.V.Top: return -font.ascender*cast(float)isize/10.0f;
10812 case NVGTextAlign.V.Middle: return -(font.ascender+font.descender)/2.0f*cast(float)isize/10.0f;
10813 case NVGTextAlign.V.Baseline: return 0.0f;
10814 case NVGTextAlign.V.Bottom: return -font.descender*cast(float)isize/10.0f;
10817 assert(0);
10820 public:
10821 /** Create new FontStash context. It can be destroyed with `fs.kill()` later.
10823 * Note that if you don't plan to rasterize glyphs (i.e. you will use created
10824 * FontStash only to measure text), you can simply pass `FONSParams.init`).
10826 static FONSContext create() (in auto ref FONSParams params) nothrow @trusted @nogc {
10827 import core.stdc.string : memcpy;
10829 FONSContext stash = null;
10831 // allocate memory for the font stash
10832 stash = cast(FONSContext)malloc(FONScontextInternal.sizeof);
10833 if (stash is null) goto error;
10834 memset(stash, 0, FONScontextInternal.sizeof);
10836 memcpy(&stash.params, &params, params.sizeof);
10837 if (stash.params.width < 1) stash.params.width = 32;
10838 if (stash.params.height < 1) stash.params.height = 32;
10840 // initialize implementation library
10841 if (!fons__tt_init(stash)) goto error;
10843 if (stash.params.renderCreate !is null) {
10844 if (!stash.params.renderCreate(stash.params.userPtr, stash.params.width, stash.params.height)) goto error;
10847 stash.atlas = FONSAtlas.create(stash.params.width, stash.params.height, FONS_INIT_ATLAS_NODES);
10848 if (stash.atlas is null) goto error;
10850 // don't allocate space for fonts: hash manager will do that for us later
10851 //stash.cfonts = 0;
10852 //stash.nfonts = 0;
10854 // create texture for the cache
10855 stash.itw = 1.0f/stash.params.width;
10856 stash.ith = 1.0f/stash.params.height;
10857 stash.texData = cast(ubyte*)malloc(stash.params.width*stash.params.height);
10858 if (stash.texData is null) goto error;
10859 memset(stash.texData, 0, stash.params.width*stash.params.height);
10861 stash.dirtyRect.ptr[0] = stash.params.width;
10862 stash.dirtyRect.ptr[1] = stash.params.height;
10863 stash.dirtyRect.ptr[2] = 0;
10864 stash.dirtyRect.ptr[3] = 0;
10866 // add white rect at 0, 0 for debug drawing
10867 stash.addWhiteRect(2, 2);
10869 stash.pushState();
10870 stash.clearState();
10872 return stash;
10874 error:
10875 stash.kill();
10876 return null;
10879 public:
10880 /// Add fallback font (FontStash will try to find missing glyph in all fallback fonts before giving up).
10881 bool addFallbackFont (int base, int fallback) nothrow @trusted @nogc {
10882 FONSfont* baseFont = fonts[base];
10883 if (baseFont !is null && baseFont.nfallbacks < FONS_MAX_FALLBACKS) {
10884 baseFont.fallbacks.ptr[baseFont.nfallbacks++] = fallback;
10885 return true;
10887 return false;
10890 @property void size (float size) nothrow @trusted @nogc { pragma(inline, true); getState.size = size; } /// Set current font size.
10891 @property float size () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.size; } /// Get current font size.
10893 @property void spacing (float spacing) nothrow @trusted @nogc { pragma(inline, true); getState.spacing = spacing; } /// Set current letter spacing.
10894 @property float spacing () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.spacing; } /// Get current letter spacing.
10896 @property void blur (float blur) nothrow @trusted @nogc { pragma(inline, true); getState.blur = blur; } /// Set current letter blur.
10897 @property float blur () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.blur; } /// Get current letter blur.
10899 @property void textAlign (NVGTextAlign talign) nothrow @trusted @nogc { pragma(inline, true); getState.talign = talign; } /// Set current text align.
10900 @property NVGTextAlign textAlign () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.talign; } /// Get current text align.
10902 @property void fontId (int font) nothrow @trusted @nogc { pragma(inline, true); getState.font = font; } /// Set current font id.
10903 @property int fontId () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.font; } /// Get current font id.
10905 @property void fontId (const(char)[] name) nothrow @trusted @nogc { pragma(inline, true); getState.font = getFontByName(name); } /// Set current font using its name.
10907 /// Check if FontStash has a font with the given name loaded.
10908 bool hasFont (const(char)[] name) const pure nothrow @trusted @nogc { pragma(inline, true); return (getFontByName(name) >= 0); }
10910 /// Get AA for the current font, or for the specified font.
10911 bool getFontAA (int font=-1) nothrow @trusted @nogc {
10912 FONSstate* state = getState;
10913 if (font < 0) font = state.font;
10914 if (font < 0 || font >= nfonts) return false;
10915 FONSfont* f = fonts[font];
10916 return (f !is null ? !f.font.mono : false);
10919 /// Push current state. Returns `false` if state stack overflowed.
10920 bool pushState () nothrow @trusted @nogc {
10921 if (nstates >= FONS_MAX_STATES) {
10922 if (handleError !is null) handleError(FONSError.StatesOverflow, 0);
10923 return false;
10925 if (nstates > 0) {
10926 import core.stdc.string : memcpy;
10927 memcpy(&states[nstates], &states[nstates-1], FONSstate.sizeof);
10929 ++nstates;
10930 return true;
10933 /// Pop current state. Returns `false` if state stack underflowed.
10934 bool popState () nothrow @trusted @nogc {
10935 if (nstates <= 1) {
10936 if (handleError !is null) handleError(FONSError.StatesUnderflow, 0);
10937 return false;
10939 --nstates;
10940 return true;
10943 /// Clear current state (i.e. set it to some sane defaults).
10944 void clearState () nothrow @trusted @nogc {
10945 FONSstate* state = getState;
10946 state.size = 12.0f;
10947 state.font = 0;
10948 state.blur = 0;
10949 state.spacing = 0;
10950 state.talign.reset;
10953 private enum NoAlias = ":noaa";
10955 /** Add font to FontStash.
10957 * Load scalable font from disk, and add it to FontStash. If you will try to load a font
10958 * with same name and path several times, FontStash will load it only once. Also, you can
10959 * load new disk font for any existing logical font.
10961 * Params:
10962 * name = logical font name, that will be used to select this font later.
10963 * path = path to disk file with your font.
10964 * defAA = should FontStash use antialiased font rasterizer?
10966 * Returns:
10967 * font id or [FONS_INVALID].
10969 int addFont (const(char)[] name, const(char)[] path, bool defAA=false) nothrow @trusted {
10970 if (path.length == 0 || name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID;
10971 if (path.length > 32768) return FONS_INVALID; // arbitrary limit
10973 // if font path ends with ":noaa", turn off antialiasing
10974 if (path.length >= NoAlias.length && strequci(path[$-NoAlias.length..$], NoAlias)) {
10975 path = path[0..$-NoAlias.length];
10976 if (path.length == 0) return FONS_INVALID;
10977 defAA = false;
10980 // if font name ends with ":noaa", turn off antialiasing
10981 if (name.length > NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) {
10982 name = name[0..$-NoAlias.length];
10983 defAA = false;
10986 // find a font with the given name
10987 int fidx = findNameInHash(name);
10988 //{ import core.stdc.stdio; printf("loading font '%.*s' [%s] (fidx=%d)...\n", cast(uint)path.length, path.ptr, fontnamebuf.ptr, fidx); }
10990 int loadFontFile (const(char)[] path) {
10991 // check if existing font (if any) has the same path
10992 if (fidx >= 0) {
10993 import core.stdc.string : strlen;
10994 auto plen = (fonts[fidx].path !is null ? strlen(fonts[fidx].path) : 0);
10995 version(Posix) {
10996 //{ 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); }
10997 if (plen == path.length && fonts[fidx].path[0..plen] == path) {
10998 //{ import core.stdc.stdio; printf("*** font [%.*s] already loaded from [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)plen, path.ptr); }
10999 // i found her!
11000 return fidx;
11002 } else {
11003 if (plen == path.length && strequci(fonts[fidx].path[0..plen], path)) {
11004 // i found her!
11005 return fidx;
11009 version(Windows) {
11010 // special shitdows check: this will reject fontconfig font names (but still allow things like "c:myfont")
11011 foreach (immutable char ch; path[(path.length >= 2 && path[1] == ':' ? 2 : 0)..$]) if (ch == ':') return FONS_INVALID;
11013 // either no such font, or different path
11014 //{ import core.stdc.stdio; printf("trying font [%.*s] from file [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)path.length, path.ptr); }
11015 int xres = FONS_INVALID;
11016 try {
11017 import core.stdc.stdlib : free, malloc;
11018 static if (NanoVegaHasIVVFS) {
11019 auto fl = VFile(path);
11020 auto dataSize = fl.size;
11021 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID;
11022 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize);
11023 if (data is null) assert(0, "out of memory in NanoVega fontstash");
11024 scope(failure) free(data); // oops
11025 fl.rawReadExact(data[0..cast(uint)dataSize]);
11026 fl.close();
11027 } else {
11028 import core.stdc.stdio : FILE, fopen, fclose, fread, ftell, fseek;
11029 import std.internal.cstring : tempCString;
11030 auto fl = fopen(path.tempCString, "rb");
11031 if (fl is null) return FONS_INVALID;
11032 scope(exit) fclose(fl);
11033 if (fseek(fl, 0, 2/*SEEK_END*/) != 0) return FONS_INVALID;
11034 auto dataSize = ftell(fl);
11035 if (fseek(fl, 0, 0/*SEEK_SET*/) != 0) return FONS_INVALID;
11036 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID;
11037 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize);
11038 if (data is null) assert(0, "out of memory in NanoVega fontstash");
11039 scope(failure) free(data); // oops
11040 ubyte* dptr = data;
11041 auto left = cast(uint)dataSize;
11042 while (left > 0) {
11043 auto rd = fread(dptr, 1, left, fl);
11044 if (rd == 0) { free(data); return FONS_INVALID; } // unexpected EOF or reading error, it doesn't matter
11045 dptr += rd;
11046 left -= rd;
11049 scope(failure) free(data); // oops
11050 // create font data
11051 FONSfontData* fdata = fons__createFontData(data, cast(int)dataSize, true); // free data
11052 fdata.incref();
11053 xres = addFontWithData(name, fdata, defAA);
11054 if (xres == FONS_INVALID) {
11055 fdata.decref(); // this will free [data] and [fdata]
11056 } else {
11057 // remember path
11058 fonts[xres].setPath(path);
11060 } catch (Exception e) {
11061 // oops; sorry
11063 return xres;
11066 // first try direct path
11067 auto res = loadFontFile(path);
11068 // if loading failed, try fontconfig (if fontconfig is available)
11069 static if (NanoVegaHasFontConfig) {
11070 if (res == FONS_INVALID && fontconfigAvailable) {
11071 // idiotic fontconfig NEVER fails; let's skip it if `path` looks like a path
11072 bool ok = true;
11073 if (path.length > 4 && (path[$-4..$] == ".ttf" || path[$-4..$] == ".ttc")) ok = false;
11074 if (ok) { foreach (immutable char ch; path) if (ch == '/') { ok = false; break; } }
11075 if (ok) {
11076 import std.internal.cstring : tempCString;
11077 FcPattern* pat = FcNameParse(path.tempCString);
11078 if (pat !is null) {
11079 scope(exit) FcPatternDestroy(pat);
11080 if (FcConfigSubstitute(null, pat, FcMatchPattern)) {
11081 FcDefaultSubstitute(pat);
11082 // find the font
11083 FcResult result;
11084 FcPattern* font = FcFontMatch(null, pat, &result);
11085 if (font !is null) {
11086 scope(exit) FcPatternDestroy(font);
11087 char* file = null;
11088 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
11089 if (file !is null && file[0]) {
11090 import core.stdc.string : strlen;
11091 res = loadFontFile(file[0..strlen(file)]);
11100 return res;
11103 /** Add font to FontStash, using data from memory.
11105 * And already loaded font to FontStash. You can replace existing logical fonts.
11106 * But note that you can't remove logical font by passing "empty" data.
11108 * $(WARNING If [FONS_INVALID] returned, `data` won't be freed even if `freeData` is `true`.)
11110 * Params:
11111 * name = logical font name, that will be used to select this font later.
11112 * data = font data.
11113 * dataSize = font data size.
11114 * freeData = should FontStash take ownership of the font data?
11115 * defAA = should FontStash use antialiased font rasterizer?
11117 * Returns:
11118 * font id or [FONS_INVALID].
11120 int addFontMem (const(char)[] name, ubyte* data, int dataSize, bool freeData, bool defAA=false) nothrow @trusted @nogc {
11121 if (data is null || dataSize < 16) return FONS_INVALID;
11122 FONSfontData* fdata = fons__createFontData(data, dataSize, freeData);
11123 fdata.incref();
11124 auto res = addFontWithData(name, fdata, defAA);
11125 if (res == FONS_INVALID) {
11126 // we promised to not free data on error
11127 fdata.freeData = false;
11128 fdata.decref(); // this will free [fdata]
11130 return res;
11133 /** Add fonts from another FontStash.
11135 * This is more effective (and faster) than reloading fonts, because internally font data
11136 * is reference counted.
11138 void addFontsFrom (FONSContext source) nothrow @trusted @nogc {
11139 if (source is null) return;
11140 foreach (FONSfont* font; source.fonts[0..source.nfonts]) {
11141 if (font !is null) {
11142 auto newidx = addCookedFont(font);
11143 FONSfont* newfont = fonts[newidx];
11144 assert(newfont !is null);
11145 assert(newfont.path is null);
11146 // copy path
11147 if (font.path !is null && font.path[0]) {
11148 import core.stdc.stdlib : malloc;
11149 import core.stdc.string : strcpy, strlen;
11150 newfont.path = cast(char*)malloc(strlen(font.path)+1);
11151 if (newfont.path is null) assert(0, "FONS: out of memory");
11152 strcpy(newfont.path, font.path);
11158 /// Returns logical font name corresponding to the given font id, or `null`.
11159 /// $(WARNING Copy returned name, as name buffer can be invalidated by next FontStash API call!)
11160 const(char)[] getNameById (int idx) const pure nothrow @trusted @nogc {
11161 if (idx < 0 || idx >= nfonts || fonts[idx] is null) return null;
11162 return fonts[idx].name[0..fonts[idx].namelen];
11165 /// Returns font id corresponding to the given logical font name, or [FONS_INVALID].
11166 int getFontByName (const(char)[] name) const pure nothrow @trusted @nogc {
11167 //{ import core.stdc.stdio; printf("fonsGetFontByName: [%.*s]\n", cast(uint)name.length, name.ptr); }
11168 // remove ":noaa" suffix
11169 if (name.length >= NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) {
11170 name = name[0..$-NoAlias.length];
11172 if (name.length == 0) return FONS_INVALID;
11173 return findNameInHash(name);
11176 /** Measures the specified text string. Parameter bounds should be a float[4],
11177 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax]
11178 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
11180 float getTextBounds(T) (float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc if (isAnyCharType!T) {
11181 FONSstate* state = getState;
11182 uint codepoint;
11183 uint utf8state = 0;
11184 FONSQuad q;
11185 FONSglyph* glyph = null;
11186 int prevGlyphIndex = -1;
11187 short isize = cast(short)(state.size*10.0f);
11188 short iblur = cast(short)state.blur;
11189 FONSfont* font;
11191 if (state.font < 0 || state.font >= nfonts) return 0;
11192 font = fonts[state.font];
11193 if (font is null || font.fdata is null) return 0;
11195 float scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
11197 // Align vertically.
11198 y += getVertAlign(font, state.talign, isize);
11200 float minx = x, maxx = x;
11201 float miny = y, maxy = y;
11202 float startx = x;
11204 foreach (T ch; str) {
11205 static if (T.sizeof == 1) {
11206 //if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;
11207 mixin(DecUtfMixin!("utf8state", "codepoint", "(cast(ubyte)ch)"));
11208 if (utf8state) continue;
11209 } else {
11210 static if (T.sizeof == 4) {
11211 if (ch > dchar.max) ch = 0xFFFD;
11213 codepoint = cast(uint)ch;
11215 glyph = getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional);
11216 if (glyph !is null) {
11217 getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q);
11218 if (q.x0 < minx) minx = q.x0;
11219 if (q.x1 > maxx) maxx = q.x1;
11220 if (params.isZeroTopLeft) {
11221 if (q.y0 < miny) miny = q.y0;
11222 if (q.y1 > maxy) maxy = q.y1;
11223 } else {
11224 if (q.y1 < miny) miny = q.y1;
11225 if (q.y0 > maxy) maxy = q.y0;
11227 prevGlyphIndex = glyph.index;
11228 } else {
11229 //{ import core.stdc.stdio; printf("NO GLYPH FOR 0x%04x\n", cast(uint)codepoint); }
11230 prevGlyphIndex = -1;
11234 float advance = x-startx;
11235 //{ import core.stdc.stdio; printf("***: x=%g; startx=%g; advance=%g\n", cast(double)x, cast(double)startx, cast(double)advance); }
11237 // Align horizontally
11238 if (state.talign.left) {
11239 // empty
11240 } else if (state.talign.right) {
11241 minx -= advance;
11242 maxx -= advance;
11243 } else if (state.talign.center) {
11244 minx -= advance*0.5f;
11245 maxx -= advance*0.5f;
11248 if (bounds.length) {
11249 if (bounds.length > 0) bounds.ptr[0] = minx;
11250 if (bounds.length > 1) bounds.ptr[1] = miny;
11251 if (bounds.length > 2) bounds.ptr[2] = maxx;
11252 if (bounds.length > 3) bounds.ptr[3] = maxy;
11255 return advance;
11258 /// Returns various font metrics. Any argument can be `null` if you aren't interested in its value.
11259 void getVertMetrics (float* ascender, float* descender, float* lineh) nothrow @trusted @nogc {
11260 FONSstate* state = getState;
11261 if (state.font < 0 || state.font >= nfonts) {
11262 if (ascender !is null) *ascender = 0;
11263 if (descender !is null) *descender = 0;
11264 if (lineh !is null) *lineh = 0;
11265 } else {
11266 FONSfont* font = fonts[state.font];
11267 if (font is null || font.fdata is null) {
11268 if (ascender !is null) *ascender = 0;
11269 if (descender !is null) *descender = 0;
11270 if (lineh !is null) *lineh = 0;
11271 } else {
11272 short isize = cast(short)(state.size*10.0f);
11273 if (ascender !is null) *ascender = font.ascender*isize/10.0f;
11274 if (descender !is null) *descender = font.descender*isize/10.0f;
11275 if (lineh !is null) *lineh = font.lineh*isize/10.0f;
11280 /// Returns line bounds. Any argument can be `null` if you aren't interested in its value.
11281 void getLineBounds (float y, float* minyp, float* maxyp) nothrow @trusted @nogc {
11282 FONSfont* font;
11283 FONSstate* state = getState;
11284 short isize;
11286 if (minyp !is null) *minyp = 0;
11287 if (maxyp !is null) *maxyp = 0;
11289 if (state.font < 0 || state.font >= nfonts) return;
11290 font = fonts[state.font];
11291 isize = cast(short)(state.size*10.0f);
11292 if (font is null || font.fdata is null) return;
11294 y += getVertAlign(font, state.talign, isize);
11296 if (params.isZeroTopLeft) {
11297 immutable float miny = y-font.ascender*cast(float)isize/10.0f;
11298 immutable float maxy = miny+font.lineh*isize/10.0f;
11299 if (minyp !is null) *minyp = miny;
11300 if (maxyp !is null) *maxyp = maxy;
11301 } else {
11302 immutable float maxy = y+font.descender*cast(float)isize/10.0f;
11303 immutable float miny = maxy-font.lineh*isize/10.0f;
11304 if (minyp !is null) *minyp = miny;
11305 if (maxyp !is null) *maxyp = maxy;
11309 /// Returns font line height.
11310 float fontHeight () nothrow @trusted @nogc {
11311 float res = void;
11312 getVertMetrics(null, null, &res);
11313 return res;
11316 /// Returns font ascender (positive).
11317 float fontAscender () nothrow @trusted @nogc {
11318 float res = void;
11319 getVertMetrics(&res, null, null);
11320 return res;
11323 /// Returns font descender (negative).
11324 float fontDescender () nothrow @trusted @nogc {
11325 float res = void;
11326 getVertMetrics(null, &res, null);
11327 return res;
11330 //TODO: document this
11331 const(ubyte)* getTextureData (int* width, int* height) nothrow @trusted @nogc {
11332 if (width !is null) *width = params.width;
11333 if (height !is null) *height = params.height;
11334 return texData;
11337 //TODO: document this
11338 bool validateTexture (int* dirty) nothrow @trusted @nogc {
11339 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) {
11340 dirty[0] = dirtyRect.ptr[0];
11341 dirty[1] = dirtyRect.ptr[1];
11342 dirty[2] = dirtyRect.ptr[2];
11343 dirty[3] = dirtyRect.ptr[3];
11344 // reset dirty rect
11345 dirtyRect.ptr[0] = params.width;
11346 dirtyRect.ptr[1] = params.height;
11347 dirtyRect.ptr[2] = 0;
11348 dirtyRect.ptr[3] = 0;
11349 return true;
11351 return false;
11354 //TODO: document this
11355 void errorCallback (void delegate (FONSError error, int val) nothrow @trusted @nogc callback) nothrow @trusted @nogc {
11356 handleError = callback;
11359 //TODO: document this
11360 void getAtlasSize (int* width, int* height) const pure nothrow @trusted @nogc {
11361 if (width !is null) *width = params.width;
11362 if (height !is null) *height = params.height;
11365 //TODO: document this
11366 bool expandAtlas (int width, int height) nothrow @trusted @nogc {
11367 import core.stdc.stdlib : free;
11368 import core.stdc.string : memcpy, memset;
11370 int maxy = 0;
11371 ubyte* data = null;
11373 width = nvg__max(width, params.width);
11374 height = nvg__max(height, params.height);
11376 if (width == params.width && height == params.height) return true;
11378 // Flush pending glyphs.
11379 flush();
11381 // Create new texture
11382 if (params.renderResize !is null) {
11383 if (params.renderResize(params.userPtr, width, height) == 0) return false;
11385 // Copy old texture data over.
11386 data = cast(ubyte*)malloc(width*height);
11387 if (data is null) return 0;
11388 foreach (immutable int i; 0..params.height) {
11389 ubyte* dst = &data[i*width];
11390 ubyte* src = &texData[i*params.width];
11391 memcpy(dst, src, params.width);
11392 if (width > params.width) memset(dst+params.width, 0, width-params.width);
11394 if (height > params.height) memset(&data[params.height*width], 0, (height-params.height)*width);
11396 free(texData);
11397 texData = data;
11399 // Increase atlas size
11400 atlas.expand(width, height);
11402 // Add existing data as dirty.
11403 foreach (immutable int i; 0..atlas.nnodes) maxy = nvg__max(maxy, atlas.nodes[i].y);
11404 dirtyRect.ptr[0] = 0;
11405 dirtyRect.ptr[1] = 0;
11406 dirtyRect.ptr[2] = params.width;
11407 dirtyRect.ptr[3] = maxy;
11409 params.width = width;
11410 params.height = height;
11411 itw = 1.0f/params.width;
11412 ith = 1.0f/params.height;
11414 return true;
11417 //TODO: document this
11418 bool resetAtlas (int width, int height) nothrow @trusted @nogc {
11419 import core.stdc.stdlib : realloc;
11420 import core.stdc.string : memcpy, memset;
11422 // flush pending glyphs
11423 flush();
11425 // create new texture
11426 if (params.renderResize !is null) {
11427 if (params.renderResize(params.userPtr, width, height) == 0) return false;
11430 // reset atlas
11431 atlas.reset(width, height);
11433 // clear texture data
11434 texData = cast(ubyte*)realloc(texData, width*height);
11435 if (texData is null) assert(0, "FONS: out of memory");
11436 memset(texData, 0, width*height);
11438 // reset dirty rect
11439 dirtyRect.ptr[0] = width;
11440 dirtyRect.ptr[1] = height;
11441 dirtyRect.ptr[2] = 0;
11442 dirtyRect.ptr[3] = 0;
11444 // Reset cached glyphs
11445 foreach (FONSfont* font; fonts[0..nfonts]) {
11446 if (font !is null) {
11447 font.nglyphs = 0;
11448 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1;
11452 params.width = width;
11453 params.height = height;
11454 itw = 1.0f/params.width;
11455 ith = 1.0f/params.height;
11457 // Add white rect at 0, 0 for debug drawing.
11458 addWhiteRect(2, 2);
11460 return true;
11463 //TODO: document this
11464 bool getPathBounds (dchar dch, float[] bounds) nothrow @trusted @nogc {
11465 if (bounds.length > 4) bounds = bounds.ptr[0..4];
11466 static if (is(typeof(&fons__nvg__bounds))) {
11467 FONSstate* state = getState;
11468 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; }
11469 FONSfont* font;
11470 auto g = findGlyphForCP(fonts[state.font], dch, &font);
11471 if (g == 0) { bounds[] = 0; return false; }
11472 assert(font !is null);
11473 return fons__nvg__bounds(&font.font, g, bounds);
11474 } else {
11475 bounds[] = 0;
11476 return false;
11480 //TODO: document this
11481 bool toPath() (NVGContext vg, dchar dch, float[] bounds=null) nothrow @trusted @nogc {
11482 if (bounds.length > 4) bounds = bounds.ptr[0..4];
11483 static if (is(typeof(&fons__nvg__toPath))) {
11484 if (vg is null) { bounds[] = 0; return false; }
11485 FONSstate* state = getState;
11486 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; }
11487 FONSfont* font;
11488 auto g = findGlyphForCP(fonts[state.font], dch, &font);
11489 if (g == 0) { bounds[] = 0; return false; }
11490 assert(font !is null);
11491 return fons__nvg__toPath(vg, &font.font, g, bounds);
11492 } else {
11493 bounds[] = 0;
11494 return false;
11498 //TODO: document this
11499 bool toOutline (dchar dch, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
11500 if (ol is null) return false;
11501 static if (is(typeof(&fons__nvg__toOutline))) {
11502 FONSstate* state = getState;
11503 if (state.font < 0 || state.font >= nfonts) return false;
11504 FONSfont* font;
11505 auto g = findGlyphForCP(fonts[state.font], dch, &font);
11506 if (g == 0) return false;
11507 assert(font !is null);
11508 return fons__nvg__toOutline(&font.font, g, ol);
11509 } else {
11510 return false;
11514 //TODO: document this
11515 FONSglyph* getGlyph (FONSfont* font, uint codepoint, short isize, short iblur, FONSBitmapFlag bitmapOption) nothrow @trusted @nogc {
11516 static uint fons__hashint() (uint a) pure nothrow @safe @nogc {
11517 pragma(inline, true);
11518 a += ~(a<<15);
11519 a ^= (a>>10);
11520 a += (a<<3);
11521 a ^= (a>>6);
11522 a += ~(a<<11);
11523 a ^= (a>>16);
11524 return a;
11527 // based on Exponential blur, Jani Huhtanen, 2006
11528 enum APREC = 16;
11529 enum ZPREC = 7;
11531 static void fons__blurCols (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc {
11532 foreach (immutable int y; 0..h) {
11533 int z = 0; // force zero border
11534 foreach (int x; 1..w) {
11535 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC;
11536 dst[x] = cast(ubyte)(z>>ZPREC);
11538 dst[w-1] = 0; // force zero border
11539 z = 0;
11540 for (int x = w-2; x >= 0; --x) {
11541 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC;
11542 dst[x] = cast(ubyte)(z>>ZPREC);
11544 dst[0] = 0; // force zero border
11545 dst += dstStride;
11549 static void fons__blurRows (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc {
11550 foreach (immutable int x; 0..w) {
11551 int z = 0; // force zero border
11552 for (int y = dstStride; y < h*dstStride; y += dstStride) {
11553 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC;
11554 dst[y] = cast(ubyte)(z>>ZPREC);
11556 dst[(h-1)*dstStride] = 0; // force zero border
11557 z = 0;
11558 for (int y = (h-2)*dstStride; y >= 0; y -= dstStride) {
11559 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC;
11560 dst[y] = cast(ubyte)(z>>ZPREC);
11562 dst[0] = 0; // force zero border
11563 ++dst;
11567 static void fons__blur (ubyte* dst, int w, int h, int dstStride, int blur) nothrow @trusted @nogc {
11568 import std.math : expf = exp;
11569 if (blur < 1) return;
11570 // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity)
11571 immutable float sigma = cast(float)blur*0.57735f; // 1/sqrt(3)
11572 int alpha = cast(int)((1<<APREC)*(1.0f-expf(-2.3f/(sigma+1.0f))));
11573 fons__blurRows(dst, w, h, dstStride, alpha);
11574 fons__blurCols(dst, w, h, dstStride, alpha);
11575 fons__blurRows(dst, w, h, dstStride, alpha);
11576 fons__blurCols(dst, w, h, dstStride, alpha);
11577 //fons__blurrows(dst, w, h, dstStride, alpha);
11578 //fons__blurcols(dst, w, h, dstStride, alpha);
11581 int advance, lsb, x0, y0, x1, y1, gx, gy;
11582 FONSglyph* glyph = null;
11583 float size = isize/10.0f;
11584 FONSfont* renderFont = font;
11586 if (isize < 2) return null;
11587 if (iblur > 20) iblur = 20;
11588 int pad = iblur+2;
11590 // Find code point and size.
11591 uint h = fons__hashint(codepoint)&(FONS_HASH_LUT_SIZE-1);
11592 int i = font.lut.ptr[h];
11593 while (i != -1) {
11594 //if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) return &font.glyphs[i];
11595 if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) {
11596 glyph = &font.glyphs[i];
11597 // Negative coordinate indicates there is no bitmap data created.
11598 if (bitmapOption == FONSBitmapFlag.Optional || (glyph.x0 >= 0 && glyph.y0 >= 0)) return glyph;
11599 // At this point, glyph exists but the bitmap data is not yet created.
11600 break;
11602 i = font.glyphs[i].next;
11605 // Create a new glyph or rasterize bitmap data for a cached glyph.
11606 //scale = fons__tt_getPixelHeightScale(&font.font, size);
11607 int g = findGlyphForCP(font, cast(dchar)codepoint, &renderFont);
11608 // It is possible that we did not find a fallback glyph.
11609 // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph.
11611 float scale = fons__tt_getPixelHeightScale(&renderFont.font, size);
11612 fons__tt_buildGlyphBitmap(&renderFont.font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1);
11613 int gw = x1-x0+pad*2;
11614 int gh = y1-y0+pad*2;
11616 // Determines the spot to draw glyph in the atlas.
11617 if (bitmapOption == FONSBitmapFlag.Required) {
11618 // Find free spot for the rect in the atlas.
11619 bool added = atlas.addRect(gw, gh, &gx, &gy);
11620 if (!added && handleError !is null) {
11621 // Atlas is full, let the user to resize the atlas (or not), and try again.
11622 handleError(FONSError.AtlasFull, 0);
11623 added = atlas.addRect(gw, gh, &gx, &gy);
11625 if (!added) return null;
11626 } else {
11627 // Negative coordinate indicates there is no bitmap data created.
11628 gx = -1;
11629 gy = -1;
11632 // Init glyph.
11633 if (glyph is null) {
11634 glyph = font.allocGlyph();
11635 glyph.codepoint = codepoint;
11636 glyph.size = isize;
11637 glyph.blur = iblur;
11638 glyph.next = 0;
11640 // Insert char to hash lookup.
11641 glyph.next = font.lut.ptr[h];
11642 font.lut.ptr[h] = font.nglyphs-1;
11644 glyph.index = g;
11645 glyph.x0 = cast(short)gx;
11646 glyph.y0 = cast(short)gy;
11647 glyph.x1 = cast(short)(glyph.x0+gw);
11648 glyph.y1 = cast(short)(glyph.y0+gh);
11649 glyph.xadv = cast(short)(scale*advance*10.0f);
11650 glyph.xoff = cast(short)(x0-pad);
11651 glyph.yoff = cast(short)(y0-pad);
11653 if (bitmapOption == FONSBitmapFlag.Optional) return glyph;
11655 // Rasterize
11656 ubyte* dst = &texData[(glyph.x0+pad)+(glyph.y0+pad)*params.width];
11657 fons__tt_renderGlyphBitmap(&font.font, dst, gw-pad*2, gh-pad*2, params.width, scale, scale, g);
11659 // Make sure there is one pixel empty border.
11660 dst = &texData[glyph.x0+glyph.y0*params.width];
11661 foreach (immutable int y; 0..gh) {
11662 dst[y*params.width] = 0;
11663 dst[gw-1+y*params.width] = 0;
11665 foreach (immutable int x; 0..gw) {
11666 dst[x] = 0;
11667 dst[x+(gh-1)*params.width] = 0;
11670 // Debug code to color the glyph background
11671 version(none) {
11672 foreach (immutable yy; 0..gh) {
11673 foreach (immutable xx; 0..gw) {
11674 int a = cast(int)dst[xx+yy*params.width]+42;
11675 if (a > 255) a = 255;
11676 dst[xx+yy*params.width] = cast(ubyte)a;
11681 // Blur
11682 if (iblur > 0) {
11683 ubyte* bdst = &texData[glyph.x0+glyph.y0*params.width];
11684 fons__blur(bdst, gw, gh, params.width, iblur);
11687 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], glyph.x0);
11688 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], glyph.y0);
11689 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], glyph.x1);
11690 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], glyph.y1);
11692 return glyph;
11695 //TODO: document this
11696 void getQuad (FONSfont* font, int prevGlyphIndex, FONSglyph* glyph, float size, float scale, float spacing, float* x, float* y, FONSQuad* q) nothrow @trusted @nogc {
11697 if (prevGlyphIndex >= 0) {
11698 immutable float adv = fons__tt_getGlyphKernAdvance(&font.font, size, prevGlyphIndex, glyph.index)/**scale*/; //k8: do we really need scale here?
11699 //if (adv != 0) { import core.stdc.stdio; printf("adv=%g (scale=%g; spacing=%g)\n", cast(double)adv, cast(double)scale, cast(double)spacing); }
11700 *x += cast(int)(adv+spacing /*+0.5f*/); //k8: for me, it looks better this way (with non-aa fonts)
11703 // Each glyph has 2px border to allow good interpolation,
11704 // one pixel to prevent leaking, and one to allow good interpolation for rendering.
11705 // Inset the texture region by one pixel for correct interpolation.
11706 immutable float xoff = cast(short)(glyph.xoff+1);
11707 immutable float yoff = cast(short)(glyph.yoff+1);
11708 immutable float x0 = cast(float)(glyph.x0+1);
11709 immutable float y0 = cast(float)(glyph.y0+1);
11710 immutable float x1 = cast(float)(glyph.x1-1);
11711 immutable float y1 = cast(float)(glyph.y1-1);
11713 if (params.isZeroTopLeft) {
11714 //immutable float rx = cast(float)cast(int)(*x+xoff);
11715 //immutable float ry = cast(float)cast(int)(*y+yoff);
11716 immutable float rx = nvg__floorf(*x+xoff);
11717 immutable float ry = nvg__floorf(*y+yoff);
11719 q.x0 = rx;
11720 q.y0 = ry;
11721 q.x1 = rx+x1-x0;
11722 q.y1 = ry+y1-y0;
11724 q.s0 = x0*itw;
11725 q.t0 = y0*ith;
11726 q.s1 = x1*itw;
11727 q.t1 = y1*ith;
11728 } else {
11729 //immutable float rx = cast(float)cast(int)(*x+xoff);
11730 //immutable float ry = cast(float)cast(int)(*y-yoff);
11731 immutable float rx = nvg__floorf(*x+xoff);
11732 immutable float ry = nvg__floorf(*y-yoff);
11734 q.x0 = rx;
11735 q.y0 = ry;
11736 q.x1 = rx+x1-x0;
11737 q.y1 = ry-y1+y0;
11739 q.s0 = x0*itw;
11740 q.t0 = y0*ith;
11741 q.s1 = x1*itw;
11742 q.t1 = y1*ith;
11745 *x += cast(int)(glyph.xadv/10.0f+0.5f);
11748 void flush () nothrow @trusted @nogc {
11749 // flush texture
11750 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) {
11751 if (params.renderUpdate !is null) params.renderUpdate(params.userPtr, dirtyRect.ptr, texData);
11752 // reset dirty rect
11753 dirtyRect.ptr[0] = params.width;
11754 dirtyRect.ptr[1] = params.height;
11755 dirtyRect.ptr[2] = 0;
11756 dirtyRect.ptr[3] = 0;
11761 /// Free all resources used by the `stash`, and `stash` itself.
11762 /// Group: font_stash
11763 public void kill (ref FONSContext stash) nothrow @trusted @nogc {
11764 import core.stdc.stdlib : free;
11765 if (stash is null) return;
11766 stash.clear();
11767 free(stash);
11768 stash = null;
11772 // ////////////////////////////////////////////////////////////////////////// //
11773 // Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de>
11774 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
11776 enum FONS_UTF8_ACCEPT = 0;
11777 enum FONS_UTF8_REJECT = 12;
11779 static immutable ubyte[364] utf8d = [
11780 // The first part of the table maps bytes to character classes that
11781 // to reduce the size of the transition table and create bitmasks.
11782 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,
11783 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,
11784 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,
11785 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,
11786 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,
11787 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,
11788 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,
11789 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,
11791 // The second part is a transition table that maps a combination
11792 // of a state of the automaton and a character class to a state.
11793 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
11794 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12,
11795 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12,
11796 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12,
11797 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
11800 private enum DecUtfMixin(string state, string codep, string byte_) =
11802 uint type_ = utf8d.ptr[`~byte_~`];
11803 `~codep~` = (`~state~` != FONS_UTF8_ACCEPT ? (`~byte_~`&0x3fu)|(`~codep~`<<6) : (0xff>>type_)&`~byte_~`);
11804 if ((`~state~` = utf8d.ptr[256+`~state~`+type_]) == FONS_UTF8_REJECT) {
11805 `~state~` = FONS_UTF8_ACCEPT;
11806 `~codep~` = 0xFFFD;
11811 uint fons__decutf8 (uint* state, uint* codep, uint byte_) {
11812 pragma(inline, true);
11813 uint type = utf8d.ptr[byte_];
11814 *codep = (*state != FONS_UTF8_ACCEPT ? (byte_&0x3fu)|(*codep<<6) : (0xff>>type)&byte_);
11815 *state = utf8d.ptr[256 + *state+type];
11816 return *state;
11821 // ////////////////////////////////////////////////////////////////////////// //
11822 /// This iterator can be used to do text measurement.
11823 /// $(WARNING Don't add new fonts to stash while you are iterating, or you WILL get segfault!)
11824 /// Group: font_stash
11825 public struct FONSTextBoundsIterator {
11826 private:
11827 FONSContext stash;
11828 FONSstate state;
11829 uint codepoint = 0xFFFD;
11830 uint utf8state = 0;
11831 int prevGlyphIndex = -1;
11832 short isize, iblur;
11833 float scale = 0;
11834 FONSfont* font;
11835 float startx = 0, x = 0, y = 0;
11836 float minx = 0, miny = 0, maxx = 0, maxy = 0;
11838 private:
11839 void clear () nothrow @trusted @nogc {
11840 import core.stdc.string : memset;
11841 memset(&this, 0, this.sizeof);
11842 this.prevGlyphIndex = -1;
11843 this.codepoint = 0xFFFD;
11846 public:
11847 /// Initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator.
11848 this (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { reset(astash, ax, ay); }
11850 /// (Re)initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator.
11851 void reset (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc {
11852 clear();
11854 if (astash is null || astash.nstates == 0) return;
11856 stash = astash;
11857 state = *stash.getState;
11859 if (state.font < 0 || state.font >= stash.nfonts) { clear(); return; }
11860 font = stash.fonts[state.font];
11861 if (font is null || font.fdata is null) { clear(); return; }
11863 x = ax;
11864 y = ay;
11865 isize = cast(short)(state.size*10.0f);
11866 iblur = cast(short)state.blur;
11867 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
11869 // align vertically
11870 y += astash.getVertAlign(font, state.talign, isize);
11872 minx = maxx = x;
11873 miny = maxy = y;
11874 startx = x;
11877 /// Can this iterator be used?
11878 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null); }
11880 /// Put some text into iterator, calculate new values.
11881 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) {
11882 enum DoCodePointMixin = q{
11883 glyph = stash.getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional);
11884 if (glyph !is null) {
11885 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q);
11886 if (q.x0 < minx) minx = q.x0;
11887 if (q.x1 > maxx) maxx = q.x1;
11888 if (stash.params.isZeroTopLeft) {
11889 if (q.y0 < miny) miny = q.y0;
11890 if (q.y1 > maxy) maxy = q.y1;
11891 } else {
11892 if (q.y1 < miny) miny = q.y1;
11893 if (q.y0 > maxy) maxy = q.y0;
11895 prevGlyphIndex = glyph.index;
11896 } else {
11897 prevGlyphIndex = -1;
11901 if (stash is null || str.length == 0) return; // alas
11903 FONSQuad q;
11904 FONSglyph* glyph;
11906 static if (is(T == char)) {
11907 foreach (char ch; str) {
11908 mixin(DecUtfMixin!("utf8state", "codepoint", "cast(ubyte)ch"));
11909 if (utf8state) continue; // full char is not collected yet
11910 mixin(DoCodePointMixin);
11912 } else {
11913 if (utf8state) {
11914 utf8state = 0;
11915 codepoint = 0xFFFD;
11916 mixin(DoCodePointMixin);
11918 foreach (T dch; str) {
11919 static if (is(T == dchar)) {
11920 if (dch > dchar.max) dch = 0xFFFD;
11922 codepoint = cast(uint)dch;
11923 mixin(DoCodePointMixin);
11928 /// Returns current advance.
11929 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null ? x-startx : 0); }
11931 /// Returns current text bounds.
11932 void getBounds (ref float[4] bounds) const pure nothrow @safe @nogc {
11933 if (stash is null) { bounds[] = 0; return; }
11934 float lminx = minx, lmaxx = maxx;
11935 // align horizontally
11936 if (state.talign.left) {
11937 // empty
11938 } else if (state.talign.right) {
11939 float ca = advance;
11940 lminx -= ca;
11941 lmaxx -= ca;
11942 } else if (state.talign.center) {
11943 float ca = advance*0.5f;
11944 lminx -= ca;
11945 lmaxx -= ca;
11947 bounds[0] = lminx;
11948 bounds[1] = miny;
11949 bounds[2] = lmaxx;
11950 bounds[3] = maxy;
11953 /// Returns current horizontal text bounds.
11954 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc {
11955 if (stash !is null) {
11956 float lminx = minx, lmaxx = maxx;
11957 // align horizontally
11958 if (state.talign.left) {
11959 // empty
11960 } else if (state.talign.right) {
11961 float ca = advance;
11962 lminx -= ca;
11963 lmaxx -= ca;
11964 } else if (state.talign.center) {
11965 float ca = advance*0.5f;
11966 lminx -= ca;
11967 lmaxx -= ca;
11969 xmin = lminx;
11970 xmax = lmaxx;
11971 } else {
11972 xmin = xmax = 0;
11976 /// Returns current vertical text bounds.
11977 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc {
11978 pragma(inline, true);
11979 if (stash !is null) {
11980 ymin = miny;
11981 ymax = maxy;
11982 } else {
11983 ymin = ymax = 0;
11987 /// Returns font line height.
11988 float lineHeight () nothrow @trusted @nogc {
11989 pragma(inline, true);
11990 return (stash !is null ? stash.fonts[state.font].lineh*cast(short)(state.size*10.0f)/10.0f : 0);
11993 /// Returns font ascender (positive).
11994 float ascender () nothrow @trusted @nogc {
11995 pragma(inline, true);
11996 return (stash !is null ? stash.fonts[state.font].ascender*cast(short)(state.size*10.0f)/10.0f : 0);
11999 /// Returns font descender (negative).
12000 float descender () nothrow @trusted @nogc {
12001 pragma(inline, true);
12002 return (stash !is null ? stash.fonts[state.font].descender*cast(short)(state.size*10.0f)/10.0f : 0);
12007 // ////////////////////////////////////////////////////////////////////////// //
12008 // backgl
12009 // ////////////////////////////////////////////////////////////////////////// //
12010 import core.stdc.stdlib : malloc, realloc, free;
12011 import core.stdc.string : memcpy, memset;
12013 static if (__VERSION__ < 2076) {
12014 private auto DGNoThrowNoGC(T) (scope T t) /*if (isFunctionPointer!T || isDelegate!T)*/ {
12015 import std.traits;
12016 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
12017 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
12022 //import arsd.simpledisplay;
12023 version(nanovg_builtin_opengl_bindings) { import arsd.simpledisplay; } else { import iv.glbinds; }
12025 private:
12026 // sdpy is missing that yet
12027 static if (!is(typeof(GL_STENCIL_BUFFER_BIT))) enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
12030 // OpenGL API missing from simpledisplay
12031 private extern(System) nothrow @nogc {
12032 alias GLvoid = void;
12033 alias GLboolean = ubyte;
12034 alias GLuint = uint;
12035 alias GLenum = uint;
12036 alias GLchar = char;
12037 alias GLsizei = int;
12038 alias GLfloat = float;
12039 alias GLsizeiptr = ptrdiff_t;
12041 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
12043 enum uint GL_INVALID_ENUM = 0x0500;
12045 enum uint GL_ZERO = 0;
12046 enum uint GL_ONE = 1;
12048 enum uint GL_FLOAT = 0x1406;
12050 enum uint GL_STREAM_DRAW = 0x88E0;
12052 enum uint GL_CCW = 0x0901;
12054 enum uint GL_STENCIL_TEST = 0x0B90;
12055 enum uint GL_SCISSOR_TEST = 0x0C11;
12057 enum uint GL_EQUAL = 0x0202;
12058 enum uint GL_NOTEQUAL = 0x0205;
12060 enum uint GL_ALWAYS = 0x0207;
12061 enum uint GL_KEEP = 0x1E00;
12063 enum uint GL_INCR = 0x1E02;
12065 enum uint GL_INCR_WRAP = 0x8507;
12066 enum uint GL_DECR_WRAP = 0x8508;
12068 enum uint GL_CULL_FACE = 0x0B44;
12069 enum uint GL_BACK = 0x0405;
12071 enum uint GL_FRAGMENT_SHADER = 0x8B30;
12072 enum uint GL_VERTEX_SHADER = 0x8B31;
12074 enum uint GL_COMPILE_STATUS = 0x8B81;
12075 enum uint GL_LINK_STATUS = 0x8B82;
12077 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
12078 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
12079 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
12080 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
12082 enum uint GL_GENERATE_MIPMAP = 0x8191;
12083 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
12085 enum uint GL_RED = 0x1903;
12087 enum uint GL_TEXTURE0 = 0x84C0U;
12088 enum uint GL_TEXTURE1 = 0x84C1U;
12090 enum uint GL_ARRAY_BUFFER = 0x8892;
12092 enum uint GL_SRC_COLOR = 0x0300;
12093 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
12094 enum uint GL_SRC_ALPHA = 0x0302;
12095 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
12096 enum uint GL_DST_ALPHA = 0x0304;
12097 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
12098 enum uint GL_DST_COLOR = 0x0306;
12099 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
12100 enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
12102 enum uint GL_INVERT = 0x150AU;
12104 enum uint GL_DEPTH_STENCIL = 0x84F9U;
12105 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
12107 enum uint GL_FRAMEBUFFER = 0x8D40U;
12108 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
12109 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
12111 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
12112 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
12113 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
12114 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
12115 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
12117 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
12118 enum uint GL_CLEAR = 0x1500U;
12119 enum uint GL_COPY = 0x1503U;
12120 enum uint GL_XOR = 0x1506U;
12122 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
12125 version(Windows) {
12126 private void* kglLoad (const(char)* name) {
12127 void* res = glGetProcAddress(name);
12128 if (res is null) {
12129 import core.sys.windows.windef, core.sys.windows.winbase;
12130 static HINSTANCE dll = null;
12131 if (dll is null) {
12132 dll = LoadLibraryA("opengl32.dll");
12133 if (dll is null) return null; // <32, but idc
12134 return GetProcAddress(dll, name);
12138 } else {
12139 alias kglLoad = glGetProcAddress;
12143 alias glbfn_glStencilMask = void function(GLuint);
12144 __gshared glbfn_glStencilMask glStencilMask_NVGLZ; alias glStencilMask = glStencilMask_NVGLZ;
12145 alias glbfn_glStencilFunc = void function(GLenum, GLint, GLuint);
12146 __gshared glbfn_glStencilFunc glStencilFunc_NVGLZ; alias glStencilFunc = glStencilFunc_NVGLZ;
12147 alias glbfn_glGetShaderInfoLog = void function(GLuint, GLsizei, GLsizei*, GLchar*);
12148 __gshared glbfn_glGetShaderInfoLog glGetShaderInfoLog_NVGLZ; alias glGetShaderInfoLog = glGetShaderInfoLog_NVGLZ;
12149 alias glbfn_glGetProgramInfoLog = void function(GLuint, GLsizei, GLsizei*, GLchar*);
12150 __gshared glbfn_glGetProgramInfoLog glGetProgramInfoLog_NVGLZ; alias glGetProgramInfoLog = glGetProgramInfoLog_NVGLZ;
12151 alias glbfn_glCreateProgram = GLuint function();
12152 __gshared glbfn_glCreateProgram glCreateProgram_NVGLZ; alias glCreateProgram = glCreateProgram_NVGLZ;
12153 alias glbfn_glCreateShader = GLuint function(GLenum);
12154 __gshared glbfn_glCreateShader glCreateShader_NVGLZ; alias glCreateShader = glCreateShader_NVGLZ;
12155 alias glbfn_glShaderSource = void function(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
12156 __gshared glbfn_glShaderSource glShaderSource_NVGLZ; alias glShaderSource = glShaderSource_NVGLZ;
12157 alias glbfn_glCompileShader = void function(GLuint);
12158 __gshared glbfn_glCompileShader glCompileShader_NVGLZ; alias glCompileShader = glCompileShader_NVGLZ;
12159 alias glbfn_glGetShaderiv = void function(GLuint, GLenum, GLint*);
12160 __gshared glbfn_glGetShaderiv glGetShaderiv_NVGLZ; alias glGetShaderiv = glGetShaderiv_NVGLZ;
12161 alias glbfn_glAttachShader = void function(GLuint, GLuint);
12162 __gshared glbfn_glAttachShader glAttachShader_NVGLZ; alias glAttachShader = glAttachShader_NVGLZ;
12163 alias glbfn_glBindAttribLocation = void function(GLuint, GLuint, const(GLchar)*);
12164 __gshared glbfn_glBindAttribLocation glBindAttribLocation_NVGLZ; alias glBindAttribLocation = glBindAttribLocation_NVGLZ;
12165 alias glbfn_glLinkProgram = void function(GLuint);
12166 __gshared glbfn_glLinkProgram glLinkProgram_NVGLZ; alias glLinkProgram = glLinkProgram_NVGLZ;
12167 alias glbfn_glGetProgramiv = void function(GLuint, GLenum, GLint*);
12168 __gshared glbfn_glGetProgramiv glGetProgramiv_NVGLZ; alias glGetProgramiv = glGetProgramiv_NVGLZ;
12169 alias glbfn_glDeleteProgram = void function(GLuint);
12170 __gshared glbfn_glDeleteProgram glDeleteProgram_NVGLZ; alias glDeleteProgram = glDeleteProgram_NVGLZ;
12171 alias glbfn_glDeleteShader = void function(GLuint);
12172 __gshared glbfn_glDeleteShader glDeleteShader_NVGLZ; alias glDeleteShader = glDeleteShader_NVGLZ;
12173 alias glbfn_glGetUniformLocation = GLint function(GLuint, const(GLchar)*);
12174 __gshared glbfn_glGetUniformLocation glGetUniformLocation_NVGLZ; alias glGetUniformLocation = glGetUniformLocation_NVGLZ;
12175 alias glbfn_glGenBuffers = void function(GLsizei, GLuint*);
12176 __gshared glbfn_glGenBuffers glGenBuffers_NVGLZ; alias glGenBuffers = glGenBuffers_NVGLZ;
12177 alias glbfn_glPixelStorei = void function(GLenum, GLint);
12178 __gshared glbfn_glPixelStorei glPixelStorei_NVGLZ; alias glPixelStorei = glPixelStorei_NVGLZ;
12179 alias glbfn_glUniform4fv = void function(GLint, GLsizei, const(GLfloat)*);
12180 __gshared glbfn_glUniform4fv glUniform4fv_NVGLZ; alias glUniform4fv = glUniform4fv_NVGLZ;
12181 alias glbfn_glColorMask = void function(GLboolean, GLboolean, GLboolean, GLboolean);
12182 __gshared glbfn_glColorMask glColorMask_NVGLZ; alias glColorMask = glColorMask_NVGLZ;
12183 alias glbfn_glStencilOpSeparate = void function(GLenum, GLenum, GLenum, GLenum);
12184 __gshared glbfn_glStencilOpSeparate glStencilOpSeparate_NVGLZ; alias glStencilOpSeparate = glStencilOpSeparate_NVGLZ;
12185 alias glbfn_glDrawArrays = void function(GLenum, GLint, GLsizei);
12186 __gshared glbfn_glDrawArrays glDrawArrays_NVGLZ; alias glDrawArrays = glDrawArrays_NVGLZ;
12187 alias glbfn_glStencilOp = void function(GLenum, GLenum, GLenum);
12188 __gshared glbfn_glStencilOp glStencilOp_NVGLZ; alias glStencilOp = glStencilOp_NVGLZ;
12189 alias glbfn_glUseProgram = void function(GLuint);
12190 __gshared glbfn_glUseProgram glUseProgram_NVGLZ; alias glUseProgram = glUseProgram_NVGLZ;
12191 alias glbfn_glCullFace = void function(GLenum);
12192 __gshared glbfn_glCullFace glCullFace_NVGLZ; alias glCullFace = glCullFace_NVGLZ;
12193 alias glbfn_glFrontFace = void function(GLenum);
12194 __gshared glbfn_glFrontFace glFrontFace_NVGLZ; alias glFrontFace = glFrontFace_NVGLZ;
12195 alias glbfn_glActiveTexture = void function(GLenum);
12196 __gshared glbfn_glActiveTexture glActiveTexture_NVGLZ; alias glActiveTexture = glActiveTexture_NVGLZ;
12197 alias glbfn_glBindBuffer = void function(GLenum, GLuint);
12198 __gshared glbfn_glBindBuffer glBindBuffer_NVGLZ; alias glBindBuffer = glBindBuffer_NVGLZ;
12199 alias glbfn_glBufferData = void function(GLenum, GLsizeiptr, const(void)*, GLenum);
12200 __gshared glbfn_glBufferData glBufferData_NVGLZ; alias glBufferData = glBufferData_NVGLZ;
12201 alias glbfn_glEnableVertexAttribArray = void function(GLuint);
12202 __gshared glbfn_glEnableVertexAttribArray glEnableVertexAttribArray_NVGLZ; alias glEnableVertexAttribArray = glEnableVertexAttribArray_NVGLZ;
12203 alias glbfn_glVertexAttribPointer = void function(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
12204 __gshared glbfn_glVertexAttribPointer glVertexAttribPointer_NVGLZ; alias glVertexAttribPointer = glVertexAttribPointer_NVGLZ;
12205 alias glbfn_glUniform1i = void function(GLint, GLint);
12206 __gshared glbfn_glUniform1i glUniform1i_NVGLZ; alias glUniform1i = glUniform1i_NVGLZ;
12207 alias glbfn_glUniform2fv = void function(GLint, GLsizei, const(GLfloat)*);
12208 __gshared glbfn_glUniform2fv glUniform2fv_NVGLZ; alias glUniform2fv = glUniform2fv_NVGLZ;
12209 alias glbfn_glDisableVertexAttribArray = void function(GLuint);
12210 __gshared glbfn_glDisableVertexAttribArray glDisableVertexAttribArray_NVGLZ; alias glDisableVertexAttribArray = glDisableVertexAttribArray_NVGLZ;
12211 alias glbfn_glDeleteBuffers = void function(GLsizei, const(GLuint)*);
12212 __gshared glbfn_glDeleteBuffers glDeleteBuffers_NVGLZ; alias glDeleteBuffers = glDeleteBuffers_NVGLZ;
12213 alias glbfn_glBlendFuncSeparate = void function(GLenum, GLenum, GLenum, GLenum);
12214 __gshared glbfn_glBlendFuncSeparate glBlendFuncSeparate_NVGLZ; alias glBlendFuncSeparate = glBlendFuncSeparate_NVGLZ;
12216 alias glbfn_glLogicOp = void function (GLenum opcode);
12217 __gshared glbfn_glLogicOp glLogicOp_NVGLZ; alias glLogicOp = glLogicOp_NVGLZ;
12218 alias glbfn_glFramebufferTexture2D = void function (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
12219 __gshared glbfn_glFramebufferTexture2D glFramebufferTexture2D_NVGLZ; alias glFramebufferTexture2D = glFramebufferTexture2D_NVGLZ;
12220 alias glbfn_glDeleteFramebuffers = void function (GLsizei n, const(GLuint)* framebuffers);
12221 __gshared glbfn_glDeleteFramebuffers glDeleteFramebuffers_NVGLZ; alias glDeleteFramebuffers = glDeleteFramebuffers_NVGLZ;
12222 alias glbfn_glGenFramebuffers = void function (GLsizei n, GLuint* framebuffers);
12223 __gshared glbfn_glGenFramebuffers glGenFramebuffers_NVGLZ; alias glGenFramebuffers = glGenFramebuffers_NVGLZ;
12224 alias glbfn_glCheckFramebufferStatus = GLenum function (GLenum target);
12225 __gshared glbfn_glCheckFramebufferStatus glCheckFramebufferStatus_NVGLZ; alias glCheckFramebufferStatus = glCheckFramebufferStatus_NVGLZ;
12226 alias glbfn_glBindFramebuffer = void function (GLenum target, GLuint framebuffer);
12227 __gshared glbfn_glBindFramebuffer glBindFramebuffer_NVGLZ; alias glBindFramebuffer = glBindFramebuffer_NVGLZ;
12229 alias glbfn_glGetIntegerv = void function (GLenum pname, GLint* data);
12230 __gshared glbfn_glGetIntegerv glGetIntegerv_NVGLZ; alias glGetIntegerv = glGetIntegerv_NVGLZ;
12232 private void nanovgInitOpenGL () {
12233 __gshared bool initialized = false;
12234 if (initialized) return;
12235 glStencilMask_NVGLZ = cast(glbfn_glStencilMask)glbindGetProcAddress(`glStencilMask`);
12236 if (glStencilMask_NVGLZ is null) assert(0, `OpenGL function 'glStencilMask' not found!`);
12237 glStencilFunc_NVGLZ = cast(glbfn_glStencilFunc)glbindGetProcAddress(`glStencilFunc`);
12238 if (glStencilFunc_NVGLZ is null) assert(0, `OpenGL function 'glStencilFunc' not found!`);
12239 glGetShaderInfoLog_NVGLZ = cast(glbfn_glGetShaderInfoLog)glbindGetProcAddress(`glGetShaderInfoLog`);
12240 if (glGetShaderInfoLog_NVGLZ is null) assert(0, `OpenGL function 'glGetShaderInfoLog' not found!`);
12241 glGetProgramInfoLog_NVGLZ = cast(glbfn_glGetProgramInfoLog)glbindGetProcAddress(`glGetProgramInfoLog`);
12242 if (glGetProgramInfoLog_NVGLZ is null) assert(0, `OpenGL function 'glGetProgramInfoLog' not found!`);
12243 glCreateProgram_NVGLZ = cast(glbfn_glCreateProgram)glbindGetProcAddress(`glCreateProgram`);
12244 if (glCreateProgram_NVGLZ is null) assert(0, `OpenGL function 'glCreateProgram' not found!`);
12245 glCreateShader_NVGLZ = cast(glbfn_glCreateShader)glbindGetProcAddress(`glCreateShader`);
12246 if (glCreateShader_NVGLZ is null) assert(0, `OpenGL function 'glCreateShader' not found!`);
12247 glShaderSource_NVGLZ = cast(glbfn_glShaderSource)glbindGetProcAddress(`glShaderSource`);
12248 if (glShaderSource_NVGLZ is null) assert(0, `OpenGL function 'glShaderSource' not found!`);
12249 glCompileShader_NVGLZ = cast(glbfn_glCompileShader)glbindGetProcAddress(`glCompileShader`);
12250 if (glCompileShader_NVGLZ is null) assert(0, `OpenGL function 'glCompileShader' not found!`);
12251 glGetShaderiv_NVGLZ = cast(glbfn_glGetShaderiv)glbindGetProcAddress(`glGetShaderiv`);
12252 if (glGetShaderiv_NVGLZ is null) assert(0, `OpenGL function 'glGetShaderiv' not found!`);
12253 glAttachShader_NVGLZ = cast(glbfn_glAttachShader)glbindGetProcAddress(`glAttachShader`);
12254 if (glAttachShader_NVGLZ is null) assert(0, `OpenGL function 'glAttachShader' not found!`);
12255 glBindAttribLocation_NVGLZ = cast(glbfn_glBindAttribLocation)glbindGetProcAddress(`glBindAttribLocation`);
12256 if (glBindAttribLocation_NVGLZ is null) assert(0, `OpenGL function 'glBindAttribLocation' not found!`);
12257 glLinkProgram_NVGLZ = cast(glbfn_glLinkProgram)glbindGetProcAddress(`glLinkProgram`);
12258 if (glLinkProgram_NVGLZ is null) assert(0, `OpenGL function 'glLinkProgram' not found!`);
12259 glGetProgramiv_NVGLZ = cast(glbfn_glGetProgramiv)glbindGetProcAddress(`glGetProgramiv`);
12260 if (glGetProgramiv_NVGLZ is null) assert(0, `OpenGL function 'glGetProgramiv' not found!`);
12261 glDeleteProgram_NVGLZ = cast(glbfn_glDeleteProgram)glbindGetProcAddress(`glDeleteProgram`);
12262 if (glDeleteProgram_NVGLZ is null) assert(0, `OpenGL function 'glDeleteProgram' not found!`);
12263 glDeleteShader_NVGLZ = cast(glbfn_glDeleteShader)glbindGetProcAddress(`glDeleteShader`);
12264 if (glDeleteShader_NVGLZ is null) assert(0, `OpenGL function 'glDeleteShader' not found!`);
12265 glGetUniformLocation_NVGLZ = cast(glbfn_glGetUniformLocation)glbindGetProcAddress(`glGetUniformLocation`);
12266 if (glGetUniformLocation_NVGLZ is null) assert(0, `OpenGL function 'glGetUniformLocation' not found!`);
12267 glGenBuffers_NVGLZ = cast(glbfn_glGenBuffers)glbindGetProcAddress(`glGenBuffers`);
12268 if (glGenBuffers_NVGLZ is null) assert(0, `OpenGL function 'glGenBuffers' not found!`);
12269 glPixelStorei_NVGLZ = cast(glbfn_glPixelStorei)glbindGetProcAddress(`glPixelStorei`);
12270 if (glPixelStorei_NVGLZ is null) assert(0, `OpenGL function 'glPixelStorei' not found!`);
12271 glUniform4fv_NVGLZ = cast(glbfn_glUniform4fv)glbindGetProcAddress(`glUniform4fv`);
12272 if (glUniform4fv_NVGLZ is null) assert(0, `OpenGL function 'glUniform4fv' not found!`);
12273 glColorMask_NVGLZ = cast(glbfn_glColorMask)glbindGetProcAddress(`glColorMask`);
12274 if (glColorMask_NVGLZ is null) assert(0, `OpenGL function 'glColorMask' not found!`);
12275 glStencilOpSeparate_NVGLZ = cast(glbfn_glStencilOpSeparate)glbindGetProcAddress(`glStencilOpSeparate`);
12276 if (glStencilOpSeparate_NVGLZ is null) assert(0, `OpenGL function 'glStencilOpSeparate' not found!`);
12277 glDrawArrays_NVGLZ = cast(glbfn_glDrawArrays)glbindGetProcAddress(`glDrawArrays`);
12278 if (glDrawArrays_NVGLZ is null) assert(0, `OpenGL function 'glDrawArrays' not found!`);
12279 glStencilOp_NVGLZ = cast(glbfn_glStencilOp)glbindGetProcAddress(`glStencilOp`);
12280 if (glStencilOp_NVGLZ is null) assert(0, `OpenGL function 'glStencilOp' not found!`);
12281 glUseProgram_NVGLZ = cast(glbfn_glUseProgram)glbindGetProcAddress(`glUseProgram`);
12282 if (glUseProgram_NVGLZ is null) assert(0, `OpenGL function 'glUseProgram' not found!`);
12283 glCullFace_NVGLZ = cast(glbfn_glCullFace)glbindGetProcAddress(`glCullFace`);
12284 if (glCullFace_NVGLZ is null) assert(0, `OpenGL function 'glCullFace' not found!`);
12285 glFrontFace_NVGLZ = cast(glbfn_glFrontFace)glbindGetProcAddress(`glFrontFace`);
12286 if (glFrontFace_NVGLZ is null) assert(0, `OpenGL function 'glFrontFace' not found!`);
12287 glActiveTexture_NVGLZ = cast(glbfn_glActiveTexture)glbindGetProcAddress(`glActiveTexture`);
12288 if (glActiveTexture_NVGLZ is null) assert(0, `OpenGL function 'glActiveTexture' not found!`);
12289 glBindBuffer_NVGLZ = cast(glbfn_glBindBuffer)glbindGetProcAddress(`glBindBuffer`);
12290 if (glBindBuffer_NVGLZ is null) assert(0, `OpenGL function 'glBindBuffer' not found!`);
12291 glBufferData_NVGLZ = cast(glbfn_glBufferData)glbindGetProcAddress(`glBufferData`);
12292 if (glBufferData_NVGLZ is null) assert(0, `OpenGL function 'glBufferData' not found!`);
12293 glEnableVertexAttribArray_NVGLZ = cast(glbfn_glEnableVertexAttribArray)glbindGetProcAddress(`glEnableVertexAttribArray`);
12294 if (glEnableVertexAttribArray_NVGLZ is null) assert(0, `OpenGL function 'glEnableVertexAttribArray' not found!`);
12295 glVertexAttribPointer_NVGLZ = cast(glbfn_glVertexAttribPointer)glbindGetProcAddress(`glVertexAttribPointer`);
12296 if (glVertexAttribPointer_NVGLZ is null) assert(0, `OpenGL function 'glVertexAttribPointer' not found!`);
12297 glUniform1i_NVGLZ = cast(glbfn_glUniform1i)glbindGetProcAddress(`glUniform1i`);
12298 if (glUniform1i_NVGLZ is null) assert(0, `OpenGL function 'glUniform1i' not found!`);
12299 glUniform2fv_NVGLZ = cast(glbfn_glUniform2fv)glbindGetProcAddress(`glUniform2fv`);
12300 if (glUniform2fv_NVGLZ is null) assert(0, `OpenGL function 'glUniform2fv' not found!`);
12301 glDisableVertexAttribArray_NVGLZ = cast(glbfn_glDisableVertexAttribArray)glbindGetProcAddress(`glDisableVertexAttribArray`);
12302 if (glDisableVertexAttribArray_NVGLZ is null) assert(0, `OpenGL function 'glDisableVertexAttribArray' not found!`);
12303 glDeleteBuffers_NVGLZ = cast(glbfn_glDeleteBuffers)glbindGetProcAddress(`glDeleteBuffers`);
12304 if (glDeleteBuffers_NVGLZ is null) assert(0, `OpenGL function 'glDeleteBuffers' not found!`);
12305 glBlendFuncSeparate_NVGLZ = cast(glbfn_glBlendFuncSeparate)glbindGetProcAddress(`glBlendFuncSeparate`);
12306 if (glBlendFuncSeparate_NVGLZ is null) assert(0, `OpenGL function 'glBlendFuncSeparate' not found!`);
12308 glLogicOp_NVGLZ = cast(glbfn_glLogicOp)glbindGetProcAddress(`glLogicOp`);
12309 if (glLogicOp_NVGLZ is null) assert(0, `OpenGL function 'glLogicOp' not found!`);
12310 glFramebufferTexture2D_NVGLZ = cast(glbfn_glFramebufferTexture2D)glbindGetProcAddress(`glFramebufferTexture2D`);
12311 if (glFramebufferTexture2D_NVGLZ is null) assert(0, `OpenGL function 'glFramebufferTexture2D' not found!`);
12312 glDeleteFramebuffers_NVGLZ = cast(glbfn_glDeleteFramebuffers)glbindGetProcAddress(`glDeleteFramebuffers`);
12313 if (glDeleteFramebuffers_NVGLZ is null) assert(0, `OpenGL function 'glDeleteFramebuffers' not found!`);
12314 glGenFramebuffers_NVGLZ = cast(glbfn_glGenFramebuffers)glbindGetProcAddress(`glGenFramebuffers`);
12315 if (glGenFramebuffers_NVGLZ is null) assert(0, `OpenGL function 'glGenFramebuffers' not found!`);
12316 glCheckFramebufferStatus_NVGLZ = cast(glbfn_glCheckFramebufferStatus)glbindGetProcAddress(`glCheckFramebufferStatus`);
12317 if (glCheckFramebufferStatus_NVGLZ is null) assert(0, `OpenGL function 'glCheckFramebufferStatus' not found!`);
12318 glBindFramebuffer_NVGLZ = cast(glbfn_glBindFramebuffer)glbindGetProcAddress(`glBindFramebuffer`);
12319 if (glBindFramebuffer_NVGLZ is null) assert(0, `OpenGL function 'glBindFramebuffer' not found!`);
12321 glGetIntegerv_NVGLZ = cast(glbfn_glGetIntegerv)glbindGetProcAddress(`glGetIntegerv`);
12322 if (glGetIntegerv_NVGLZ is null) assert(0, `OpenGL function 'glGetIntegerv' not found!`);
12324 initialized = true;
12329 /// Context creation flags.
12330 /// Group: context_management
12331 public enum NVGContextFlag : int {
12332 /// Nothing special, i.e. empty flag.
12333 None = 0,
12334 /// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA).
12335 Antialias = 1U<<0,
12336 /** Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little
12337 * slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. */
12338 StencilStrokes = 1U<<1,
12339 /// Flag indicating that additional debug checks are done.
12340 Debug = 1U<<2,
12341 /// Filter (antialias) fonts
12342 FontAA = 1U<<7,
12343 /// Don't filter (antialias) fonts
12344 FontNoAA = 1U<<8,
12345 /// You can use this as a substitute for default flags, for cases like this: `nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);`.
12346 Default = 1U<<31,
12349 public enum NANOVG_GL_USE_STATE_FILTER = true;
12351 /// Returns flags for glClear().
12352 /// Group: context_management
12353 public uint glNVGClearFlags () pure nothrow @safe @nogc {
12354 pragma(inline, true);
12355 return (GL_COLOR_BUFFER_BIT|/*GL_DEPTH_BUFFER_BIT|*/GL_STENCIL_BUFFER_BIT);
12359 // ////////////////////////////////////////////////////////////////////////// //
12360 private:
12362 version = nanovega_shared_stencil;
12363 //version = nanovega_debug_clipping;
12365 enum GLNVGuniformLoc {
12366 ViewSize,
12367 Tex,
12368 Frag,
12369 TMat,
12370 TTr,
12371 ClipTex,
12374 alias GLNVGshaderType = int;
12375 enum /*GLNVGshaderType*/ {
12376 NSVG_SHADER_FILLCOLOR,
12377 NSVG_SHADER_FILLGRAD,
12378 NSVG_SHADER_FILLIMG,
12379 NSVG_SHADER_SIMPLE, // also used for clipfill
12380 NSVG_SHADER_IMG,
12383 struct GLNVGshader {
12384 GLuint prog;
12385 GLuint frag;
12386 GLuint vert;
12387 GLint[GLNVGuniformLoc.max+1] loc;
12390 struct GLNVGtexture {
12391 int id;
12392 GLuint tex;
12393 int width, height;
12394 NVGtexture type;
12395 int flags;
12396 shared int rc; // this can be 0 with tex != 0 -- postponed deletion
12397 int nextfree;
12400 struct GLNVGblend {
12401 bool simple;
12402 GLenum srcRGB;
12403 GLenum dstRGB;
12404 GLenum srcAlpha;
12405 GLenum dstAlpha;
12408 alias GLNVGcallType = int;
12409 enum /*GLNVGcallType*/ {
12410 GLNVG_NONE = 0,
12411 GLNVG_FILL,
12412 GLNVG_CONVEXFILL,
12413 GLNVG_STROKE,
12414 GLNVG_TRIANGLES,
12415 GLNVG_AFFINE, // change affine transformation matrix
12416 GLNVG_PUSHCLIP,
12417 GLNVG_POPCLIP,
12418 GLNVG_RESETCLIP,
12419 GLNVG_CLIP_DDUMP_ON,
12420 GLNVG_CLIP_DDUMP_OFF,
12423 struct GLNVGcall {
12424 int type;
12425 int evenOdd; // for fill
12426 int image;
12427 int pathOffset;
12428 int pathCount;
12429 int triangleOffset;
12430 int triangleCount;
12431 int uniformOffset;
12432 NVGMatrix affine;
12433 GLNVGblend blendFunc;
12434 NVGClipMode clipmode;
12437 struct GLNVGpath {
12438 int fillOffset;
12439 int fillCount;
12440 int strokeOffset;
12441 int strokeCount;
12444 align(1) struct GLNVGfragUniforms {
12445 align(1):
12446 enum UNIFORM_ARRAY_SIZE = 13;
12447 // note: after modifying layout or size of uniform array,
12448 // don't forget to also update the fragment shader source!
12449 align(1) union {
12450 align(1):
12451 align(1) struct {
12452 align(1):
12453 float[12] scissorMat; // matrices are actually 3 vec4s
12454 float[12] paintMat;
12455 NVGColor innerCol;
12456 NVGColor middleCol;
12457 NVGColor outerCol;
12458 float[2] scissorExt;
12459 float[2] scissorScale;
12460 float[2] extent;
12461 float radius;
12462 float feather;
12463 float strokeMult;
12464 float strokeThr;
12465 float texType;
12466 float type;
12467 float doclip;
12468 float midp; // for gradients
12469 float unused2, unused3;
12471 float[4][UNIFORM_ARRAY_SIZE] uniformArray;
12475 enum GLMaskState {
12476 DontMask = -1,
12477 Uninitialized = 0,
12478 Initialized = 1,
12479 JustCleared = 2,
12482 final class GLNVGTextureLocker {}
12484 struct GLNVGcontext {
12485 private import core.thread : ThreadID;
12487 GLNVGshader shader;
12488 GLNVGtexture* textures;
12489 float[2] view;
12490 int freetexid; // -1: none
12491 int ntextures;
12492 int ctextures;
12493 GLuint vertBuf;
12494 int fragSize;
12495 int flags;
12496 // FBOs for masks
12497 GLuint[NVG_MAX_STATES] fbo;
12498 GLuint[2][NVG_MAX_STATES] fboTex; // FBO textures: [0] is color, [1] is stencil
12499 int fboWidth, fboHeight;
12500 GLMaskState[NVG_MAX_STATES] maskStack;
12501 int msp; // mask stack pointer; starts from `0`; points to next free item; see below for logic description
12502 int lastClipFBO; // -666: cache invalidated; -1: don't mask
12503 int lastClipUniOfs;
12504 bool doClipUnion; // specal mode
12505 GLNVGshader shaderFillFBO;
12506 GLNVGshader shaderCopyFBO;
12508 bool inFrame; // will be `true` if we can perform OpenGL operations (used in texture deletion)
12509 shared bool mustCleanTextures; // will be `true` if we should delete some textures
12510 ThreadID mainTID;
12511 uint mainFBO;
12513 // Per frame buffers
12514 GLNVGcall* calls;
12515 int ccalls;
12516 int ncalls;
12517 GLNVGpath* paths;
12518 int cpaths;
12519 int npaths;
12520 NVGVertex* verts;
12521 int cverts;
12522 int nverts;
12523 ubyte* uniforms;
12524 int cuniforms;
12525 int nuniforms;
12526 NVGMatrix lastAffine;
12528 // cached state
12529 static if (NANOVG_GL_USE_STATE_FILTER) {
12530 GLuint boundTexture;
12531 GLuint stencilMask;
12532 GLenum stencilFunc;
12533 GLint stencilFuncRef;
12534 GLuint stencilFuncMask;
12535 GLNVGblend blendFunc;
12539 int glnvg__maxi() (int a, int b) { pragma(inline, true); return (a > b ? a : b); }
12541 void glnvg__bindTexture (GLNVGcontext* gl, GLuint tex) nothrow @trusted @nogc {
12542 static if (NANOVG_GL_USE_STATE_FILTER) {
12543 if (gl.boundTexture != tex) {
12544 gl.boundTexture = tex;
12545 glBindTexture(GL_TEXTURE_2D, tex);
12547 } else {
12548 glBindTexture(GL_TEXTURE_2D, tex);
12552 void glnvg__stencilMask (GLNVGcontext* gl, GLuint mask) nothrow @trusted @nogc {
12553 static if (NANOVG_GL_USE_STATE_FILTER) {
12554 if (gl.stencilMask != mask) {
12555 gl.stencilMask = mask;
12556 glStencilMask(mask);
12558 } else {
12559 glStencilMask(mask);
12563 void glnvg__stencilFunc (GLNVGcontext* gl, GLenum func, GLint ref_, GLuint mask) nothrow @trusted @nogc {
12564 static if (NANOVG_GL_USE_STATE_FILTER) {
12565 if (gl.stencilFunc != func || gl.stencilFuncRef != ref_ || gl.stencilFuncMask != mask) {
12566 gl.stencilFunc = func;
12567 gl.stencilFuncRef = ref_;
12568 gl.stencilFuncMask = mask;
12569 glStencilFunc(func, ref_, mask);
12571 } else {
12572 glStencilFunc(func, ref_, mask);
12576 // texture id is never zero
12577 // sets refcount to one
12578 GLNVGtexture* glnvg__allocTexture (GLNVGcontext* gl) nothrow @trusted @nogc {
12579 GLNVGtexture* tex = null;
12581 int tid = gl.freetexid;
12582 if (tid == -1) {
12583 if (gl.ntextures >= gl.ctextures) {
12584 assert(gl.ntextures == gl.ctextures);
12585 //pragma(msg, GLNVGtexture.sizeof*32);
12586 int ctextures = (gl.ctextures == 0 ? 32 : gl.ctextures+gl.ctextures/2); // 1.5x overallocate
12587 GLNVGtexture* textures = cast(GLNVGtexture*)realloc(gl.textures, GLNVGtexture.sizeof*ctextures);
12588 if (textures is null) assert(0, "NanoVega: out of memory for textures");
12589 memset(&textures[gl.ctextures], 0, (ctextures-gl.ctextures)*GLNVGtexture.sizeof);
12590 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated more textures (n=%d; c=%d; nc=%d)\n", gl.ntextures, gl.ctextures, ctextures); }}
12591 gl.textures = textures;
12592 gl.ctextures = ctextures;
12594 assert(gl.ntextures+1 <= gl.ctextures);
12595 tid = gl.ntextures++;
12596 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf(" got next free texture id %d, ntextures=%d\n", tid+1, gl.ntextures); }}
12597 } else {
12598 gl.freetexid = gl.textures[tid].nextfree;
12600 assert(tid <= gl.ntextures);
12602 assert(gl.textures[tid].id == 0);
12603 tex = &gl.textures[tid];
12604 memset(tex, 0, (*tex).sizeof);
12605 tex.id = tid+1;
12606 tex.rc = 1;
12607 tex.nextfree = -1;
12609 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated texture with id %d (%d)\n", tex.id, tid+1); }}
12611 return tex;
12614 GLNVGtexture* glnvg__findTexture (GLNVGcontext* gl, int id) nothrow @trusted @nogc {
12615 if (id <= 0 || id > gl.ntextures) return null;
12616 if (gl.textures[id-1].id == 0) return null; // free one
12617 assert(gl.textures[id-1].id == id);
12618 return &gl.textures[id-1];
12621 bool glnvg__deleteTexture (GLNVGcontext* gl, ref int id) nothrow @trusted @nogc {
12622 if (id <= 0 || id > gl.ntextures) return false;
12623 auto tx = &gl.textures[id-1];
12624 if (tx.id == 0) { id = 0; return false; } // free one
12625 assert(tx.id == id);
12626 assert(tx.tex != 0);
12627 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("decrefing texture with id %d (%d)\n", tx.id, id); }}
12628 import core.atomic : atomicOp;
12629 if (atomicOp!"-="(tx.rc, 1) == 0) {
12630 import core.thread : ThreadID;
12631 ThreadID mytid;
12632 static if (__VERSION__ < 2076) {
12633 DGNoThrowNoGC(() {
12634 import core.thread; mytid = Thread.getThis.id;
12635 })();
12636 } else {
12637 try { import core.thread; mytid = Thread.getThis.id; } catch (Exception e) {}
12639 if (gl.mainTID == mytid && gl.inFrame) {
12640 // can delete it right now
12641 if ((tx.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tx.tex);
12642 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** deleted texture with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }}
12643 memset(tx, 0, (*tx).sizeof);
12644 //{ import core.stdc.stdio; printf("deleting texture with id %d\n", id); }
12645 tx.nextfree = gl.freetexid;
12646 gl.freetexid = id-1;
12647 } else {
12648 // alas, we aren't doing frame business, so we should postpone deletion
12649 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** POSTPONED texture deletion with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }}
12650 version(aliced) {
12651 synchronized(GLNVGTextureLocker.classinfo) {
12652 tx.id = 0; // mark it as dead
12653 gl.mustCleanTextures = true; // set "need cleanup" flag
12655 } else {
12656 try {
12657 synchronized(GLNVGTextureLocker.classinfo) {
12658 tx.id = 0; // mark it as dead
12659 gl.mustCleanTextures = true; // set "need cleanup" flag
12661 } catch (Exception e) {}
12665 id = 0;
12666 return true;
12669 void glnvg__dumpShaderError (GLuint shader, const(char)* name, const(char)* type) nothrow @trusted @nogc {
12670 import core.stdc.stdio : fprintf, stderr;
12671 GLchar[512+1] str = 0;
12672 GLsizei len = 0;
12673 glGetShaderInfoLog(shader, 512, &len, str.ptr);
12674 if (len > 512) len = 512;
12675 str[len] = '\0';
12676 fprintf(stderr, "Shader %s/%s error:\n%s\n", name, type, str.ptr);
12679 void glnvg__dumpProgramError (GLuint prog, const(char)* name) nothrow @trusted @nogc {
12680 import core.stdc.stdio : fprintf, stderr;
12681 GLchar[512+1] str = 0;
12682 GLsizei len = 0;
12683 glGetProgramInfoLog(prog, 512, &len, str.ptr);
12684 if (len > 512) len = 512;
12685 str[len] = '\0';
12686 fprintf(stderr, "Program %s error:\n%s\n", name, str.ptr);
12689 void glnvg__resetError(bool force=false) (GLNVGcontext* gl) nothrow @trusted @nogc {
12690 static if (!force) {
12691 if ((gl.flags&NVGContextFlag.Debug) == 0) return;
12693 glGetError();
12696 void glnvg__checkError(bool force=false) (GLNVGcontext* gl, const(char)* str) nothrow @trusted @nogc {
12697 GLenum err;
12698 static if (!force) {
12699 if ((gl.flags&NVGContextFlag.Debug) == 0) return;
12701 err = glGetError();
12702 if (err != GL_NO_ERROR) {
12703 import core.stdc.stdio : fprintf, stderr;
12704 fprintf(stderr, "Error %08x after %s\n", err, str);
12705 return;
12709 bool glnvg__createShader (GLNVGshader* shader, const(char)* name, const(char)* header, const(char)* opts, const(char)* vshader, const(char)* fshader) nothrow @trusted @nogc {
12710 GLint status;
12711 GLuint prog, vert, frag;
12712 const(char)*[3] str;
12714 memset(shader, 0, (*shader).sizeof);
12716 prog = glCreateProgram();
12717 vert = glCreateShader(GL_VERTEX_SHADER);
12718 frag = glCreateShader(GL_FRAGMENT_SHADER);
12719 str[0] = header;
12720 str[1] = (opts !is null ? opts : "");
12721 str[2] = vshader;
12722 glShaderSource(vert, 3, cast(const(char)**)str.ptr, null);
12724 glCompileShader(vert);
12725 glGetShaderiv(vert, GL_COMPILE_STATUS, &status);
12726 if (status != GL_TRUE) {
12727 glnvg__dumpShaderError(vert, name, "vert");
12728 return false;
12731 str[0] = header;
12732 str[1] = (opts !is null ? opts : "");
12733 str[2] = fshader;
12734 glShaderSource(frag, 3, cast(const(char)**)str.ptr, null);
12736 glCompileShader(frag);
12737 glGetShaderiv(frag, GL_COMPILE_STATUS, &status);
12738 if (status != GL_TRUE) {
12739 glnvg__dumpShaderError(frag, name, "frag");
12740 return false;
12743 glAttachShader(prog, vert);
12744 glAttachShader(prog, frag);
12746 glBindAttribLocation(prog, 0, "vertex");
12747 glBindAttribLocation(prog, 1, "tcoord");
12749 glLinkProgram(prog);
12750 glGetProgramiv(prog, GL_LINK_STATUS, &status);
12751 if (status != GL_TRUE) {
12752 glnvg__dumpProgramError(prog, name);
12753 return false;
12756 shader.prog = prog;
12757 shader.vert = vert;
12758 shader.frag = frag;
12760 return true;
12763 void glnvg__deleteShader (GLNVGshader* shader) nothrow @trusted @nogc {
12764 if (shader.prog != 0) glDeleteProgram(shader.prog);
12765 if (shader.vert != 0) glDeleteShader(shader.vert);
12766 if (shader.frag != 0) glDeleteShader(shader.frag);
12769 void glnvg__getUniforms (GLNVGshader* shader) nothrow @trusted @nogc {
12770 shader.loc[GLNVGuniformLoc.ViewSize] = glGetUniformLocation(shader.prog, "viewSize");
12771 shader.loc[GLNVGuniformLoc.Tex] = glGetUniformLocation(shader.prog, "tex");
12772 shader.loc[GLNVGuniformLoc.Frag] = glGetUniformLocation(shader.prog, "frag");
12773 shader.loc[GLNVGuniformLoc.TMat] = glGetUniformLocation(shader.prog, "tmat");
12774 shader.loc[GLNVGuniformLoc.TTr] = glGetUniformLocation(shader.prog, "ttr");
12775 shader.loc[GLNVGuniformLoc.ClipTex] = glGetUniformLocation(shader.prog, "clipTex");
12778 void glnvg__killFBOs (GLNVGcontext* gl) nothrow @trusted @nogc {
12779 foreach (immutable fidx, ref GLuint fbo; gl.fbo[]) {
12780 if (fbo != 0) {
12781 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
12782 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
12783 foreach (ref GLuint tid; gl.fboTex.ptr[fidx][]) if (tid != 0) { glDeleteTextures(1, &tid); tid = 0; }
12784 glDeleteFramebuffers(1, &fbo);
12785 fbo = 0;
12788 gl.fboWidth = gl.fboHeight = 0;
12791 // returns `true` is new FBO was created
12792 // will not unbind buffer, if it was created
12793 bool glnvg__allocFBO (GLNVGcontext* gl, int fidx, bool doclear=true) nothrow @trusted @nogc {
12794 assert(fidx >= 0 && fidx < gl.fbo.length);
12795 assert(gl.fboWidth > 0);
12796 assert(gl.fboHeight > 0);
12798 if (gl.fbo.ptr[fidx] != 0) return false; // nothing to do, this FBO is already initialized
12800 glnvg__resetError(gl);
12802 // allocate FBO object
12803 GLuint fbo = 0;
12804 glGenFramebuffers(1, &fbo);
12805 if (fbo == 0) assert(0, "NanoVega: cannot create FBO");
12806 glnvg__checkError(gl, "glnvg__allocFBO: glGenFramebuffers");
12807 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
12808 //scope(exit) glBindFramebuffer(GL_FRAMEBUFFER, 0);
12810 // attach 2D texture to this FBO
12811 GLuint tidColor = 0;
12812 glGenTextures(1, &tidColor);
12813 if (tidColor == 0) assert(0, "NanoVega: cannot create RGBA texture for FBO");
12814 glBindTexture(GL_TEXTURE_2D, tidColor);
12815 //scope(exit) glBindTexture(GL_TEXTURE_2D, 0);
12816 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
12817 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_S");
12818 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
12819 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_T");
12820 //FIXME: linear or nearest?
12821 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
12822 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
12823 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MIN_FILTER");
12824 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
12825 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
12826 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MAG_FILTER");
12827 // empty texture
12828 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
12829 // create texture with only one color channel
12830 glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, gl.fboWidth, gl.fboHeight, 0, GL_RED, GL_UNSIGNED_BYTE, null);
12831 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
12832 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (color)");
12833 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tidColor, 0);
12834 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (color)");
12836 // attach stencil texture to this FBO
12837 GLuint tidStencil = 0;
12838 version(nanovega_shared_stencil) {
12839 if (gl.fboTex.ptr[0].ptr[0] == 0) {
12840 glGenTextures(1, &tidStencil);
12841 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO");
12842 gl.fboTex.ptr[0].ptr[0] = tidStencil;
12843 } else {
12844 tidStencil = gl.fboTex.ptr[0].ptr[0];
12846 if (fidx != 0) gl.fboTex.ptr[fidx].ptr[1] = 0; // stencil texture is shared among FBOs
12847 } else {
12848 glGenTextures(1, &tidStencil);
12849 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO");
12850 gl.fboTex.ptr[0].ptr[0] = tidStencil;
12852 glBindTexture(GL_TEXTURE_2D, tidStencil);
12853 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, gl.fboWidth, gl.fboHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, null);
12854 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (stencil)");
12855 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tidStencil, 0);
12856 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (stencil)");
12859 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
12860 if (status != GL_FRAMEBUFFER_COMPLETE) {
12861 version(all) {
12862 import core.stdc.stdio;
12863 if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) printf("fucked attachement\n");
12864 if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) printf("fucked dimensions\n");
12865 if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) printf("missing attachement\n");
12866 if (status == GL_FRAMEBUFFER_UNSUPPORTED) printf("unsupported\n");
12868 assert(0, "NanoVega: framebuffer creation failed");
12872 // clear 'em all
12873 if (doclear) {
12874 glClearColor(0, 0, 0, 0);
12875 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
12878 // save texture ids
12879 gl.fbo.ptr[fidx] = fbo;
12880 gl.fboTex.ptr[fidx].ptr[0] = tidColor;
12881 version(nanovega_shared_stencil) {} else {
12882 gl.fboTex.ptr[fidx].ptr[1] = tidStencil;
12885 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture);
12887 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): created with index %d\n", gl.msp-1, fidx); }
12889 return true;
12892 // will not unbind buffer
12893 void glnvg__clearFBO (GLNVGcontext* gl, int fidx) nothrow @trusted @nogc {
12894 assert(fidx >= 0 && fidx < gl.fbo.length);
12895 assert(gl.fboWidth > 0);
12896 assert(gl.fboHeight > 0);
12897 assert(gl.fbo.ptr[fidx] != 0);
12898 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[fidx]);
12899 glClearColor(0, 0, 0, 0);
12900 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
12901 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cleared with index %d\n", gl.msp-1, fidx); }
12904 // will not unbind buffer
12905 void glnvg__copyFBOToFrom (GLNVGcontext* gl, int didx, int sidx) nothrow @trusted @nogc {
12906 import core.stdc.string : memset;
12907 assert(didx >= 0 && didx < gl.fbo.length);
12908 assert(sidx >= 0 && sidx < gl.fbo.length);
12909 assert(gl.fboWidth > 0);
12910 assert(gl.fboHeight > 0);
12911 assert(gl.fbo.ptr[didx] != 0);
12912 assert(gl.fbo.ptr[sidx] != 0);
12913 if (didx == sidx) return;
12915 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copy FBO: %d -> %d\n", gl.msp-1, sidx, didx); }
12917 glUseProgram(gl.shaderCopyFBO.prog);
12919 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[didx]);
12920 glDisable(GL_CULL_FACE);
12921 glDisable(GL_BLEND);
12922 glDisable(GL_SCISSOR_TEST);
12923 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[sidx].ptr[0]);
12924 // copy texture by drawing full quad
12925 enum x = 0;
12926 enum y = 0;
12927 immutable int w = gl.fboWidth;
12928 immutable int h = gl.fboHeight;
12929 glBegin(GL_QUADS);
12930 glVertex2i(x, y); // top-left
12931 glVertex2i(w, y); // top-right
12932 glVertex2i(w, h); // bottom-right
12933 glVertex2i(x, h); // bottom-left
12934 glEnd();
12936 // restore state (but don't unbind FBO)
12937 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture);
12938 glEnable(GL_CULL_FACE);
12939 glEnable(GL_BLEND);
12940 glUseProgram(gl.shader.prog);
12943 void glnvg__resetFBOClipTextureCache (GLNVGcontext* gl) nothrow @trusted @nogc {
12944 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): texture cache invalidated (%d)\n", gl.msp-1, gl.lastClipFBO); }
12946 if (gl.lastClipFBO >= 0) {
12947 glActiveTexture(GL_TEXTURE1);
12948 glBindTexture(GL_TEXTURE_2D, 0);
12949 glActiveTexture(GL_TEXTURE0);
12952 gl.lastClipFBO = -666;
12955 void glnvg__setFBOClipTexture (GLNVGcontext* gl, GLNVGfragUniforms* frag) nothrow @trusted @nogc {
12956 //assert(gl.msp > 0 && gl.msp <= gl.maskStack.length);
12957 if (gl.lastClipFBO != -666) {
12958 // cached
12959 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cached (%d)\n", gl.msp-1, gl.lastClipFBO); }
12960 frag.doclip = (gl.lastClipFBO >= 0 ? 1 : 0);
12961 return;
12964 // no cache
12965 int fboidx = -1;
12966 mainloop: foreach_reverse (immutable sp, GLMaskState mst; gl.maskStack.ptr[0..gl.msp]/*; reverse*/) {
12967 final switch (mst) {
12968 case GLMaskState.DontMask: fboidx = -1; break mainloop;
12969 case GLMaskState.Uninitialized: break;
12970 case GLMaskState.Initialized: fboidx = cast(int)sp; break mainloop;
12971 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__setFBOClipTexture()` internal error");
12975 if (fboidx < 0) {
12976 // don't mask
12977 gl.lastClipFBO = -1;
12978 frag.doclip = 0;
12979 } else {
12980 // do masking
12981 assert(gl.fbo.ptr[fboidx] != 0);
12982 gl.lastClipFBO = fboidx;
12983 frag.doclip = 1;
12986 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): new cache (new:%d)\n", gl.msp-1, gl.lastClipFBO); }
12988 if (gl.lastClipFBO >= 0) {
12989 assert(gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
12990 glActiveTexture(GL_TEXTURE1);
12991 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
12992 glActiveTexture(GL_TEXTURE0);
12996 // returns index in `gl.fbo`, or -1 for "don't mask"
12997 int glnvg__generateFBOClipTexture (GLNVGcontext* gl) nothrow @trusted @nogc {
12998 assert(gl.msp > 0 && gl.msp <= gl.maskStack.length);
12999 // we need initialized FBO, even for "don't mask" case
13000 // for this, look back in stack, and either copy initialized FBO,
13001 // or stop at first uninitialized one, and clear it
13002 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.Initialized) {
13003 // shortcut
13004 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); }
13005 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[gl.msp-1]);
13006 return gl.msp-1;
13008 foreach_reverse (immutable sp; 0..gl.msp/*; reverse*/) {
13009 final switch (gl.maskStack.ptr[sp]) {
13010 case GLMaskState.DontMask:
13011 // clear it
13012 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture\n", gl.msp-1); }
13013 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1);
13014 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared;
13015 return gl.msp-1;
13016 case GLMaskState.Uninitialized: break; // do nothing
13017 case GLMaskState.Initialized:
13018 // i found her! copy to TOS
13019 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copying texture from %d\n", gl.msp-1, cast(int)sp); }
13020 glnvg__allocFBO(gl, gl.msp-1, false);
13021 glnvg__copyFBOToFrom(gl, gl.msp-1, sp);
13022 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized;
13023 return gl.msp-1;
13024 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__generateFBOClipTexture()` internal error");
13027 // nothing was initialized, lol
13028 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture (first one)\n", gl.msp-1); }
13029 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1);
13030 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared;
13031 return gl.msp-1;
13034 void glnvg__renderPushClip (void* uptr) nothrow @trusted @nogc {
13035 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13036 GLNVGcall* call = glnvg__allocCall(gl);
13037 if (call is null) return;
13038 call.type = GLNVG_PUSHCLIP;
13041 void glnvg__renderPopClip (void* uptr) nothrow @trusted @nogc {
13042 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13043 GLNVGcall* call = glnvg__allocCall(gl);
13044 if (call is null) return;
13045 call.type = GLNVG_POPCLIP;
13048 void glnvg__renderResetClip (void* uptr) nothrow @trusted @nogc {
13049 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13050 GLNVGcall* call = glnvg__allocCall(gl);
13051 if (call is null) return;
13052 call.type = GLNVG_RESETCLIP;
13055 void glnvg__clipDebugDump (void* uptr, bool doit) nothrow @trusted @nogc {
13056 version(nanovega_debug_clipping) {
13057 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13058 GLNVGcall* call = glnvg__allocCall(gl);
13059 call.type = (doit ? GLNVG_CLIP_DDUMP_ON : GLNVG_CLIP_DDUMP_OFF);
13063 bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc {
13064 import core.stdc.stdio : snprintf;
13066 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13067 enum align_ = 4;
13069 char[64] shaderHeader = void;
13070 //enum shaderHeader = "#define UNIFORM_ARRAY_SIZE 12\n";
13071 snprintf(shaderHeader.ptr, shaderHeader.length, "#define UNIFORM_ARRAY_SIZE %u\n", cast(uint)GLNVGfragUniforms.UNIFORM_ARRAY_SIZE);
13073 enum fillVertShader = q{
13074 uniform vec2 viewSize;
13075 attribute vec2 vertex;
13076 attribute vec2 tcoord;
13077 varying vec2 ftcoord;
13078 varying vec2 fpos;
13079 uniform vec4 tmat; /* abcd of affine matrix: xyzw */
13080 uniform vec2 ttr; /* tx and ty of affine matrix */
13081 void main (void) {
13082 /* affine transformation */
13083 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x;
13084 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y;
13085 ftcoord = tcoord;
13086 fpos = vec2(nx, ny);
13087 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1);
13091 enum fillFragShader = q{
13092 uniform vec4 frag[UNIFORM_ARRAY_SIZE];
13093 uniform sampler2D tex;
13094 uniform sampler2D clipTex;
13095 uniform vec2 viewSize;
13096 varying vec2 ftcoord;
13097 varying vec2 fpos;
13098 #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz)
13099 #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz)
13100 #define innerCol frag[6]
13101 #define middleCol frag[7]
13102 #define outerCol frag[7+1]
13103 #define scissorExt frag[8+1].xy
13104 #define scissorScale frag[8+1].zw
13105 #define extent frag[9+1].xy
13106 #define radius frag[9+1].z
13107 #define feather frag[9+1].w
13108 #define strokeMult frag[10+1].x
13109 #define strokeThr frag[10+1].y
13110 #define texType int(frag[10+1].z)
13111 #define type int(frag[10+1].w)
13112 #define doclip int(frag[11+1].x)
13113 #define midp frag[11+1].y
13115 float sdroundrect (in vec2 pt, in vec2 ext, in float rad) {
13116 vec2 ext2 = ext-vec2(rad, rad);
13117 vec2 d = abs(pt)-ext2;
13118 return min(max(d.x, d.y), 0.0)+length(max(d, 0.0))-rad;
13121 // Scissoring
13122 float scissorMask (in vec2 p) {
13123 vec2 sc = (abs((scissorMat*vec3(p, 1.0)).xy)-scissorExt);
13124 sc = vec2(0.5, 0.5)-sc*scissorScale;
13125 return clamp(sc.x, 0.0, 1.0)*clamp(sc.y, 0.0, 1.0);
13128 #ifdef EDGE_AA
13129 // Stroke - from [0..1] to clipped pyramid, where the slope is 1px.
13130 float strokeMask () {
13131 return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult)*min(1.0, ftcoord.y);
13133 #endif
13135 void main (void) {
13136 // clipping
13137 if (doclip != 0) {
13138 /*vec4 clr = texelFetch(clipTex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);*/
13139 vec4 clr = texture2D(clipTex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y));
13140 if (clr.r == 0.0) discard;
13142 float scissor = scissorMask(fpos);
13143 if (scissor <= 0.0) discard; //k8: is it really faster?
13144 #ifdef EDGE_AA
13145 float strokeAlpha = strokeMask();
13146 if (strokeAlpha < strokeThr) discard;
13147 #else
13148 float strokeAlpha = 1.0;
13149 #endif
13150 // rendering
13151 vec4 color;
13152 if (type == 0) { /* NSVG_SHADER_FILLCOLOR */
13153 color = innerCol;
13154 // Combine alpha
13155 color *= strokeAlpha*scissor;
13156 } else if (type == 1) { /* NSVG_SHADER_FILLGRAD */
13157 // Gradient
13158 // Calculate gradient color using box gradient
13159 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy;
13160 float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0);
13161 if (midp <= 0.0) {
13162 color = mix(innerCol, outerCol, d);
13163 } else {
13164 float gdst = min(midp, 1.0);
13165 if (d < gdst) {
13166 color = mix(innerCol, middleCol, d/gdst);
13167 } else {
13168 color = mix(middleCol, outerCol, (d-gdst)/gdst);
13171 // Combine alpha
13172 color *= strokeAlpha*scissor;
13173 } else if (type == 2) { /* NSVG_SHADER_FILLIMG */
13174 // Image
13175 // Calculate color from texture
13176 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy/extent;
13177 color = texture2D(tex, pt);
13178 if (texType == 1) color = vec4(color.xyz*color.w, color.w);
13179 if (texType == 2) color = vec4(color.x);
13180 // Apply color tint and alpha
13181 color *= innerCol;
13182 // Combine alpha
13183 color *= strokeAlpha*scissor;
13184 } else if (type == 3) { /* NSVG_SHADER_SIMPLE */
13185 // Stencil fill
13186 color = vec4(1, 1, 1, 1);
13187 } else if (type == 4) { /* NSVG_SHADER_IMG */
13188 // Textured tris
13189 color = texture2D(tex, ftcoord);
13190 if (texType == 1) color = vec4(color.xyz*color.w, color.w);
13191 if (texType == 2) color = vec4(color.x);
13192 color *= scissor;
13193 color *= innerCol; // Apply color tint
13195 gl_FragColor = color;
13199 enum clipVertShaderFill = q{
13200 uniform vec2 viewSize;
13201 attribute vec2 vertex;
13202 uniform vec4 tmat; /* abcd of affine matrix: xyzw */
13203 uniform vec2 ttr; /* tx and ty of affine matrix */
13204 void main (void) {
13205 /* affine transformation */
13206 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x;
13207 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y;
13208 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1);
13212 enum clipFragShaderFill = q{
13213 uniform vec2 viewSize;
13214 void main (void) {
13215 gl_FragColor = vec4(1, 1, 1, 1);
13219 enum clipVertShaderCopy = q{
13220 uniform vec2 viewSize;
13221 attribute vec2 vertex;
13222 void main (void) {
13223 gl_Position = vec4(2.0*vertex.x/viewSize.x-1.0, 1.0-2.0*vertex.y/viewSize.y, 0, 1);
13227 enum clipFragShaderCopy = q{
13228 uniform sampler2D tex;
13229 uniform vec2 viewSize;
13230 void main (void) {
13231 //gl_FragColor = texelFetch(tex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);
13232 gl_FragColor = texture2D(tex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y));
13236 glnvg__checkError(gl, "init");
13238 string defines = (gl.flags&NVGContextFlag.Antialias ? "#define EDGE_AA 1\n" : null);
13239 if (!glnvg__createShader(&gl.shader, "shader", shaderHeader.ptr, defines.ptr, fillVertShader, fillFragShader)) return false;
13240 if (!glnvg__createShader(&gl.shaderFillFBO, "shaderFillFBO", shaderHeader.ptr, defines.ptr, clipVertShaderFill, clipFragShaderFill)) return false;
13241 if (!glnvg__createShader(&gl.shaderCopyFBO, "shaderCopyFBO", shaderHeader.ptr, defines.ptr, clipVertShaderCopy, clipFragShaderCopy)) return false;
13243 glnvg__checkError(gl, "uniform locations");
13244 glnvg__getUniforms(&gl.shader);
13245 glnvg__getUniforms(&gl.shaderFillFBO);
13246 glnvg__getUniforms(&gl.shaderCopyFBO);
13248 // Create dynamic vertex array
13249 glGenBuffers(1, &gl.vertBuf);
13251 gl.fragSize = GLNVGfragUniforms.sizeof+align_-GLNVGfragUniforms.sizeof%align_;
13253 glnvg__checkError(gl, "create done");
13255 glFinish();
13257 return true;
13260 int glnvg__renderCreateTexture (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc {
13261 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13262 GLNVGtexture* tex = glnvg__allocTexture(gl);
13264 if (tex is null) return 0;
13266 glGenTextures(1, &tex.tex);
13267 tex.width = w;
13268 tex.height = h;
13269 tex.type = type;
13270 tex.flags = imageFlags;
13271 glnvg__bindTexture(gl, tex.tex);
13273 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("created texture with id %d; glid=%u\n", tex.id, tex.tex); }}
13275 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
13276 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width);
13277 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13278 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13280 // GL 1.4 and later has support for generating mipmaps using a tex parameter.
13281 if ((imageFlags&(NVGImageFlag.GenerateMipmaps|NVGImageFlag.NoFiltering)) == NVGImageFlag.GenerateMipmaps) glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
13283 immutable ttype = (type == NVGtexture.RGBA ? GL_RGBA : GL_RED);
13284 glTexImage2D(GL_TEXTURE_2D, 0, ttype, w, h, 0, ttype, GL_UNSIGNED_BYTE, data);
13286 immutable tfmin =
13287 (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST :
13288 imageFlags&NVGImageFlag.GenerateMipmaps ? GL_LINEAR_MIPMAP_LINEAR :
13289 GL_LINEAR);
13290 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tfmin);
13291 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST : GL_LINEAR));
13293 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, (imageFlags&NVGImageFlag.RepeatX ? GL_REPEAT : GL_CLAMP_TO_EDGE));
13294 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, (imageFlags&NVGImageFlag.RepeatY ? GL_REPEAT : GL_CLAMP_TO_EDGE));
13296 glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
13297 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
13298 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13299 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13301 glnvg__checkError(gl, "create tex");
13302 glnvg__bindTexture(gl, 0);
13304 return tex.id;
13307 bool glnvg__renderDeleteTexture (void* uptr, int image) nothrow @trusted @nogc {
13308 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13309 return glnvg__deleteTexture(gl, image);
13312 bool glnvg__renderTextureIncRef (void* uptr, int image) nothrow @trusted @nogc {
13313 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13314 GLNVGtexture* tex = glnvg__findTexture(gl, image);
13315 if (tex is null) {
13316 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT incref texture with id %d\n", image); }}
13317 return false;
13319 import core.atomic : atomicOp;
13320 atomicOp!"+="(tex.rc, 1);
13321 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("texture #%d: incref; newref=%d\n", image, tex.rc); }}
13322 return true;
13325 bool glnvg__renderUpdateTexture (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc {
13326 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13327 GLNVGtexture* tex = glnvg__findTexture(gl, image);
13329 if (tex is null) {
13330 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT update texture with id %d\n", image); }}
13331 return false;
13334 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("updated texture with id %d; glid=%u\n", tex.id, image, tex.tex); }}
13336 glnvg__bindTexture(gl, tex.tex);
13338 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
13339 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width);
13340 glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
13341 glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
13343 immutable ttype = (tex.type == NVGtexture.RGBA ? GL_RGBA : GL_RED);
13344 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, ttype, GL_UNSIGNED_BYTE, data);
13346 glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
13347 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
13348 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13349 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13351 glnvg__bindTexture(gl, 0);
13353 return true;
13356 bool glnvg__renderGetTextureSize (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc {
13357 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13358 GLNVGtexture* tex = glnvg__findTexture(gl, image);
13359 if (tex is null) {
13360 if (w !is null) *w = 0;
13361 if (h !is null) *h = 0;
13362 return false;
13363 } else {
13364 if (w !is null) *w = tex.width;
13365 if (h !is null) *h = tex.height;
13366 return true;
13370 void glnvg__xformToMat3x4 (float[] m3, const(float)[] t) nothrow @trusted @nogc {
13371 assert(t.length >= 6);
13372 assert(m3.length >= 12);
13373 m3.ptr[0] = t.ptr[0];
13374 m3.ptr[1] = t.ptr[1];
13375 m3.ptr[2] = 0.0f;
13376 m3.ptr[3] = 0.0f;
13377 m3.ptr[4] = t.ptr[2];
13378 m3.ptr[5] = t.ptr[3];
13379 m3.ptr[6] = 0.0f;
13380 m3.ptr[7] = 0.0f;
13381 m3.ptr[8] = t.ptr[4];
13382 m3.ptr[9] = t.ptr[5];
13383 m3.ptr[10] = 1.0f;
13384 m3.ptr[11] = 0.0f;
13387 NVGColor glnvg__premulColor() (in auto ref NVGColor c) nothrow @trusted @nogc {
13388 //pragma(inline, true);
13389 NVGColor res = void;
13390 res.r = c.r*c.a;
13391 res.g = c.g*c.a;
13392 res.b = c.b*c.a;
13393 res.a = c.a;
13394 return res;
13397 bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* paint, NVGscissor* scissor, float width, float fringe, float strokeThr) nothrow @trusted @nogc {
13398 import core.stdc.math : sqrtf;
13399 GLNVGtexture* tex = null;
13400 NVGMatrix invxform = void;
13402 memset(frag, 0, (*frag).sizeof);
13404 if (fringe <= 0.0f) fringe = 1.0f;
13406 frag.innerCol = glnvg__premulColor(paint.innerColor);
13407 frag.middleCol = glnvg__premulColor(paint.middleColor);
13408 frag.outerCol = glnvg__premulColor(paint.outerColor);
13409 frag.midp = paint.midp;
13411 if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) {
13412 memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof);
13413 frag.scissorExt.ptr[0] = 1.0f;
13414 frag.scissorExt.ptr[1] = 1.0f;
13415 frag.scissorScale.ptr[0] = 1.0f;
13416 frag.scissorScale.ptr[1] = 1.0f;
13417 } else {
13418 //nvgTransformInverse(invxform[], scissor.xform[]);
13419 invxform = scissor.xform.inverted;
13420 glnvg__xformToMat3x4(frag.scissorMat[], invxform.mat[]);
13421 frag.scissorExt.ptr[0] = scissor.extent.ptr[0];
13422 frag.scissorExt.ptr[1] = scissor.extent.ptr[1];
13423 frag.scissorScale.ptr[0] = sqrtf(scissor.xform.mat.ptr[0]*scissor.xform.mat.ptr[0]+scissor.xform.mat.ptr[2]*scissor.xform.mat.ptr[2])/fringe;
13424 frag.scissorScale.ptr[1] = sqrtf(scissor.xform.mat.ptr[1]*scissor.xform.mat.ptr[1]+scissor.xform.mat.ptr[3]*scissor.xform.mat.ptr[3])/fringe;
13427 memcpy(frag.extent.ptr, paint.extent.ptr, frag.extent.sizeof);
13428 frag.strokeMult = (width*0.5f+fringe*0.5f)/fringe;
13429 frag.strokeThr = strokeThr;
13431 if (paint.image.valid) {
13432 tex = glnvg__findTexture(gl, paint.image.id);
13433 if (tex is null) return false;
13434 if ((tex.flags&NVGImageFlag.FlipY) != 0) {
13436 NVGMatrix flipped;
13437 nvgTransformScale(flipped[], 1.0f, -1.0f);
13438 nvgTransformMultiply(flipped[], paint.xform[]);
13439 nvgTransformInverse(invxform[], flipped[]);
13442 NVGMatrix m1 = void, m2 = void;
13443 nvgTransformTranslate(m1[], 0.0f, frag.extent.ptr[1]*0.5f);
13444 nvgTransformMultiply(m1[], paint.xform[]);
13445 nvgTransformScale(m2[], 1.0f, -1.0f);
13446 nvgTransformMultiply(m2[], m1[]);
13447 nvgTransformTranslate(m1[], 0.0f, -frag.extent.ptr[1]*0.5f);
13448 nvgTransformMultiply(m1[], m2[]);
13449 nvgTransformInverse(invxform[], m1[]);
13451 NVGMatrix m1 = NVGMatrix.Translated(0.0f, frag.extent.ptr[1]*0.5f);
13452 m1.mul(paint.xform);
13453 NVGMatrix m2 = NVGMatrix.Scaled(1.0f, -1.0f);
13454 m2.mul(m1);
13455 m1 = NVGMatrix.Translated(0.0f, -frag.extent.ptr[1]*0.5f);
13456 m1.mul(m2);
13457 invxform = m1.inverted;
13458 } else {
13459 //nvgTransformInverse(invxform[], paint.xform[]);
13460 invxform = paint.xform.inverted;
13462 frag.type = NSVG_SHADER_FILLIMG;
13464 if (tex.type == NVGtexture.RGBA) {
13465 frag.texType = (tex.flags&NVGImageFlag.Premultiplied ? 0 : 1);
13466 } else {
13467 frag.texType = 2;
13469 //printf("frag.texType = %d\n", frag.texType);
13470 } else {
13471 frag.type = (paint.simpleColor ? NSVG_SHADER_FILLCOLOR : NSVG_SHADER_FILLGRAD);
13472 frag.radius = paint.radius;
13473 frag.feather = paint.feather;
13474 //nvgTransformInverse(invxform[], paint.xform[]);
13475 invxform = paint.xform.inverted;
13478 glnvg__xformToMat3x4(frag.paintMat[], invxform.mat[]);
13480 return true;
13483 void glnvg__setUniforms (GLNVGcontext* gl, int uniformOffset, int image) nothrow @trusted @nogc {
13484 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset);
13485 glnvg__setFBOClipTexture(gl, frag);
13486 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.Frag], frag.UNIFORM_ARRAY_SIZE, &(frag.uniformArray.ptr[0].ptr[0]));
13487 glnvg__checkError(gl, "glnvg__setUniforms");
13488 if (image != 0) {
13489 GLNVGtexture* tex = glnvg__findTexture(gl, image);
13490 glnvg__bindTexture(gl, (tex !is null ? tex.tex : 0));
13491 glnvg__checkError(gl, "tex paint tex");
13492 } else {
13493 glnvg__bindTexture(gl, 0);
13497 void glnvg__finishClip (GLNVGcontext* gl, NVGClipMode clipmode) nothrow @trusted @nogc {
13498 assert(clipmode != NVGClipMode.None);
13500 // fill FBO, clear stencil buffer
13501 //TODO: optimize with bounds?
13502 version(all) {
13503 //glnvg__resetAffine(gl);
13504 //glUseProgram(gl.shaderFillFBO.prog);
13505 glDisable(GL_CULL_FACE);
13506 glDisable(GL_BLEND);
13507 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13508 glEnable(GL_STENCIL_TEST);
13509 if (gl.doClipUnion) {
13510 // for "and" we should clear everything that is NOT stencil-masked
13511 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13512 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13513 } else {
13514 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x00, 0xff);
13515 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13517 glBegin(GL_QUADS);
13518 glVertex2i(0, 0);
13519 glVertex2i(0, gl.fboHeight);
13520 glVertex2i(gl.fboWidth, gl.fboHeight);
13521 glVertex2i(gl.fboWidth, 0);
13522 glEnd();
13523 //glnvg__restoreAffine(gl);
13526 glBindFramebuffer(GL_FRAMEBUFFER, gl.mainFBO);
13527 glDisable(GL_COLOR_LOGIC_OP);
13528 //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // done above
13529 glEnable(GL_BLEND);
13530 glDisable(GL_STENCIL_TEST);
13531 glEnable(GL_CULL_FACE);
13532 glUseProgram(gl.shader.prog);
13534 // set current FBO as used one
13535 assert(gl.msp > 0 && gl.fbo.ptr[gl.msp-1] > 0 && gl.fboTex.ptr[gl.msp-1].ptr[0] > 0);
13536 if (gl.lastClipFBO != gl.msp-1) {
13537 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); }
13538 gl.lastClipFBO = gl.msp-1;
13539 glActiveTexture(GL_TEXTURE1);
13540 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
13541 glActiveTexture(GL_TEXTURE0);
13545 void glnvg__setClipUniforms (GLNVGcontext* gl, int uniformOffset, NVGClipMode clipmode) nothrow @trusted @nogc {
13546 assert(clipmode != NVGClipMode.None);
13547 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset);
13548 // save uniform offset for `glnvg__finishClip()`
13549 gl.lastClipUniOfs = uniformOffset;
13550 // get FBO index, bind this FBO
13551 immutable int clipTexId = glnvg__generateFBOClipTexture(gl);
13552 assert(clipTexId >= 0);
13553 glUseProgram(gl.shaderFillFBO.prog);
13554 glnvg__checkError(gl, "use");
13555 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[clipTexId]);
13556 // set logic op for clip
13557 gl.doClipUnion = false;
13558 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.JustCleared) {
13559 // it is cleared to zero, we can just draw a path
13560 glDisable(GL_COLOR_LOGIC_OP);
13561 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized;
13562 } else {
13563 glEnable(GL_COLOR_LOGIC_OP);
13564 final switch (clipmode) {
13565 case NVGClipMode.None: assert(0, "wtf?!");
13566 case NVGClipMode.Union: glLogicOp(GL_CLEAR); gl.doClipUnion = true; break; // use `GL_CLEAR` to avoid adding another shader mode
13567 case NVGClipMode.Or: glLogicOp(GL_COPY); break; // GL_OR
13568 case NVGClipMode.Xor: glLogicOp(GL_XOR); break;
13569 case NVGClipMode.Sub: glLogicOp(GL_CLEAR); break;
13570 case NVGClipMode.Replace: glLogicOp(GL_COPY); break;
13573 // set affine matrix
13574 glUniform4fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TMat], 1, gl.lastAffine.mat.ptr);
13575 glnvg__checkError(gl, "affine 0");
13576 glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TTr], 1, gl.lastAffine.mat.ptr+4);
13577 glnvg__checkError(gl, "affine 1");
13578 // setup common OpenGL parameters
13579 glDisable(GL_BLEND);
13580 glDisable(GL_CULL_FACE);
13581 glEnable(GL_STENCIL_TEST);
13582 glnvg__stencilMask(gl, 0xff);
13583 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13584 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
13585 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13588 void glnvg__renderViewport (void* uptr, int width, int height) nothrow @trusted @nogc {
13589 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13590 gl.inFrame = true;
13591 gl.view.ptr[0] = cast(float)width;
13592 gl.view.ptr[1] = cast(float)height;
13593 // kill FBOs if we need to create new ones (flushing will recreate 'em if necessary)
13594 if (width != gl.fboWidth || height != gl.fboHeight) {
13595 glnvg__killFBOs(gl);
13596 gl.fboWidth = width;
13597 gl.fboHeight = height;
13599 gl.msp = 1;
13600 gl.maskStack.ptr[0] = GLMaskState.DontMask;
13601 // texture cleanup
13602 import core.atomic : atomicLoad;
13603 if (atomicLoad(gl.mustCleanTextures)) {
13604 try {
13605 import core.thread : Thread;
13606 static if (__VERSION__ < 2076) {
13607 DGNoThrowNoGC(() {
13608 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13609 })();
13610 } else {
13611 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13613 synchronized(GLNVGTextureLocker.classinfo) {
13614 gl.mustCleanTextures = false;
13615 foreach (immutable tidx, ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) {
13616 // no need to use atomic ops here, as we're locked
13617 if (tex.rc == 0 && tex.tex != 0 && tex.id == 0) {
13618 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** cleaned up texture with glid=%u\n", tex.tex); }}
13619 import core.stdc.string : memset;
13620 if ((tex.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tex.tex);
13621 memset(&tex, 0, tex.sizeof);
13622 tex.nextfree = gl.freetexid;
13623 gl.freetexid = cast(int)tidx;
13627 } catch (Exception e) {}
13631 void glnvg__fill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13632 GLNVGpath* paths = &gl.paths[call.pathOffset];
13633 int npaths = call.pathCount;
13635 if (call.clipmode == NVGClipMode.None) {
13636 // Draw shapes
13637 glEnable(GL_STENCIL_TEST);
13638 glnvg__stencilMask(gl, 0xffU);
13639 glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xffU);
13641 glnvg__setUniforms(gl, call.uniformOffset, 0);
13642 glnvg__checkError(gl, "fill simple");
13644 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13645 if (call.evenOdd) {
13646 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT);
13647 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT);
13648 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
13649 } else {
13650 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
13651 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
13653 glDisable(GL_CULL_FACE);
13654 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13655 glEnable(GL_CULL_FACE);
13657 // Draw anti-aliased pixels
13658 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13659 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image);
13660 glnvg__checkError(gl, "fill fill");
13662 if (gl.flags&NVGContextFlag.Antialias) {
13663 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xffU);
13664 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13665 // Draw fringes
13666 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13669 // Draw fill
13670 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xffU);
13671 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13672 if (call.evenOdd) {
13673 glDisable(GL_CULL_FACE);
13674 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount);
13675 //foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13676 glEnable(GL_CULL_FACE);
13677 } else {
13678 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount);
13681 glDisable(GL_STENCIL_TEST);
13682 } else {
13683 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); // this activates our FBO
13684 glnvg__checkError(gl, "fillclip simple");
13685 glnvg__stencilFunc(gl, GL_ALWAYS, 0x00, 0xffU);
13686 if (call.evenOdd) {
13687 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT);
13688 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT);
13689 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
13690 } else {
13691 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
13692 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
13694 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13695 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13699 void glnvg__convexFill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13700 GLNVGpath* paths = &gl.paths[call.pathOffset];
13701 immutable int npaths = call.pathCount;
13703 if (call.clipmode == NVGClipMode.None) {
13704 glnvg__setUniforms(gl, call.uniformOffset, call.image);
13705 glnvg__checkError(gl, "convex fill");
13706 } else {
13707 glnvg__setClipUniforms(gl, call.uniformOffset, call.clipmode); // this activates our FBO
13708 glnvg__checkError(gl, "clip convex fill");
13710 if (call.evenOdd) glDisable(GL_CULL_FACE);
13712 /* always draw fringes, because we can toggle AA on the fly */
13713 int firstStroke = -1, lastStroke = -1;
13714 foreach (int i; 0..npaths) {
13715 if (paths[i].fillCount) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13716 if (paths[i].strokeCount) {
13717 if (firstStroke < 0) firstStroke = i;
13718 lastStroke = i+1;
13721 // Draw fringes
13722 if (firstStroke >= 0) {
13723 foreach (int i; firstStroke..lastStroke) if (paths[i].strokeCount) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13726 if (gl.flags&NVGContextFlag.Antialias) {
13727 // Draw fringes
13728 foreach (int i; 0..npaths) if (paths[i].strokeCount) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13732 if (call.evenOdd) glEnable(GL_CULL_FACE);
13733 if (call.clipmode == NVGClipMode.None) {
13734 /* nothing to do here */
13735 } else {
13736 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13740 void glnvg__stroke (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13741 GLNVGpath* paths = &gl.paths[call.pathOffset];
13742 int npaths = call.pathCount;
13744 if (call.clipmode == NVGClipMode.None) {
13745 if (gl.flags&NVGContextFlag.StencilStrokes) {
13746 glEnable(GL_STENCIL_TEST);
13747 glnvg__stencilMask(gl, 0xff);
13749 // Fill the stroke base without overlap
13750 glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff);
13751 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
13752 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image);
13753 glnvg__checkError(gl, "stroke fill 0");
13754 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13756 // Draw anti-aliased pixels.
13757 glnvg__setUniforms(gl, call.uniformOffset, call.image);
13758 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13759 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13760 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13762 // Clear stencil buffer.
13763 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13764 glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff);
13765 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13766 glnvg__checkError(gl, "stroke fill 1");
13767 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13768 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13770 glDisable(GL_STENCIL_TEST);
13772 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f);
13773 } else {
13774 glnvg__setUniforms(gl, call.uniformOffset, call.image);
13775 glnvg__checkError(gl, "stroke fill");
13776 // Draw Strokes
13777 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13779 } else {
13780 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode);
13781 glnvg__checkError(gl, "stroke fill 0");
13782 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13783 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13787 void glnvg__triangles (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13788 if (call.clipmode == NVGClipMode.None) {
13789 glnvg__setUniforms(gl, call.uniformOffset, call.image);
13790 glnvg__checkError(gl, "triangles fill");
13791 glDrawArrays(GL_TRIANGLES, call.triangleOffset, call.triangleCount);
13792 } else {
13793 //TODO(?): use texture as mask?
13797 void glnvg__affine (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13798 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, call.affine.mat.ptr);
13799 glnvg__checkError(gl, "affine");
13800 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, call.affine.mat.ptr+4);
13801 glnvg__checkError(gl, "affine");
13802 //glnvg__setUniforms(gl, call.uniformOffset, call.image);
13805 void glnvg__renderCancelInternal (GLNVGcontext* gl, bool clearTextures) nothrow @trusted @nogc {
13806 scope(exit) gl.inFrame = false;
13807 if (clearTextures && gl.inFrame) {
13808 try {
13809 import core.thread : Thread;
13810 static if (__VERSION__ < 2076) {
13811 DGNoThrowNoGC(() {
13812 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13813 })();
13814 } else {
13815 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13817 } catch (Exception e) {}
13818 foreach (ref GLNVGcall c; gl.calls[0..gl.ncalls]) if (c.image > 0) glnvg__deleteTexture(gl, c.image);
13820 gl.nverts = 0;
13821 gl.npaths = 0;
13822 gl.ncalls = 0;
13823 gl.nuniforms = 0;
13824 gl.msp = 1;
13825 gl.maskStack.ptr[0] = GLMaskState.DontMask;
13828 void glnvg__renderCancel (void* uptr) nothrow @trusted @nogc {
13829 glnvg__renderCancelInternal(cast(GLNVGcontext*)uptr, true);
13832 GLenum glnvg_convertBlendFuncFactor (NVGBlendFactor factor) pure nothrow @trusted @nogc {
13833 if (factor == NVGBlendFactor.Zero) return GL_ZERO;
13834 if (factor == NVGBlendFactor.One) return GL_ONE;
13835 if (factor == NVGBlendFactor.SrcColor) return GL_SRC_COLOR;
13836 if (factor == NVGBlendFactor.OneMinusSrcColor) return GL_ONE_MINUS_SRC_COLOR;
13837 if (factor == NVGBlendFactor.DstColor) return GL_DST_COLOR;
13838 if (factor == NVGBlendFactor.OneMinusDstColor) return GL_ONE_MINUS_DST_COLOR;
13839 if (factor == NVGBlendFactor.SrcAlpha) return GL_SRC_ALPHA;
13840 if (factor == NVGBlendFactor.OneMinusSrcAlpha) return GL_ONE_MINUS_SRC_ALPHA;
13841 if (factor == NVGBlendFactor.DstAlpha) return GL_DST_ALPHA;
13842 if (factor == NVGBlendFactor.OneMinusDstAlpha) return GL_ONE_MINUS_DST_ALPHA;
13843 if (factor == NVGBlendFactor.SrcAlphaSaturate) return GL_SRC_ALPHA_SATURATE;
13844 return GL_INVALID_ENUM;
13847 GLNVGblend glnvg__buildBlendFunc (NVGCompositeOperationState op) pure nothrow @trusted @nogc {
13848 GLNVGblend res;
13849 res.simple = op.simple;
13850 res.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB);
13851 res.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB);
13852 res.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha);
13853 res.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha);
13854 if (res.simple) {
13855 if (res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) {
13856 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM;
13858 } else {
13859 if (res.srcRGB == GL_INVALID_ENUM || res.dstRGB == GL_INVALID_ENUM || res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) {
13860 res.simple = true;
13861 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM;
13864 return res;
13867 void glnvg__blendCompositeOperation() (GLNVGcontext* gl, in auto ref GLNVGblend op) nothrow @trusted @nogc {
13868 //glBlendFuncSeparate(glnvg_convertBlendFuncFactor(op.srcRGB), glnvg_convertBlendFuncFactor(op.dstRGB), glnvg_convertBlendFuncFactor(op.srcAlpha), glnvg_convertBlendFuncFactor(op.dstAlpha));
13869 static if (NANOVG_GL_USE_STATE_FILTER) {
13870 if (gl.blendFunc.simple == op.simple) {
13871 if (op.simple) {
13872 if (gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return;
13873 } else {
13874 if (gl.blendFunc.srcRGB == op.srcRGB && gl.blendFunc.dstRGB == op.dstRGB && gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return;
13877 gl.blendFunc = op;
13879 if (op.simple) {
13880 if (op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) {
13881 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13882 } else {
13883 glBlendFunc(op.srcAlpha, op.dstAlpha);
13885 } else {
13886 if (op.srcRGB == GL_INVALID_ENUM || op.dstRGB == GL_INVALID_ENUM || op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) {
13887 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13888 } else {
13889 glBlendFuncSeparate(op.srcRGB, op.dstRGB, op.srcAlpha, op.dstAlpha);
13894 void glnvg__renderSetAffine (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc {
13895 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13896 GLNVGcall* call;
13897 // if last operation was GLNVG_AFFINE, simply replace the matrix
13898 if (gl.ncalls > 0 && gl.calls[gl.ncalls-1].type == GLNVG_AFFINE) {
13899 call = &gl.calls[gl.ncalls-1];
13900 } else {
13901 call = glnvg__allocCall(gl);
13902 if (call is null) return;
13903 call.type = GLNVG_AFFINE;
13905 call.affine.mat.ptr[0..6] = mat.mat.ptr[0..6];
13908 version(nanovega_debug_clipping) public __gshared bool nanovegaClipDebugDump = false;
13910 void glnvg__renderFlush (void* uptr) nothrow @trusted @nogc {
13911 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13912 if (!gl.inFrame) assert(0, "NanoVega: internal driver error");
13913 try {
13914 import core.thread : Thread;
13915 static if (__VERSION__ < 2076) {
13916 DGNoThrowNoGC(() {
13917 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13918 })();
13919 } else {
13920 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13922 } catch (Exception e) {}
13923 scope(exit) gl.inFrame = false;
13925 glnvg__resetError!true(gl);
13927 int vv = 0;
13928 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &vv);
13929 if (glGetError() || vv < 0) vv = 0;
13930 gl.mainFBO = cast(uint)vv;
13933 enum ShaderType { None, Fill, Clip }
13934 auto lastShader = ShaderType.None;
13935 if (gl.ncalls > 0) {
13936 gl.msp = 1;
13937 gl.maskStack.ptr[0] = GLMaskState.DontMask;
13939 // Setup require GL state.
13940 glUseProgram(gl.shader.prog);
13942 glActiveTexture(GL_TEXTURE1);
13943 glBindTexture(GL_TEXTURE_2D, 0);
13944 glActiveTexture(GL_TEXTURE0);
13945 glnvg__resetFBOClipTextureCache(gl);
13947 //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13948 static if (NANOVG_GL_USE_STATE_FILTER) {
13949 gl.blendFunc.simple = true;
13950 gl.blendFunc.srcRGB = gl.blendFunc.dstRGB = gl.blendFunc.srcAlpha = gl.blendFunc.dstAlpha = GL_INVALID_ENUM;
13952 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // just in case
13953 glEnable(GL_CULL_FACE);
13954 glCullFace(GL_BACK);
13955 glFrontFace(GL_CCW);
13956 glEnable(GL_BLEND);
13957 glDisable(GL_DEPTH_TEST);
13958 glDisable(GL_SCISSOR_TEST);
13959 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13960 glStencilMask(0xffffffff);
13961 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13962 glStencilFunc(GL_ALWAYS, 0, 0xffffffff);
13963 glActiveTexture(GL_TEXTURE0);
13964 glBindTexture(GL_TEXTURE_2D, 0);
13965 static if (NANOVG_GL_USE_STATE_FILTER) {
13966 gl.boundTexture = 0;
13967 gl.stencilMask = 0xffffffff;
13968 gl.stencilFunc = GL_ALWAYS;
13969 gl.stencilFuncRef = 0;
13970 gl.stencilFuncMask = 0xffffffff;
13972 glnvg__checkError(gl, "OpenGL setup");
13974 // Upload vertex data
13975 glBindBuffer(GL_ARRAY_BUFFER, gl.vertBuf);
13976 glBufferData(GL_ARRAY_BUFFER, gl.nverts*NVGVertex.sizeof, gl.verts, GL_STREAM_DRAW);
13977 glEnableVertexAttribArray(0);
13978 glEnableVertexAttribArray(1);
13979 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)cast(usize)0);
13980 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)(0+2*float.sizeof));
13981 glnvg__checkError(gl, "vertex data uploading");
13983 // Set view and texture just once per frame.
13984 glUniform1i(gl.shader.loc[GLNVGuniformLoc.Tex], 0);
13985 if (gl.shader.loc[GLNVGuniformLoc.ClipTex] != -1) {
13986 //{ import core.stdc.stdio; printf("%d\n", gl.shader.loc[GLNVGuniformLoc.ClipTex]); }
13987 glUniform1i(gl.shader.loc[GLNVGuniformLoc.ClipTex], 1);
13989 if (gl.shader.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shader.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
13990 glnvg__checkError(gl, "render shader setup");
13992 // Reset affine transformations.
13993 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, NVGMatrix.IdentityMat.ptr);
13994 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, NVGMatrix.IdentityMat.ptr+4);
13995 glnvg__checkError(gl, "affine setup");
13997 // set clip shaders params
13998 // fill
13999 glUseProgram(gl.shaderFillFBO.prog);
14000 glnvg__checkError(gl, "clip shaders setup (fill 0)");
14001 if (gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
14002 glnvg__checkError(gl, "clip shaders setup (fill 1)");
14003 // copy
14004 glUseProgram(gl.shaderCopyFBO.prog);
14005 glnvg__checkError(gl, "clip shaders setup (copy 0)");
14006 if (gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
14007 glnvg__checkError(gl, "clip shaders setup (copy 1)");
14008 //glUniform1i(gl.shaderFillFBO.loc[GLNVGuniformLoc.Tex], 0);
14009 glUniform1i(gl.shaderCopyFBO.loc[GLNVGuniformLoc.Tex], 0);
14010 glnvg__checkError(gl, "clip shaders setup (copy 2)");
14011 // restore render shader
14012 glUseProgram(gl.shader.prog);
14014 //{ 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]); }
14016 gl.lastAffine.identity;
14018 foreach (int i; 0..gl.ncalls) {
14019 GLNVGcall* call = &gl.calls[i];
14020 switch (call.type) {
14021 case GLNVG_FILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__fill(gl, call); break;
14022 case GLNVG_CONVEXFILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__convexFill(gl, call); break;
14023 case GLNVG_STROKE: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__stroke(gl, call); break;
14024 case GLNVG_TRIANGLES: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__triangles(gl, call); break;
14025 case GLNVG_AFFINE: gl.lastAffine = call.affine; glnvg__affine(gl, call); break;
14026 // clip region management
14027 case GLNVG_PUSHCLIP:
14028 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]); }
14029 if (gl.msp >= gl.maskStack.length) assert(0, "NanoVega: mask stack overflow in OpenGL backend");
14030 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.DontMask) {
14031 gl.maskStack.ptr[gl.msp++] = GLMaskState.DontMask;
14032 } else {
14033 gl.maskStack.ptr[gl.msp++] = GLMaskState.Uninitialized;
14035 // no need to reset FBO cache here, as nothing was changed
14036 break;
14037 case GLNVG_POPCLIP:
14038 if (gl.msp <= 1) assert(0, "NanoVega: mask stack underflow in OpenGL backend");
14039 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]); }
14040 --gl.msp;
14041 assert(gl.msp > 0);
14042 //{ import core.stdc.stdio; printf("popped; new msp is %d; state is %d\n", gl.msp, gl.maskStack.ptr[gl.msp]); }
14043 // check popped item
14044 final switch (gl.maskStack.ptr[gl.msp]) {
14045 case GLMaskState.DontMask:
14046 // if last FBO was "don't mask", reset cache if current is not "don't mask"
14047 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) {
14048 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); }
14049 glnvg__resetFBOClipTextureCache(gl);
14051 break;
14052 case GLMaskState.Uninitialized:
14053 // if last FBO texture was uninitialized, it means that nothing was changed,
14054 // so we can keep using cached FBO
14055 break;
14056 case GLMaskState.Initialized:
14057 // if last FBO was initialized, it means that something was definitely changed
14058 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); }
14059 glnvg__resetFBOClipTextureCache(gl);
14060 break;
14061 case GLMaskState.JustCleared: assert(0, "NanoVega: internal FBO stack error");
14063 break;
14064 case GLNVG_RESETCLIP:
14065 // mark current mask as "don't mask"
14066 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]); }
14067 if (gl.msp > 0) {
14068 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) {
14069 gl.maskStack.ptr[gl.msp-1] = GLMaskState.DontMask;
14070 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); }
14071 glnvg__resetFBOClipTextureCache(gl);
14074 break;
14075 case GLNVG_CLIP_DDUMP_ON:
14076 version(nanovega_debug_clipping) nanovegaClipDebugDump = true;
14077 break;
14078 case GLNVG_CLIP_DDUMP_OFF:
14079 version(nanovega_debug_clipping) nanovegaClipDebugDump = false;
14080 break;
14081 case GLNVG_NONE: break;
14082 default:
14084 import core.stdc.stdio; stderr.fprintf("NanoVega FATAL: invalid command in OpenGL backend: %d\n", call.type);
14086 assert(0, "NanoVega: invalid command in OpenGL backend (fatal internal error)");
14088 // and free texture, why not
14089 glnvg__deleteTexture(gl, call.image);
14092 glDisableVertexAttribArray(0);
14093 glDisableVertexAttribArray(1);
14094 glDisable(GL_CULL_FACE);
14095 glBindBuffer(GL_ARRAY_BUFFER, 0);
14096 glUseProgram(0);
14097 glnvg__bindTexture(gl, 0);
14100 // this will do all necessary cleanup
14101 glnvg__renderCancelInternal(gl, false); // no need to clear textures
14104 int glnvg__maxVertCount (const(NVGpath)* paths, int npaths) nothrow @trusted @nogc {
14105 int count = 0;
14106 foreach (int i; 0..npaths) {
14107 count += paths[i].nfill;
14108 count += paths[i].nstroke;
14110 return count;
14113 GLNVGcall* glnvg__allocCall (GLNVGcontext* gl) nothrow @trusted @nogc {
14114 GLNVGcall* ret = null;
14115 if (gl.ncalls+1 > gl.ccalls) {
14116 GLNVGcall* calls;
14117 int ccalls = glnvg__maxi(gl.ncalls+1, 128)+gl.ccalls/2; // 1.5x Overallocate
14118 calls = cast(GLNVGcall*)realloc(gl.calls, GLNVGcall.sizeof*ccalls);
14119 if (calls is null) return null;
14120 gl.calls = calls;
14121 gl.ccalls = ccalls;
14123 ret = &gl.calls[gl.ncalls++];
14124 memset(ret, 0, GLNVGcall.sizeof);
14125 return ret;
14128 int glnvg__allocPaths (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14129 int ret = 0;
14130 if (gl.npaths+n > gl.cpaths) {
14131 GLNVGpath* paths;
14132 int cpaths = glnvg__maxi(gl.npaths+n, 128)+gl.cpaths/2; // 1.5x Overallocate
14133 paths = cast(GLNVGpath*)realloc(gl.paths, GLNVGpath.sizeof*cpaths);
14134 if (paths is null) return -1;
14135 gl.paths = paths;
14136 gl.cpaths = cpaths;
14138 ret = gl.npaths;
14139 gl.npaths += n;
14140 return ret;
14143 int glnvg__allocVerts (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14144 int ret = 0;
14145 if (gl.nverts+n > gl.cverts) {
14146 NVGVertex* verts;
14147 int cverts = glnvg__maxi(gl.nverts+n, 4096)+gl.cverts/2; // 1.5x Overallocate
14148 verts = cast(NVGVertex*)realloc(gl.verts, NVGVertex.sizeof*cverts);
14149 if (verts is null) return -1;
14150 gl.verts = verts;
14151 gl.cverts = cverts;
14153 ret = gl.nverts;
14154 gl.nverts += n;
14155 return ret;
14158 int glnvg__allocFragUniforms (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14159 int ret = 0, structSize = gl.fragSize;
14160 if (gl.nuniforms+n > gl.cuniforms) {
14161 ubyte* uniforms;
14162 int cuniforms = glnvg__maxi(gl.nuniforms+n, 128)+gl.cuniforms/2; // 1.5x Overallocate
14163 uniforms = cast(ubyte*)realloc(gl.uniforms, structSize*cuniforms);
14164 if (uniforms is null) return -1;
14165 gl.uniforms = uniforms;
14166 gl.cuniforms = cuniforms;
14168 ret = gl.nuniforms*structSize;
14169 gl.nuniforms += n;
14170 return ret;
14173 GLNVGfragUniforms* nvg__fragUniformPtr (GLNVGcontext* gl, int i) nothrow @trusted @nogc {
14174 return cast(GLNVGfragUniforms*)&gl.uniforms[i];
14177 void glnvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc {
14178 vtx.x = x;
14179 vtx.y = y;
14180 vtx.u = u;
14181 vtx.v = v;
14184 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 {
14185 if (npaths < 1) return;
14187 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14188 GLNVGcall* call = glnvg__allocCall(gl);
14189 NVGVertex* quad;
14190 GLNVGfragUniforms* frag;
14191 int maxverts, offset;
14193 if (call is null) return;
14195 call.type = GLNVG_FILL;
14196 call.evenOdd = evenOdd;
14197 call.clipmode = clipmode;
14198 //if (clipmode != NVGClipMode.None) { import core.stdc.stdio; printf("CLIP!\n"); }
14199 call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14200 call.triangleCount = 4;
14201 call.pathOffset = glnvg__allocPaths(gl, npaths);
14202 if (call.pathOffset == -1) goto error;
14203 call.pathCount = npaths;
14204 call.image = paint.image.id;
14205 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14207 if (npaths == 1 && paths[0].convex) {
14208 call.type = GLNVG_CONVEXFILL;
14209 call.triangleCount = 0; // Bounding box fill quad not needed for convex fill
14212 // Allocate vertices for all the paths.
14213 maxverts = glnvg__maxVertCount(paths, npaths)+call.triangleCount;
14214 offset = glnvg__allocVerts(gl, maxverts);
14215 if (offset == -1) goto error;
14217 foreach (int i; 0..npaths) {
14218 GLNVGpath* copy = &gl.paths[call.pathOffset+i];
14219 const(NVGpath)* path = &paths[i];
14220 memset(copy, 0, GLNVGpath.sizeof);
14221 if (path.nfill > 0) {
14222 copy.fillOffset = offset;
14223 copy.fillCount = path.nfill;
14224 memcpy(&gl.verts[offset], path.fill, NVGVertex.sizeof*path.nfill);
14225 offset += path.nfill;
14227 if (path.nstroke > 0) {
14228 copy.strokeOffset = offset;
14229 copy.strokeCount = path.nstroke;
14230 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke);
14231 offset += path.nstroke;
14235 // Setup uniforms for draw calls
14236 if (call.type == GLNVG_FILL) {
14237 import core.stdc.string : memcpy;
14238 // Quad
14239 call.triangleOffset = offset;
14240 quad = &gl.verts[call.triangleOffset];
14241 glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f);
14242 glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f);
14243 glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f);
14244 glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f);
14245 // Get uniform
14246 call.uniformOffset = glnvg__allocFragUniforms(gl, 2);
14247 if (call.uniformOffset == -1) goto error;
14248 // Simple shader for stencil
14249 frag = nvg__fragUniformPtr(gl, call.uniformOffset);
14250 memset(frag, 0, (*frag).sizeof);
14251 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f);
14252 memcpy(nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), frag, (*frag).sizeof);
14253 frag.strokeThr = -1.0f;
14254 frag.type = NSVG_SHADER_SIMPLE;
14255 // Fill shader
14256 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, fringe, fringe, -1.0f);
14257 } else {
14258 call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14259 if (call.uniformOffset == -1) goto error;
14260 // Fill shader
14261 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f);
14264 return;
14266 error:
14267 // We get here if call alloc was ok, but something else is not.
14268 // Roll back the last call to prevent drawing it.
14269 if (gl.ncalls > 0) --gl.ncalls;
14272 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 {
14273 if (npaths < 1) return;
14275 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14276 GLNVGcall* call = glnvg__allocCall(gl);
14277 int maxverts, offset;
14279 if (call is null) return;
14281 call.type = GLNVG_STROKE;
14282 call.clipmode = clipmode;
14283 call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14284 call.pathOffset = glnvg__allocPaths(gl, npaths);
14285 if (call.pathOffset == -1) goto error;
14286 call.pathCount = npaths;
14287 call.image = paint.image.id;
14288 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14290 // Allocate vertices for all the paths.
14291 maxverts = glnvg__maxVertCount(paths, npaths);
14292 offset = glnvg__allocVerts(gl, maxverts);
14293 if (offset == -1) goto error;
14295 foreach (int i; 0..npaths) {
14296 GLNVGpath* copy = &gl.paths[call.pathOffset+i];
14297 const(NVGpath)* path = &paths[i];
14298 memset(copy, 0, GLNVGpath.sizeof);
14299 if (path.nstroke) {
14300 copy.strokeOffset = offset;
14301 copy.strokeCount = path.nstroke;
14302 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke);
14303 offset += path.nstroke;
14307 if (gl.flags&NVGContextFlag.StencilStrokes) {
14308 // Fill shader
14309 call.uniformOffset = glnvg__allocFragUniforms(gl, 2);
14310 if (call.uniformOffset == -1) goto error;
14311 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
14312 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f);
14313 } else {
14314 // Fill shader
14315 call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14316 if (call.uniformOffset == -1) goto error;
14317 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
14320 return;
14322 error:
14323 // We get here if call alloc was ok, but something else is not.
14324 // Roll back the last call to prevent drawing it.
14325 if (gl.ncalls > 0) --gl.ncalls;
14328 void glnvg__renderTriangles (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts, float fringeWidth) nothrow @trusted @nogc {
14329 if (nverts < 1) return;
14331 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14332 GLNVGcall* call = glnvg__allocCall(gl);
14333 GLNVGfragUniforms* frag;
14335 if (call is null) return;
14337 call.type = GLNVG_TRIANGLES;
14338 call.clipmode = clipmode;
14339 call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14340 call.image = paint.image.id;
14341 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14343 // Allocate vertices for all the paths.
14344 call.triangleOffset = glnvg__allocVerts(gl, nverts);
14345 if (call.triangleOffset == -1) goto error;
14346 call.triangleCount = nverts;
14348 memcpy(&gl.verts[call.triangleOffset], verts, NVGVertex.sizeof*nverts);
14350 // Fill shader
14351 call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14352 if (call.uniformOffset == -1) goto error;
14353 frag = nvg__fragUniformPtr(gl, call.uniformOffset);
14354 glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, fringeWidth, -1.0f);
14355 frag.type = NSVG_SHADER_IMG;
14357 return;
14359 error:
14360 // We get here if call alloc was ok, but something else is not.
14361 // Roll back the last call to prevent drawing it.
14362 if (gl.ncalls > 0) --gl.ncalls;
14365 void glnvg__renderDelete (void* uptr) nothrow @trusted @nogc {
14366 GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14367 if (gl is null) return;
14369 glnvg__killFBOs(gl);
14370 glnvg__deleteShader(&gl.shader);
14371 glnvg__deleteShader(&gl.shaderFillFBO);
14372 glnvg__deleteShader(&gl.shaderCopyFBO);
14374 if (gl.vertBuf != 0) glDeleteBuffers(1, &gl.vertBuf);
14376 foreach (ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) {
14377 if (tex.id != 0 && (tex.flags&NVGImageFlag.NoDelete) == 0) {
14378 assert(tex.tex != 0);
14379 glDeleteTextures(1, &tex.tex);
14382 free(gl.textures);
14384 free(gl.paths);
14385 free(gl.verts);
14386 free(gl.uniforms);
14387 free(gl.calls);
14389 free(gl);
14393 /** Creates NanoVega contexts for OpenGL2+.
14395 * Specify creation flags as additional arguments, like this:
14396 * `nvgCreateContext(NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes);`
14398 * If you won't specify any flags, defaults will be used:
14399 * `[NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes]`.
14401 * Group: context_management
14403 public NVGContext nvgCreateContext (const(NVGContextFlag)[] flagList...) nothrow @trusted @nogc {
14404 version(aliced) {
14405 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA;
14406 } else {
14407 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes;
14409 uint flags = 0;
14410 if (flagList.length != 0) {
14411 foreach (immutable flg; flagList) flags |= (flg != NVGContextFlag.Default ? flg : DefaultFlags);
14412 } else {
14413 flags = DefaultFlags;
14415 NVGparams params = void;
14416 NVGContext ctx = null;
14417 version(nanovg_builtin_opengl_bindings) nanovgInitOpenGL(); // why not?
14418 GLNVGcontext* gl = cast(GLNVGcontext*)malloc(GLNVGcontext.sizeof);
14419 if (gl is null) goto error;
14420 memset(gl, 0, GLNVGcontext.sizeof);
14422 memset(&params, 0, params.sizeof);
14423 params.renderCreate = &glnvg__renderCreate;
14424 params.renderCreateTexture = &glnvg__renderCreateTexture;
14425 params.renderTextureIncRef = &glnvg__renderTextureIncRef;
14426 params.renderDeleteTexture = &glnvg__renderDeleteTexture;
14427 params.renderUpdateTexture = &glnvg__renderUpdateTexture;
14428 params.renderGetTextureSize = &glnvg__renderGetTextureSize;
14429 params.renderViewport = &glnvg__renderViewport;
14430 params.renderCancel = &glnvg__renderCancel;
14431 params.renderFlush = &glnvg__renderFlush;
14432 params.renderPushClip = &glnvg__renderPushClip;
14433 params.renderPopClip = &glnvg__renderPopClip;
14434 params.renderResetClip = &glnvg__renderResetClip;
14435 params.renderFill = &glnvg__renderFill;
14436 params.renderStroke = &glnvg__renderStroke;
14437 params.renderTriangles = &glnvg__renderTriangles;
14438 params.renderSetAffine = &glnvg__renderSetAffine;
14439 params.renderDelete = &glnvg__renderDelete;
14440 params.userPtr = gl;
14441 params.edgeAntiAlias = (flags&NVGContextFlag.Antialias ? true : false);
14442 if (flags&(NVGContextFlag.FontAA|NVGContextFlag.FontNoAA)) {
14443 params.fontAA = (flags&NVGContextFlag.FontNoAA ? NVG_INVERT_FONT_AA : !NVG_INVERT_FONT_AA);
14444 } else {
14445 params.fontAA = NVG_INVERT_FONT_AA;
14448 gl.flags = flags;
14449 gl.freetexid = -1;
14451 ctx = createInternal(&params);
14452 if (ctx is null) goto error;
14454 static if (__VERSION__ < 2076) {
14455 DGNoThrowNoGC(() { import core.thread; gl.mainTID = Thread.getThis.id; })();
14456 } else {
14457 try { import core.thread; gl.mainTID = Thread.getThis.id; } catch (Exception e) {}
14460 return ctx;
14462 error:
14463 // 'gl' is freed by nvgDeleteInternal.
14464 if (ctx !is null) ctx.deleteInternal();
14465 return null;
14468 /// Create NanoVega OpenGL image from texture id.
14469 /// Group: images
14470 public int glCreateImageFromHandleGL2 (NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc {
14471 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr;
14472 GLNVGtexture* tex = glnvg__allocTexture(gl);
14474 if (tex is null) return 0;
14476 tex.type = NVGtexture.RGBA;
14477 tex.tex = textureId;
14478 tex.flags = imageFlags;
14479 tex.width = w;
14480 tex.height = h;
14482 return tex.id;
14485 /// Returns OpenGL texture id for NanoVega image.
14486 /// Group: images
14487 public GLuint glImageHandleGL2 (NVGContext ctx, int image) nothrow @trusted @nogc {
14488 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr;
14489 GLNVGtexture* tex = glnvg__findTexture(gl, image);
14490 return tex.tex;
14494 // ////////////////////////////////////////////////////////////////////////// //
14495 private:
14497 static if (NanoVegaHasFontConfig) {
14498 version(nanovg_builtin_fontconfig_bindings) {
14499 pragma(lib, "fontconfig");
14501 private extern(C) nothrow @trusted @nogc {
14502 enum FC_FILE = "file"; /* String */
14503 alias FcBool = int;
14504 alias FcChar8 = char;
14505 struct FcConfig;
14506 struct FcPattern;
14507 alias FcMatchKind = int;
14508 enum : FcMatchKind {
14509 FcMatchPattern,
14510 FcMatchFont,
14511 FcMatchScan
14513 alias FcResult = int;
14514 enum : FcResult {
14515 FcResultMatch,
14516 FcResultNoMatch,
14517 FcResultTypeMismatch,
14518 FcResultNoId,
14519 FcResultOutOfMemory
14521 FcBool FcInit ();
14522 FcBool FcConfigSubstituteWithPat (FcConfig* config, FcPattern* p, FcPattern* p_pat, FcMatchKind kind);
14523 void FcDefaultSubstitute (FcPattern* pattern);
14524 FcBool FcConfigSubstitute (FcConfig* config, FcPattern* p, FcMatchKind kind);
14525 FcPattern* FcFontMatch (FcConfig* config, FcPattern* p, FcResult* result);
14526 FcPattern* FcNameParse (const(FcChar8)* name);
14527 void FcPatternDestroy (FcPattern* p);
14528 FcResult FcPatternGetString (const(FcPattern)* p, const(char)* object, int n, FcChar8** s);
14532 __gshared bool fontconfigAvailable = false;
14533 // initialize fontconfig
14534 shared static this () {
14535 if (FcInit()) {
14536 fontconfigAvailable = true;
14537 } else {
14538 import core.stdc.stdio : stderr, fprintf;
14539 stderr.fprintf("***NanoVega WARNING: cannot init fontconfig!\n");
14545 // ////////////////////////////////////////////////////////////////////////// //
14546 public enum BaphometDims = 512.0f; // baphomet icon is 512x512 ([0..511])
14548 private static immutable ubyte[7641] baphometPath = [
14549 0x01,0x04,0x06,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x08,0xa0,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,
14550 0x00,0x80,0xff,0x43,0xa2,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x08,0x00,0x80,0xff,
14551 0x43,0x7a,0x89,0xe5,0x42,0xa0,0x1d,0xc6,0x43,0x00,0x00,0x00,0x00,0x30,0x89,0x7f,0x43,0x00,0x00,0x00,
14552 0x00,0x08,0x7a,0x89,0xe5,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7a,0x89,0xe5,0x42,0x00,0x00,
14553 0x00,0x00,0x30,0x89,0x7f,0x43,0x08,0x00,0x00,0x00,0x00,0xa2,0x1d,0xc6,0x43,0x7a,0x89,0xe5,0x42,0x00,
14554 0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x09,0x06,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd,
14555 0x43,0x08,0x16,0x68,0xb3,0x43,0x72,0x87,0xdd,0x43,0x71,0x87,0xdd,0x43,0x17,0x68,0xb3,0x43,0x71,0x87,
14556 0xdd,0x43,0x30,0x89,0x7f,0x43,0x08,0x71,0x87,0xdd,0x43,0xd2,0x2f,0x18,0x43,0x16,0x68,0xb3,0x43,0x35,
14557 0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x35,0xe2,0x87,0x42,0x08,0xd1,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,
14558 0x35,0xe2,0x87,0x42,0xd2,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x08,0x35,0xe2,0x87,
14559 0x42,0x17,0x68,0xb3,0x43,0xd1,0x2f,0x18,0x43,0x72,0x87,0xdd,0x43,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd,
14560 0x43,0x09,0x06,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x07,0xa4,0x3f,0x7f,0x43,0x0b,0x86,0xdc,0x43,
14561 0x07,0x6c,0xb9,0xb2,0x43,0xe8,0xd1,0xca,0x42,0x07,0x6e,0x4d,0xa0,0x42,0xa9,0x10,0x9c,0x43,0x07,0xb7,
14562 0x40,0xd7,0x43,0xa9,0x10,0x9c,0x43,0x07,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x09,0x06,0x98,0x42,
14563 0x74,0x43,0xb1,0x8d,0x68,0x43,0x08,0xd7,0x24,0x79,0x43,0xba,0x83,0x6e,0x43,0xa9,0x16,0x7c,0x43,0x56,
14564 0xa1,0x76,0x43,0x74,0x2a,0x7d,0x43,0x44,0x73,0x80,0x43,0x08,0x55,0xd1,0x7e,0x43,0xe3,0xea,0x76,0x43,
14565 0xbc,0x18,0x81,0x43,0x7f,0xa8,0x6e,0x43,0x8f,0x0a,0x84,0x43,0x02,0xfc,0x68,0x43,0x09,0x06,0x92,0x29,
14566 0x8d,0x43,0x73,0xc3,0x67,0x43,0x08,0xa4,0xd9,0x8e,0x43,0xf2,0xa6,0x7a,0x43,0x8f,0x22,0x88,0x43,0x75,
14567 0x2a,0x7d,0x43,0x42,0x7f,0x82,0x43,0x08,0xc8,0x88,0x43,0x09,0x06,0xc1,0x79,0x74,0x43,0x50,0x64,0x89,
14568 0x43,0x08,0x68,0x2d,0x72,0x43,0xee,0x21,0x81,0x43,0xcd,0x97,0x55,0x43,0xe6,0xf1,0x7b,0x43,0x91,0xec,
14569 0x5d,0x43,0xa8,0xc7,0x6a,0x43,0x09,0x06,0xfa,0xa5,0x52,0x43,0x60,0x97,0x7c,0x43,0x08,0x19,0xff,0x50,
14570 0x43,0xe9,0x6e,0x8a,0x43,0xb0,0xbd,0x70,0x43,0x4c,0x51,0x82,0x43,0x04,0xeb,0x69,0x43,0x66,0x0f,0x8e,
14571 0x43,0x09,0x06,0x17,0xbf,0x71,0x43,0x2c,0x58,0x94,0x43,0x08,0x1c,0x96,0x6e,0x43,0x61,0x68,0x99,0x43,
14572 0x2d,0x3a,0x6e,0x43,0xc8,0x81,0x9e,0x43,0xb7,0x9b,0x72,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0x30,0xdb,
14573 0x82,0x43,0xdb,0xe9,0x93,0x43,0x08,0x11,0x82,0x84,0x43,0x61,0x68,0x99,0x43,0xe8,0x4a,0x84,0x43,0x8e,
14574 0xa6,0x9e,0x43,0x42,0x7f,0x82,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0xc4,0x02,0x85,0x43,0xd1,0x0b,0x92,
14575 0x43,0x08,0xd6,0xb2,0x86,0x43,0x34,0x1e,0x92,0x43,0x4f,0x58,0x87,0x43,0xa4,0xf1,0x92,0x43,0x03,0xd9,
14576 0x87,0x43,0x7b,0xc6,0x94,0x43,0x09,0x06,0x87,0x3e,0x64,0x43,0x31,0x3b,0x93,0x43,0x08,0x3b,0xbf,0x64,
14577 0x43,0x6f,0xf9,0x91,0x43,0x96,0x0b,0x67,0x43,0xc5,0x4a,0x91,0x43,0xcf,0xfe,0x6a,0x43,0x31,0x2f,0x91,
14578 0x43,0x09,0x06,0x16,0x74,0xb5,0x43,0x08,0xec,0x8e,0x43,0x08,0x1b,0x4b,0xb2,0x43,0xee,0x5d,0x8b,0x43,
14579 0x48,0x4d,0xad,0x43,0x12,0xa6,0x8a,0x43,0xf3,0xd7,0xa7,0x43,0x74,0xb8,0x8a,0x43,0x08,0x8c,0xb2,0xa0,
14580 0x43,0xcd,0xf8,0x8a,0x43,0x68,0x46,0x9b,0x43,0x79,0x8f,0x87,0x43,0x49,0xc9,0x96,0x43,0xe9,0x3e,0x82,
14581 0x43,0x08,0x60,0x5c,0x97,0x43,0xa1,0xde,0x8b,0x43,0x4e,0xa0,0x93,0x43,0x31,0x3b,0x93,0x43,0x9f,0xea,
14582 0x8d,0x43,0x27,0x8d,0x99,0x43,0x08,0x07,0xe0,0x8c,0x43,0x06,0x34,0x9b,0x43,0x38,0xe9,0x8c,0x43,0x46,
14583 0x0a,0x9e,0x43,0x3d,0xcc,0x8b,0x43,0xb2,0x06,0xa2,0x43,0x08,0xf1,0x40,0x8a,0x43,0xb0,0x12,0xa4,0x43,
14584 0x39,0xd1,0x88,0x43,0x76,0x43,0xa6,0x43,0xfa,0x06,0x88,0x43,0xa4,0x75,0xa9,0x43,0x08,0x19,0x6c,0x88,
14585 0x43,0x9f,0x9e,0xac,0x43,0x66,0xeb,0x87,0x43,0x44,0x76,0xb0,0x43,0x6b,0xce,0x86,0x43,0x3b,0xbc,0xb4,
14586 0x43,0x08,0xa9,0x8c,0x85,0x43,0x06,0xd0,0xb5,0x43,0xfa,0xee,0x83,0x43,0x74,0xa3,0xb6,0x43,0x3d,0x90,
14587 0x81,0x43,0x31,0xf6,0xb6,0x43,0x08,0x9d,0x61,0x7d,0x43,0xee,0x48,0xb7,0x43,0x3b,0x1f,0x75,0x43,0xcf,
14588 0xe3,0xb6,0x43,0xee,0x6f,0x6d,0x43,0x68,0xe2,0xb5,0x43,0x08,0xd4,0xed,0x6b,0x43,0x87,0x2f,0xb2,0x43,
14589 0x0e,0xc9,0x6b,0x43,0xa7,0x7c,0xae,0x43,0x98,0xfa,0x67,0x43,0xab,0x53,0xab,0x43,0x08,0x25,0x2c,0x64,
14590 0x43,0x33,0xa2,0xa8,0x43,0x40,0x96,0x61,0x43,0xc3,0xc2,0xa5,0x43,0x64,0xde,0x60,0x43,0xfa,0xa2,0xa2,
14591 0x43,0x08,0xb0,0x5d,0x60,0x43,0x06,0x4c,0x9f,0x43,0x9a,0xca,0x5f,0x43,0x38,0x3d,0x9b,0x43,0x3b,0x8f,
14592 0x5c,0x43,0x85,0xb0,0x98,0x43,0x08,0x42,0x36,0x51,0x43,0x3d,0xf0,0x91,0x43,0xcd,0x4f,0x49,0x43,0xdb,
14593 0xb9,0x8b,0x43,0xe0,0xdb,0x44,0x43,0x42,0x8b,0x84,0x43,0x08,0x7e,0xc9,0x44,0x43,0x8a,0x57,0x8d,0x43,
14594 0xbc,0x6c,0x0f,0x43,0x23,0x62,0x8e,0x43,0xf5,0x17,0x07,0x43,0xc5,0x3e,0x8f,0x43,0x09,0x06,0xe0,0xea,
14595 0x76,0x43,0xab,0xef,0xc5,0x43,0x08,0x12,0x00,0x79,0x43,0xab,0xcb,0xbf,0x43,0x79,0xb9,0x6d,0x43,0x7e,
14596 0x8d,0xba,0x43,0xee,0x6f,0x6d,0x43,0x98,0xeb,0xb5,0x43,0x08,0xe0,0x02,0x7b,0x43,0x5f,0x1c,0xb8,0x43,
14597 0x85,0x2c,0x82,0x43,0xe9,0x65,0xb8,0x43,0xd6,0xb2,0x86,0x43,0xc6,0x05,0xb5,0x43,0x08,0x03,0xcd,0x85,
14598 0x43,0x5a,0x39,0xb9,0x43,0xe4,0x4f,0x81,0x43,0xdb,0xd4,0xbf,0x43,0xdf,0x6c,0x82,0x43,0xbc,0x93,0xc5,
14599 0x43,0x09,0x06,0xf0,0xd0,0x22,0x43,0x5d,0x19,0x08,0x43,0x08,0xbc,0xab,0x49,0x43,0x4a,0x35,0x29,0x43,
14600 0xcb,0xf7,0x65,0x43,0xce,0x37,0x45,0x43,0x0e,0x99,0x63,0x43,0x67,0xc6,0x5c,0x43,0x09,0x06,0x05,0x94,
14601 0xab,0x43,0xc2,0x13,0x04,0x43,0x08,0x9f,0x26,0x98,0x43,0x11,0x42,0x25,0x43,0x97,0x00,0x8a,0x43,0x32,
14602 0x32,0x41,0x43,0xf5,0x2f,0x8b,0x43,0xc7,0xc0,0x58,0x43,0x09,0x06,0x8f,0x85,0x48,0x43,0xe0,0xa8,0x8c,
14603 0x43,0x08,0x55,0xaa,0x48,0x43,0xe0,0xa8,0x8c,0x43,0x6b,0x3d,0x49,0x43,0xc1,0x43,0x8c,0x43,0x31,0x62,
14604 0x49,0x43,0xc1,0x43,0x8c,0x43,0x08,0x2f,0xe3,0x2f,0x43,0xad,0xe7,0x98,0x43,0xff,0x0d,0x0d,0x43,0xad,
14605 0xf3,0x9a,0x43,0xf0,0xaf,0xcc,0x42,0x74,0x00,0x97,0x43,0x08,0xbb,0xa2,0xf7,0x42,0x93,0x4d,0x93,0x43,
14606 0x5e,0x19,0x08,0x43,0x5a,0x2a,0x87,0x43,0x23,0x6e,0x10,0x43,0x42,0x97,0x86,0x43,0x08,0xca,0xe8,0x33,
14607 0x43,0x1b,0x3c,0x80,0x43,0x80,0xe8,0x4d,0x43,0xda,0xf4,0x70,0x43,0xae,0x0e,0x4f,0x43,0x2b,0x1b,0x65,
14608 0x43,0x08,0x66,0x96,0x54,0x43,0xa3,0xe1,0x3b,0x43,0x4e,0xc4,0x19,0x43,0xa0,0x1a,0x16,0x43,0x10,0xe2,
14609 0x14,0x43,0x26,0x14,0xe0,0x42,0x08,0x5c,0x91,0x1c,0x43,0xcb,0x27,0xee,0x42,0xa9,0x40,0x24,0x43,0x71,
14610 0x3b,0xfc,0x42,0xf3,0xef,0x2b,0x43,0x8b,0x27,0x05,0x43,0x08,0xe2,0x4b,0x2c,0x43,0x48,0x86,0x07,0x43,
14611 0x79,0x62,0x2f,0x43,0x05,0xe5,0x09,0x43,0x55,0x32,0x34,0x43,0xa0,0xd2,0x09,0x43,0x08,0x74,0xa3,0x36,
14612 0x43,0x3a,0xd1,0x08,0x43,0x7e,0x81,0x38,0x43,0x09,0xd4,0x0a,0x43,0x0d,0xba,0x39,0x43,0xa0,0xea,0x0d,
14613 0x43,0x08,0x6f,0xe4,0x3d,0x43,0x43,0xc7,0x0e,0x43,0xd6,0xe5,0x3e,0x43,0xc4,0x4a,0x11,0x43,0x55,0x7a,
14614 0x40,0x43,0x59,0x72,0x13,0x43,0x08,0x55,0x92,0x44,0x43,0xbf,0x73,0x14,0x43,0x23,0x95,0x46,0x43,0xa5,
14615 0x09,0x17,0x43,0xe0,0xf3,0x48,0x43,0xfe,0x55,0x19,0x43,0x08,0xcd,0x4f,0x49,0x43,0xaa,0x10,0x1c,0x43,
14616 0x61,0x77,0x4b,0x43,0xfe,0x6d,0x1d,0x43,0x80,0xe8,0x4d,0x43,0x2b,0x94,0x1e,0x43,0x08,0x58,0xc9,0x51,
14617 0x43,0x41,0x27,0x1f,0x43,0x9b,0x82,0x53,0x43,0x35,0x72,0x20,0x43,0x53,0xf2,0x54,0x43,0x88,0xcf,0x21,
14618 0x43,0x08,0x7b,0x29,0x55,0x43,0xe8,0x0a,0x25,0x43,0xb2,0x2d,0x58,0x43,0xef,0xe8,0x26,0x43,0x9b,0xb2,
14619 0x5b,0x43,0xd0,0x8f,0x28,0x43,0x08,0x5f,0xef,0x5f,0x43,0xeb,0x11,0x2a,0x43,0xfd,0xdc,0x5f,0x43,0x6e,
14620 0x95,0x2c,0x43,0x3b,0xa7,0x60,0x43,0x2b,0xf4,0x2e,0x43,0x08,0x06,0xbb,0x61,0x43,0xfd,0xe5,0x31,0x43,
14621 0xe7,0x61,0x63,0x43,0xef,0x30,0x33,0x43,0x53,0x52,0x65,0x43,0xa3,0xb1,0x33,0x43,0x08,0x12,0xa0,0x68,
14622 0x43,0x7f,0x69,0x34,0x43,0x40,0xc6,0x69,0x43,0x64,0xff,0x36,0x43,0x7e,0x90,0x6a,0x43,0x71,0xcc,0x39,
14623 0x43,0x08,0xbc,0x5a,0x6b,0x43,0x51,0x73,0x3b,0x43,0xc1,0x49,0x6c,0x43,0xa5,0xd0,0x3c,0x43,0xe0,0xba,
14624 0x6e,0x43,0xb8,0x74,0x3c,0x43,0x08,0x6b,0x1c,0x73,0x43,0x13,0xc1,0x3e,0x43,0x40,0xf6,0x71,0x43,0xce,
14625 0x1f,0x41,0x43,0x55,0x89,0x72,0x43,0x8d,0x7e,0x43,0x43,0x08,0x68,0x2d,0x72,0x43,0x89,0xae,0x4b,0x43,
14626 0xc1,0x79,0x74,0x43,0xcb,0x78,0x4c,0x43,0x55,0xa1,0x76,0x43,0x5b,0xb1,0x4d,0x43,0x08,0xa2,0x38,0x7a,
14627 0x43,0xd1,0x56,0x4e,0x43,0x85,0xb6,0x78,0x43,0xb1,0x15,0x54,0x43,0x83,0xc7,0x77,0x43,0x89,0x0e,0x5c,
14628 0x43,0x08,0xcf,0x46,0x77,0x43,0x0f,0x81,0x5f,0x43,0x1a,0xde,0x7a,0x43,0xce,0xc7,0x5d,0x43,0x42,0x73,
14629 0x80,0x43,0x99,0xc3,0x5a,0x43,0x08,0x85,0x2c,0x82,0x43,0xf6,0xe6,0x59,0x43,0x81,0x3d,0x81,0x43,0x16,
14630 0x10,0x50,0x43,0xd6,0x8e,0x80,0x43,0x5b,0x99,0x49,0x43,0x08,0xc4,0xea,0x80,0x43,0x22,0x95,0x46,0x43,
14631 0xfa,0xe2,0x81,0x43,0xda,0xec,0x43,0x43,0x78,0x77,0x83,0x43,0xe4,0xb2,0x41,0x43,0x08,0x8a,0x27,0x85,
14632 0x43,0x86,0x77,0x3e,0x43,0x0c,0x9f,0x85,0x43,0x07,0xf4,0x3b,0x43,0x8f,0x16,0x86,0x43,0xe6,0x82,0x39,
14633 0x43,0x08,0x85,0x44,0x86,0x43,0x37,0xd9,0x35,0x43,0x1e,0x4f,0x87,0x43,0xe1,0x7b,0x34,0x43,0xdf,0x90,
14634 0x88,0x43,0xb6,0x55,0x33,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0xe5,0x31,0x43,0xfa,0x12,0x8a,0x43,0xbf,
14635 0x03,0x2d,0x43,0x19,0x78,0x8a,0x43,0x45,0x5e,0x2c,0x43,0x08,0x03,0xf1,0x8b,0x43,0xac,0x47,0x29,0x43,
14636 0x2f,0x17,0x8d,0x43,0x45,0x46,0x28,0x43,0xc8,0x21,0x8e,0x43,0x30,0xb3,0x27,0x43,0x08,0xa9,0xc8,0x8f,
14637 0x43,0xef,0xe8,0x26,0x43,0xbf,0x5b,0x90,0x43,0x5b,0xc1,0x24,0x43,0x10,0xca,0x90,0x43,0xa0,0x62,0x22,
14638 0x43,0x08,0x26,0x5d,0x91,0x43,0xbb,0xcc,0x1f,0x43,0xf0,0x70,0x92,0x43,0x78,0x13,0x1e,0x43,0x77,0xd7,
14639 0x93,0x43,0x73,0x24,0x1d,0x43,0x08,0x65,0x3f,0x96,0x43,0xce,0x58,0x1b,0x43,0xbe,0x7f,0x96,0x43,0xbf,
14640 0x8b,0x18,0x43,0x60,0x5c,0x97,0x43,0xb6,0xad,0x16,0x43,0x08,0xba,0xa8,0x99,0x43,0x78,0xcb,0x11,0x43,
14641 0x49,0xe1,0x9a,0x43,0x78,0xcb,0x11,0x43,0x01,0x51,0x9c,0x43,0x73,0xdc,0x10,0x43,0x08,0x72,0x24,0x9d,
14642 0x43,0xd2,0xff,0x0f,0x43,0x1c,0xd3,0x9d,0x43,0x07,0xec,0x0e,0x43,0xeb,0xc9,0x9d,0x43,0xe8,0x7a,0x0c,
14643 0x43,0x08,0x60,0x80,0x9d,0x43,0xd7,0xbe,0x08,0x43,0x4d,0xe8,0x9f,0x43,0x86,0x50,0x08,0x43,0x25,0xbd,
14644 0xa1,0x43,0x5b,0x2a,0x07,0x43,0x08,0x99,0x7f,0xa3,0x43,0xc9,0xf1,0x05,0x43,0x48,0x1d,0xa5,0x43,0x86,
14645 0x38,0x04,0x43,0x6c,0x71,0xa6,0x43,0x18,0x59,0x01,0x43,0x08,0x32,0x96,0xa6,0x43,0x6e,0x64,0xff,0x42,
14646 0x48,0x29,0xa7,0x43,0xed,0xcf,0xfd,0x42,0x5f,0xbc,0xa7,0x43,0x71,0x3b,0xfc,0x42,0x08,0xf3,0xe3,0xa9,
14647 0x43,0xf7,0x7d,0xf7,0x42,0xd8,0x6d,0xaa,0x43,0x45,0xe5,0xf2,0x42,0x48,0x41,0xab,0x43,0xcb,0x27,0xee,
14648 0x42,0x08,0x24,0xf9,0xab,0x43,0x52,0x6a,0xe9,0x42,0xee,0x0c,0xad,0x43,0x4c,0x8c,0xe7,0x42,0x1b,0x33,
14649 0xae,0x43,0xcc,0xf7,0xe5,0x42,0x08,0xaa,0x6b,0xaf,0x43,0xe8,0x61,0xe3,0x42,0x90,0xf5,0xaf,0x43,0xc9,
14650 0xf0,0xe0,0x42,0xe0,0x63,0xb0,0x43,0xe5,0x5a,0xde,0x42,0x08,0xaa,0x83,0xb3,0x43,0x29,0x2d,0x09,0x43,
14651 0x6a,0xfe,0x8e,0x43,0xb8,0x74,0x3c,0x43,0xd5,0x06,0x95,0x43,0xe6,0x79,0x67,0x43,0x08,0x2f,0x53,0x97,
14652 0x43,0xe9,0xb0,0x74,0x43,0xa8,0x28,0xa0,0x43,0x43,0xfd,0x76,0x43,0x83,0x28,0xad,0x43,0x17,0x59,0x81,
14653 0x43,0x08,0x3d,0xe7,0xbf,0x43,0x4b,0x8d,0x8c,0x43,0xae,0x96,0xba,0x43,0x66,0x27,0x92,0x43,0x15,0xe0,
14654 0xc7,0x43,0x6f,0x11,0x96,0x43,0x08,0x7e,0x5d,0xb2,0x43,0xdb,0x01,0x98,0x43,0x9e,0x56,0xa0,0x43,0x80,
14655 0xc1,0x97,0x43,0x69,0x2e,0x97,0x43,0x31,0x17,0x8d,0x43,0x09,0x06,0xab,0xa7,0x39,0x43,0x67,0x0f,0x0e,
14656 0x43,0x08,0xdb,0xbc,0x3b,0x43,0xe8,0x92,0x10,0x43,0xb5,0x85,0x3b,0x43,0x97,0x3c,0x14,0x43,0xab,0xa7,
14657 0x39,0x43,0x0c,0x0b,0x18,0x43,0x09,0x06,0xca,0x30,0x40,0x43,0x30,0x3b,0x13,0x43,0x08,0x17,0xc8,0x43,
14658 0x43,0xa5,0x09,0x17,0x43,0x7e,0xc9,0x44,0x43,0x1a,0xd8,0x1a,0x43,0x9d,0x22,0x43,0x43,0x8d,0xa6,0x1e,
14659 0x43,0x09,0x06,0xc8,0x78,0x4c,0x43,0xed,0xc9,0x1d,0x43,0x08,0x0b,0x32,0x4e,0x43,0x22,0xce,0x20,0x43,
14660 0x23,0xc5,0x4e,0x43,0x58,0xd2,0x23,0x43,0x0b,0x32,0x4e,0x43,0x2b,0xc4,0x26,0x43,0x09,0x06,0xec,0x08,
14661 0x58,0x43,0xc7,0xb1,0x26,0x43,0x08,0x02,0x9c,0x58,0x43,0xef,0x00,0x2b,0x43,0xd9,0x64,0x58,0x43,0x02,
14662 0xbd,0x2e,0x43,0x10,0x51,0x57,0x43,0x37,0xc1,0x31,0x43,0x09,0x06,0xcb,0xdf,0x61,0x43,0x4a,0x65,0x31,
14663 0x43,0x08,0xbe,0x2a,0x63,0x43,0xbd,0x33,0x35,0x43,0x32,0xe1,0x62,0x43,0x56,0x4a,0x38,0x43,0xde,0x83,
14664 0x61,0x43,0x3c,0xe0,0x3a,0x43,0x09,0x06,0x1c,0x7e,0x6a,0x43,0x5b,0x39,0x39,0x43,0x08,0x31,0x11,0x6b,
14665 0x43,0x0c,0xd2,0x3d,0x43,0x1c,0x7e,0x6a,0x43,0x13,0xd9,0x42,0x43,0xd9,0xc4,0x68,0x43,0xcb,0x60,0x48,
14666 0x43,0x09,0x06,0xe5,0xc1,0x73,0x43,0x16,0xf8,0x4b,0x43,0x08,0xa6,0xf7,0x72,0x43,0xb1,0xfd,0x4f,0x43,
14667 0x3b,0x07,0x71,0x43,0x4a,0x14,0x53,0x43,0xa2,0xf0,0x6d,0x43,0x7c,0x29,0x55,0x43,0x09,0x06,0x00,0x8d,
14668 0xa6,0x43,0xef,0x21,0x01,0x43,0x08,0x52,0xfb,0xa6,0x43,0xce,0xc8,0x02,0x43,0xe6,0x16,0xa7,0x43,0x51,
14669 0x4c,0x05,0x43,0x3b,0x68,0xa6,0x43,0x4c,0x75,0x08,0x43,0x09,0x06,0xde,0x20,0xa1,0x43,0x86,0x50,0x08,
14670 0x43,0x08,0xd4,0x4e,0xa1,0x43,0xd3,0xe7,0x0b,0x43,0xb5,0xe9,0xa0,0x43,0x59,0x5a,0x0f,0x43,0xba,0xcc,
14671 0x9f,0x43,0x54,0x83,0x12,0x43,0x09,0x06,0x77,0xfb,0x99,0x43,0x6c,0x16,0x13,0x43,0x08,0xde,0xfc,0x9a,
14672 0x43,0x4a,0xbd,0x14,0x43,0x06,0x34,0x9b,0x43,0xfe,0x55,0x19,0x43,0x13,0xe9,0x99,0x43,0x41,0x27,0x1f,
14673 0x43,0x09,0x06,0x46,0xce,0x93,0x43,0x26,0xa5,0x1d,0x43,0x08,0xe7,0xaa,0x94,0x43,0xbb,0xcc,0x1f,0x43,
14674 0x18,0xb4,0x94,0x43,0xa8,0x40,0x24,0x43,0xe2,0xbb,0x93,0x43,0x21,0xfe,0x28,0x43,0x09,0x06,0xb1,0x8e,
14675 0x8d,0x43,0xa8,0x58,0x28,0x43,0x08,0x19,0x90,0x8e,0x43,0x54,0x13,0x2b,0x43,0xa4,0xd9,0x8e,0x43,0x84,
14676 0x40,0x31,0x43,0x46,0xaa,0x8d,0x43,0x29,0x24,0x37,0x43,0x09,0x06,0xd6,0xbe,0x88,0x43,0xef,0x30,0x33,
14677 0x43,0x08,0x0c,0xb7,0x89,0x43,0x0e,0xa2,0x35,0x43,0xc0,0x37,0x8a,0x43,0x7a,0xaa,0x3b,0x43,0xbb,0x48,
14678 0x89,0x43,0xbb,0x7b,0x41,0x43,0x09,0x06,0x3a,0xad,0x82,0x43,0xc4,0x59,0x43,0x43,0x08,0xd2,0xb7,0x83,
14679 0x43,0x2b,0x5b,0x44,0x43,0x35,0xd6,0x85,0x43,0x48,0xf5,0x49,0x43,0x42,0x97,0x86,0x43,0xc4,0xa1,0x4f,
14680 0x43,0x09,0x06,0x9c,0xb3,0x80,0x43,0x48,0x55,0x5a,0x43,0x08,0xff,0xc5,0x80,0x43,0x09,0x73,0x55,0x43,
14681 0x93,0xe1,0x80,0x43,0x0f,0x39,0x53,0x43,0xf1,0xbe,0x7e,0x43,0x18,0xe7,0x4c,0x43,0x09,0x06,0xe0,0x02,
14682 0x7b,0x43,0x92,0xec,0x5d,0x43,0x08,0x09,0x3a,0x7b,0x43,0xf0,0xf7,0x58,0x43,0x09,0x3a,0x7b,0x43,0xe6,
14683 0x31,0x5b,0x43,0xe0,0x02,0x7b,0x43,0xa8,0x4f,0x56,0x43,0x09,0x06,0x39,0x4f,0x7d,0x43,0x3e,0x8f,0x5c,
14684 0x43,0x08,0xe9,0xe0,0x7c,0x43,0x03,0x9c,0x58,0x43,0x1e,0x2b,0x81,0x43,0x7f,0x30,0x5a,0x43,0xff,0x73,
14685 0x7d,0x43,0xf6,0xb6,0x51,0x43,0x09,0x06,0x5c,0xb8,0x52,0x43,0x28,0x21,0x87,0x43,0x08,0xae,0x3e,0x57,
14686 0x43,0x12,0x9a,0x88,0x43,0x23,0xf5,0x56,0x43,0x04,0xf1,0x8b,0x43,0x25,0xfc,0x5b,0x43,0x85,0x74,0x8e,
14687 0x43,0x08,0x2f,0xf2,0x61,0x43,0x8e,0x52,0x90,0x43,0xd9,0xdc,0x6c,0x43,0x85,0x74,0x8e,0x43,0xc6,0x20,
14688 0x69,0x43,0x3d,0xd8,0x8d,0x43,0x08,0x6d,0x8c,0x5a,0x43,0xf5,0x3b,0x8d,0x43,0x3d,0x77,0x58,0x43,0xa1,
14689 0xc6,0x87,0x43,0xf8,0xed,0x5e,0x43,0x5e,0x0d,0x86,0x43,0x09,0x06,0xde,0xcc,0x92,0x43,0xf7,0x17,0x87,
14690 0x43,0x08,0xb6,0x89,0x90,0x43,0xae,0x87,0x88,0x43,0x4a,0xa5,0x90,0x43,0xa1,0xde,0x8b,0x43,0xf9,0x2a,
14691 0x8e,0x43,0x23,0x62,0x8e,0x43,0x08,0xf5,0x2f,0x8b,0x43,0x5c,0x49,0x90,0x43,0x35,0xd6,0x85,0x43,0x8e,
14692 0x46,0x8e,0x43,0x3d,0xb4,0x87,0x43,0x47,0xaa,0x8d,0x43,0x08,0x6a,0xfe,0x8e,0x43,0xff,0x0d,0x8d,0x43,
14693 0xbb,0x6c,0x8f,0x43,0xf7,0x17,0x87,0x43,0x5c,0x31,0x8c,0x43,0xb2,0x5e,0x85,0x43,0x09,0x06,0x60,0x38,
14694 0x91,0x43,0x69,0x5d,0x7a,0x43,0x08,0x34,0x1e,0x92,0x43,0x1e,0x5b,0x89,0x43,0x04,0x63,0x7e,0x43,0x5e,
14695 0x01,0x84,0x43,0x59,0x2a,0x87,0x43,0x0d,0xcf,0x8d,0x43,0x09,0x03,0x04,0x06,0x5a,0x18,0x63,0x43,0x82,
14696 0x79,0x8b,0x43,0x08,0x25,0x2c,0x64,0x43,0x82,0x79,0x8b,0x43,0x2a,0x1b,0x65,0x43,0x9d,0xef,0x8a,0x43,
14697 0x2a,0x1b,0x65,0x43,0xc1,0x37,0x8a,0x43,0x08,0x2a,0x1b,0x65,0x43,0x17,0x89,0x89,0x43,0x25,0x2c,0x64,
14698 0x43,0x31,0xff,0x88,0x43,0x5a,0x18,0x63,0x43,0x31,0xff,0x88,0x43,0x08,0xf3,0x16,0x62,0x43,0x31,0xff,
14699 0x88,0x43,0xee,0x27,0x61,0x43,0x17,0x89,0x89,0x43,0xee,0x27,0x61,0x43,0xc1,0x37,0x8a,0x43,0x08,0xee,
14700 0x27,0x61,0x43,0x9d,0xef,0x8a,0x43,0xf3,0x16,0x62,0x43,0x82,0x79,0x8b,0x43,0x5a,0x18,0x63,0x43,0x82,
14701 0x79,0x8b,0x43,0x09,0x06,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x08,0x34,0xee,0x89,0x43,0x82,0x79,
14702 0x8b,0x43,0x85,0x5c,0x8a,0x43,0x9d,0xef,0x8a,0x43,0x85,0x5c,0x8a,0x43,0xc1,0x37,0x8a,0x43,0x08,0x85,
14703 0x5c,0x8a,0x43,0x17,0x89,0x89,0x43,0x34,0xee,0x89,0x43,0x31,0xff,0x88,0x43,0x4f,0x64,0x89,0x43,0x31,
14704 0xff,0x88,0x43,0x08,0x9c,0xe3,0x88,0x43,0x31,0xff,0x88,0x43,0x19,0x6c,0x88,0x43,0x17,0x89,0x89,0x43,
14705 0x19,0x6c,0x88,0x43,0xc1,0x37,0x8a,0x43,0x08,0x19,0x6c,0x88,0x43,0x9d,0xef,0x8a,0x43,0x9c,0xe3,0x88,
14706 0x43,0x82,0x79,0x8b,0x43,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x09,0x02,0x04,0x06,0x19,0x60,0x86,
14707 0x43,0xec,0xed,0xa3,0x43,0x08,0x35,0xd6,0x85,0x43,0x76,0x43,0xa6,0x43,0x93,0xe1,0x80,0x43,0x57,0x02,
14708 0xac,0x43,0x61,0xd8,0x80,0x43,0x87,0x17,0xae,0x43,0x08,0xa5,0x85,0x80,0x43,0xc3,0xfe,0xaf,0x43,0xce,
14709 0xbc,0x80,0x43,0x83,0x40,0xb1,0x43,0xa5,0x91,0x82,0x43,0x79,0x6e,0xb1,0x43,0x08,0x23,0x26,0x84,0x43,
14710 0x40,0x93,0xb1,0x43,0x30,0xe7,0x84,0x43,0xbe,0x1b,0xb1,0x43,0x11,0x82,0x84,0x43,0xab,0x6b,0xaf,0x43,
14711 0x08,0xb7,0x41,0x84,0x43,0x3b,0x98,0xae,0x43,0xb7,0x41,0x84,0x43,0xc3,0xf2,0xad,0x43,0xa1,0xae,0x83,
14712 0x43,0x83,0x28,0xad,0x43,0x08,0xb2,0x52,0x83,0x43,0x80,0x39,0xac,0x43,0x81,0x49,0x83,0x43,0xf0,0x00,
14713 0xab,0x43,0xe4,0x67,0x85,0x43,0x76,0x4f,0xa8,0x43,0x08,0x9c,0xd7,0x86,0x43,0xd1,0x83,0xa6,0x43,0xec,
14714 0x45,0x87,0x43,0x01,0x75,0xa2,0x43,0x19,0x60,0x86,0x43,0xec,0xed,0xa3,0x43,0x09,0x06,0xd9,0xdc,0x6c,
14715 0x43,0x14,0x25,0xa4,0x43,0x08,0xa2,0xf0,0x6d,0x43,0x9f,0x7a,0xa6,0x43,0x47,0xec,0x77,0x43,0x80,0x39,
14716 0xac,0x43,0xa9,0xfe,0x77,0x43,0xb0,0x4e,0xae,0x43,0x08,0x23,0xa4,0x78,0x43,0xea,0x35,0xb0,0x43,0xd2,
14717 0x35,0x78,0x43,0xab,0x77,0xb1,0x43,0xc1,0x79,0x74,0x43,0xa2,0xa5,0xb1,0x43,0x08,0xc6,0x50,0x71,0x43,
14718 0x68,0xca,0xb1,0x43,0xab,0xce,0x6f,0x43,0xe7,0x52,0xb1,0x43,0xea,0x98,0x70,0x43,0xd4,0xa2,0xaf,0x43,
14719 0x08,0x9d,0x19,0x71,0x43,0x96,0xd8,0xae,0x43,0x9d,0x19,0x71,0x43,0xec,0x29,0xae,0x43,0xca,0x3f,0x72,
14720 0x43,0xab,0x5f,0xad,0x43,0x08,0xa6,0xf7,0x72,0x43,0xa7,0x70,0xac,0x43,0x09,0x0a,0x73,0x43,0x17,0x38,
14721 0xab,0x43,0x44,0xcd,0x6e,0x43,0x9f,0x86,0xa8,0x43,0x08,0xd4,0xed,0x6b,0x43,0xf8,0xba,0xa6,0x43,0x31,
14722 0x11,0x6b,0x43,0x2a,0xac,0xa2,0x43,0xd9,0xdc,0x6c,0x43,0x14,0x25,0xa4,0x43,0x09,0x01,0x05,0x06,0x66,
14723 0x5d,0x7a,0x43,0x74,0xeb,0xc2,0x43,0x08,0x09,0x22,0x77,0x43,0x50,0xbb,0xc7,0x43,0xe9,0xe0,0x7c,0x43,
14724 0xf5,0x86,0xc9,0x43,0x8f,0x94,0x7a,0x43,0xc5,0x95,0xcd,0x43,0x09,0x06,0x08,0x98,0x80,0x43,0x6b,0x19,
14725 0xc3,0x43,0x08,0xb7,0x35,0x82,0x43,0x79,0xf2,0xc7,0x43,0xf1,0xbe,0x7e,0x43,0x1e,0xbe,0xc9,0x43,0x73,
14726 0x7c,0x80,0x43,0xec,0xcc,0xcd,0x43,0x09,0x06,0x28,0xab,0x7d,0x43,0xae,0xde,0xc6,0x43,0x08,0x1e,0xcd,
14727 0x7b,0x43,0x8a,0xa2,0xc9,0x43,0x30,0x89,0x7f,0x43,0x5c,0x94,0xcc,0x43,0x28,0xab,0x7d,0x43,0x42,0x2a,
14728 0xcf,0x43,0x09,0x01,0x05,0x06,0x24,0x14,0xe0,0x42,0xf5,0x77,0x97,0x43,0x08,0xf7,0x1d,0xe7,0x42,0x74,
14729 0x00,0x97,0x43,0x4d,0x93,0xec,0x42,0xdb,0xf5,0x95,0x43,0x29,0x4b,0xed,0x42,0xcd,0x34,0x95,0x43,0x09,
14730 0x06,0x29,0x7b,0xf5,0x42,0x6f,0x1d,0x98,0x43,0x08,0xe4,0xf1,0xfb,0x42,0x61,0x5c,0x97,0x43,0xdb,0x7d,
14731 0x01,0x43,0xb2,0xbe,0x95,0x43,0x55,0x23,0x02,0x43,0xe7,0xaa,0x94,0x43,0x09,0x06,0x98,0xdc,0x03,0x43,
14732 0xbe,0x8b,0x98,0x43,0x08,0x66,0xdf,0x05,0x43,0x47,0xe6,0x97,0x43,0xae,0x87,0x08,0x43,0x98,0x48,0x96,
14733 0x43,0x61,0x08,0x09,0x43,0xd6,0x06,0x95,0x43,0x09,0x06,0x31,0x0b,0x0b,0x43,0x8e,0x82,0x98,0x43,0x08,
14734 0xdb,0xc5,0x0d,0x43,0x80,0xc1,0x97,0x43,0xd6,0xee,0x10,0x43,0xa9,0xec,0x95,0x43,0x79,0xcb,0x11,0x43,
14735 0x55,0x8f,0x94,0x43,0x09,0x06,0xd1,0x2f,0x18,0x43,0xdb,0x01,0x98,0x43,0x08,0xad,0xe7,0x18,0x43,0x38,
14736 0x25,0x97,0x43,0x8a,0x9f,0x19,0x43,0x80,0xb5,0x95,0x43,0xd6,0x1e,0x19,0x43,0xe0,0xd8,0x94,0x43,0x09,
14737 0x06,0x9a,0x5b,0x1d,0x43,0x58,0x8a,0x97,0x43,0x08,0x01,0x5d,0x1e,0x43,0xf1,0x88,0x96,0x43,0x2f,0x83,
14738 0x1f,0x43,0x19,0xb4,0x94,0x43,0x19,0xf0,0x1e,0x43,0x6f,0x05,0x94,0x43,0x09,0x06,0x0b,0x53,0x24,0x43,
14739 0xae,0xdb,0x96,0x43,0x08,0x25,0xd5,0x25,0x43,0x50,0xac,0x95,0x43,0x53,0xfb,0x26,0x43,0x8a,0x7b,0x93,
14740 0x43,0x76,0x43,0x26,0x43,0xb7,0x95,0x92,0x43,0x09,0x06,0x76,0x5b,0x2a,0x43,0x47,0xda,0x95,0x43,0x08,
14741 0xf3,0xef,0x2b,0x43,0x10,0xe2,0x94,0x43,0x6d,0x95,0x2c,0x43,0xae,0xc3,0x92,0x43,0x68,0xa6,0x2b,0x43,
14742 0x47,0xc2,0x91,0x43,0x09,0x06,0x36,0xc1,0x31,0x43,0x2c,0x58,0x94,0x43,0x08,0x8c,0x1e,0x33,0x43,0x31,
14743 0x3b,0x93,0x43,0x79,0x7a,0x33,0x43,0xff,0x25,0x91,0x43,0xd9,0x9d,0x32,0x43,0xc1,0x5b,0x90,0x43,0x09,
14744 0x06,0x25,0x35,0x36,0x43,0x31,0x3b,0x93,0x43,0x08,0x3f,0xb7,0x37,0x43,0xc1,0x67,0x92,0x43,0xe0,0x93,
14745 0x38,0x43,0xae,0xb7,0x90,0x43,0x7e,0x81,0x38,0x43,0x0d,0xdb,0x8f,0x43,0x09,0x06,0xb5,0x85,0x3b,0x43,
14746 0xe4,0xaf,0x91,0x43,0x08,0xcf,0x07,0x3d,0x43,0x9d,0x13,0x91,0x43,0xbc,0x63,0x3d,0x43,0x47,0xb6,0x8f,
14747 0x43,0xe5,0x9a,0x3d,0x43,0x74,0xd0,0x8e,0x43,0x09,0x06,0xae,0xc6,0x42,0x43,0xa4,0xd9,0x8e,0x43,0x08,
14748 0xca,0x48,0x44,0x43,0xfa,0x2a,0x8e,0x43,0xa2,0x11,0x44,0x43,0x9d,0xfb,0x8c,0x43,0x55,0x92,0x44,0x43,
14749 0x0d,0xc3,0x8b,0x43,0x09,0x06,0x39,0x10,0xc3,0x43,0x34,0x36,0x96,0x43,0x08,0x92,0x44,0xc1,0x43,0xe4,
14750 0xc7,0x95,0x43,0x6f,0xf0,0xbf,0x43,0x4b,0xbd,0x94,0x43,0x47,0xb9,0xbf,0x43,0x0b,0xf3,0x93,0x43,0x09,
14751 0x06,0x8f,0x49,0xbe,0x43,0xb7,0xad,0x96,0x43,0x08,0x11,0xb5,0xbc,0x43,0x77,0xe3,0x95,0x43,0x9c,0xf2,
14752 0xba,0x43,0xfa,0x4e,0x94,0x43,0xae,0x96,0xba,0x43,0x31,0x3b,0x93,0x43,0x09,0x06,0xdb,0xb0,0xb9,0x43,
14753 0x10,0xee,0x96,0x43,0x08,0x42,0xa6,0xb8,0x43,0xc8,0x51,0x96,0x43,0x50,0x5b,0xb7,0x43,0x19,0xb4,0x94,
14754 0x43,0xf7,0x1a,0xb7,0x43,0x58,0x72,0x93,0x43,0x09,0x06,0xf2,0x2b,0xb6,0x43,0x10,0xee,0x96,0x43,0x08,
14755 0x9d,0xce,0xb4,0x43,0x04,0x2d,0x96,0x43,0xed,0x30,0xb3,0x43,0x2c,0x58,0x94,0x43,0xce,0xcb,0xb2,0x43,
14756 0xd6,0xfa,0x92,0x43,0x09,0x06,0x5a,0x09,0xb1,0x43,0x19,0xc0,0x96,0x43,0x08,0x6c,0xad,0xb0,0x43,0x77,
14757 0xe3,0x95,0x43,0x7e,0x51,0xb0,0x43,0xc0,0x73,0x94,0x43,0xd8,0x91,0xb0,0x43,0x1e,0x97,0x93,0x43,0x09,
14758 0x06,0x48,0x4d,0xad,0x43,0xbe,0x7f,0x96,0x43,0x08,0x95,0xcc,0xac,0x43,0x58,0x7e,0x95,0x43,0x4d,0x30,
14759 0xac,0x43,0x80,0xa9,0x93,0x43,0xd8,0x79,0xac,0x43,0xd6,0xfa,0x92,0x43,0x09,0x06,0x90,0xd1,0xa9,0x43,
14760 0x14,0xd1,0x95,0x43,0x08,0x83,0x10,0xa9,0x43,0xb7,0xa1,0x94,0x43,0x3b,0x74,0xa8,0x43,0xf1,0x70,0x92,
14761 0x43,0x29,0xd0,0xa8,0x43,0x1e,0x8b,0x91,0x43,0x09,0x06,0x5a,0xcd,0xa6,0x43,0x8a,0x87,0x95,0x43,0x08,
14762 0x1c,0x03,0xa6,0x43,0x23,0x86,0x94,0x43,0x5f,0xb0,0xa5,0x43,0xc1,0x67,0x92,0x43,0xe1,0x27,0xa6,0x43,
14763 0x8a,0x6f,0x91,0x43,0x09,0x06,0xd4,0x5a,0xa3,0x43,0x2c,0x58,0x94,0x43,0x08,0x29,0xac,0xa2,0x43,0x31,
14764 0x3b,0x93,0x43,0x32,0x7e,0xa2,0x43,0xff,0x25,0x91,0x43,0x83,0xec,0xa2,0x43,0x8e,0x52,0x90,0x43,0x09,
14765 0x06,0xf8,0x96,0xa0,0x43,0x1e,0x97,0x93,0x43,0x08,0xeb,0xd5,0x9f,0x43,0x7b,0xba,0x92,0x43,0x99,0x67,
14766 0x9f,0x43,0x9d,0x13,0x91,0x43,0x99,0x67,0x9f,0x43,0xfa,0x36,0x90,0x43,0x09,0x06,0xeb,0xc9,0x9d,0x43,
14767 0xc8,0x39,0x92,0x43,0x08,0xde,0x08,0x9d,0x43,0xb2,0xa6,0x91,0x43,0xe6,0xda,0x9c,0x43,0x2c,0x40,0x90,
14768 0x43,0x52,0xbf,0x9c,0x43,0x5a,0x5a,0x8f,0x43,0x09,0x06,0x37,0x3d,0x9b,0x43,0x85,0x80,0x90,0x43,0x08,
14769 0x2a,0x7c,0x9a,0x43,0xdb,0xd1,0x8f,0x43,0xf0,0xa0,0x9a,0x43,0x7d,0xa2,0x8e,0x43,0x65,0x57,0x9a,0x43,
14770 0xee,0x69,0x8d,0x43,0x09,0x02,0x04,0x06,0x2a,0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x08,0x0d,0x8a,0x31,
14771 0x42,0x9f,0x0e,0x94,0x43,0xf3,0x1f,0x34,0x42,0x3d,0xfc,0x93,0x43,0x63,0xff,0x36,0x42,0xa9,0xe0,0x93,
14772 0x43,0x08,0xb5,0x34,0x5d,0x42,0x0b,0xf3,0x93,0x43,0x6d,0xa4,0x5e,0x42,0x03,0x39,0x98,0x43,0xe7,0x31,
14773 0x5b,0x42,0x93,0x89,0x9d,0x43,0x08,0x02,0x9c,0x58,0x42,0xd4,0x5a,0xa3,0x43,0x38,0x70,0x53,0x42,0x14,
14774 0x49,0xaa,0x43,0xf8,0xed,0x5e,0x42,0x83,0x28,0xad,0x43,0x08,0xea,0x68,0x68,0x42,0x20,0x22,0xaf,0x43,
14775 0x12,0xb8,0x6c,0x42,0xb5,0x49,0xb1,0x43,0x2a,0x4b,0x6d,0x42,0x0d,0x96,0xb3,0x43,0x07,0x2a,0x4b,0x6d,
14776 0x42,0xc6,0x05,0xb5,0x43,0x08,0x87,0x6e,0x6c,0x42,0x68,0xee,0xb7,0x43,0x1c,0x66,0x66,0x42,0x31,0x0e,
14777 0xbb,0x43,0x57,0x11,0x5e,0x42,0x8f,0x49,0xbe,0x43,0x08,0x66,0x96,0x54,0x42,0xb9,0x5c,0xb8,0x43,0x2c,
14778 0x2b,0x3c,0x42,0x68,0xd6,0xb3,0x43,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x07,0x2a,0xf4,0x2e,0x42,
14779 0x61,0xa4,0xa3,0x43,0x08,0x55,0x1a,0x30,0x42,0xf0,0xd0,0xa2,0x43,0xf8,0xf6,0x30,0x42,0xb2,0x06,0xa2,
14780 0x43,0x98,0xd3,0x31,0x42,0xd6,0x4e,0xa1,0x43,0x08,0x1c,0x6f,0x38,0x42,0x2a,0x94,0x9e,0x43,0xc1,0x22,
14781 0x36,0x42,0xf5,0x9b,0x9d,0x43,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x57,
14782 0xa2,0x9b,0x43,0x08,0xab,0x8f,0x35,0x42,0x8a,0xab,0x9b,0x43,0xe9,0x71,0x3a,0x42,0xb2,0xe2,0x9b,0x43,
14783 0xb7,0x74,0x3c,0x42,0x34,0x5a,0x9c,0x43,0x08,0x23,0x7d,0x42,0x42,0x0b,0x2f,0x9e,0x43,0xe5,0x9a,0x3d,
14784 0x42,0x38,0x6d,0xa3,0x43,0x36,0xd9,0x35,0x42,0xf3,0xd7,0xa7,0x43,0x08,0x12,0x61,0x2e,0x42,0xb0,0x42,
14785 0xac,0x43,0x63,0xff,0x36,0x42,0xdd,0x74,0xaf,0x43,0x1e,0xa6,0x45,0x42,0x44,0x82,0xb2,0x43,0x08,0x74,
14786 0x1b,0x4b,0x42,0x79,0x7a,0xb3,0x43,0x10,0x21,0x4f,0x42,0x2a,0x18,0xb5,0x43,0xdb,0x4c,0x54,0x42,0x91,
14787 0x19,0xb6,0x43,0x08,0xee,0x3f,0x65,0x42,0x5f,0x28,0xba,0x43,0xa7,0xaf,0x66,0x42,0xb9,0x50,0xb6,0x43,
14788 0x14,0x58,0x5c,0x42,0xca,0xdc,0xb1,0x43,0x08,0x2c,0x8b,0x4c,0x42,0x4e,0x30,0xac,0x43,0x19,0xcf,0x48,
14789 0x42,0x2a,0xd0,0xa8,0x43,0xbc,0xab,0x49,0x42,0xa9,0x4c,0xa6,0x43,0x08,0x61,0x5f,0x47,0x42,0xfa,0xa2,
14790 0xa2,0x43,0xa7,0xaf,0x66,0x42,0x85,0x98,0x94,0x43,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x07,0x2a,
14791 0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x09,0x06,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x08,0xdc,0xe3,
14792 0xf1,0x41,0xe9,0x9e,0x92,0x43,0xd2,0xe7,0x0b,0x42,0xd6,0x06,0x95,0x43,0x2a,0xf4,0x2e,0x42,0x04,0x21,
14793 0x94,0x43,0x07,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x08,0x87,0x17,0x2e,0x42,0xc3,0x62,0x95,0x43,
14794 0xe7,0x3a,0x2d,0x42,0xf5,0x6b,0x95,0x43,0x44,0x5e,0x2c,0x42,0xf5,0x6b,0x95,0x43,0x08,0xd1,0x47,0x1c,
14795 0x42,0x19,0xc0,0x96,0x43,0x66,0xdf,0x05,0x42,0x38,0x19,0x95,0x43,0x12,0x6a,0x00,0x42,0xb2,0xbe,0x95,
14796 0x43,0x08,0xbb,0x6b,0xea,0x41,0xd6,0x12,0x97,0x43,0x2d,0x82,0xfa,0x41,0x61,0x74,0x9b,0x43,0x7e,0x72,
14797 0x06,0x42,0x8a,0xab,0x9b,0x43,0x08,0xc8,0x39,0x12,0x42,0x4e,0xd0,0x9b,0x43,0x53,0xe3,0x22,0x42,0xc3,
14798 0x86,0x9b,0x43,0x2a,0xf4,0x2e,0x42,0x57,0xa2,0x9b,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,
14799 0x08,0x01,0xa5,0x2a,0x42,0xa4,0x2d,0x9d,0x43,0x96,0x9c,0x24,0x42,0x06,0x40,0x9d,0x43,0x8a,0xb7,0x1d,
14800 0x42,0x9a,0x5b,0x9d,0x43,0x08,0x6b,0x16,0x13,0x42,0xcd,0x64,0x9d,0x43,0x42,0xc7,0x0e,0x42,0x9a,0x5b,
14801 0x9d,0x43,0x23,0x26,0x04,0x42,0xcd,0x64,0x9d,0x43,0x08,0xe6,0x91,0xeb,0x41,0x38,0x49,0x9d,0x43,0x73,
14802 0x7b,0xdb,0x41,0xf5,0x83,0x99,0x43,0x7f,0x60,0xe2,0x41,0x0b,0x0b,0x98,0x43,0x08,0x7f,0x60,0xe2,0x41,
14803 0xec,0x99,0x95,0x43,0xe3,0x5a,0xde,0x41,0xbe,0x7f,0x96,0x43,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,
14804 0x07,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x09,0x06,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x08,
14805 0xd4,0x7e,0x29,0x42,0xab,0x6b,0xaf,0x43,0x4e,0x0c,0x26,0x42,0x44,0x6a,0xae,0x43,0x38,0x79,0x25,0x42,
14806 0xd4,0x96,0xad,0x43,0x08,0x25,0xbd,0x21,0x42,0xe2,0x4b,0xac,0x43,0x49,0x35,0x29,0x42,0x9a,0x97,0xa7,
14807 0x43,0x2a,0xf4,0x2e,0x42,0x61,0xa4,0xa3,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x09,0x06,
14808 0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x08,0x86,0x20,0x80,0x43,0x57,0x41,0xe6,0x43,0x7d,0x4e,0x80,
14809 0x43,0x25,0x38,0xe6,0x43,0xa5,0x85,0x80,0x43,0xf3,0x2e,0xe6,0x43,0x08,0x35,0xca,0x83,0x43,0xd4,0xc9,
14810 0xe5,0x43,0x9c,0xd7,0x86,0x43,0x44,0x91,0xe4,0x43,0xd5,0xca,0x8a,0x43,0x91,0x1c,0xe6,0x43,0x08,0x53,
14811 0x5f,0x8c,0x43,0xf8,0x1d,0xe7,0x43,0x2f,0x17,0x8d,0x43,0x4e,0x7b,0xe8,0x43,0x92,0x29,0x8d,0x43,0x2f,
14812 0x22,0xea,0x43,0x07,0x92,0x29,0x8d,0x43,0x44,0xb5,0xea,0x43,0x08,0xfe,0x0d,0x8d,0x43,0x2a,0x4b,0xed,
14813 0x43,0xe3,0x8b,0x8b,0x43,0x55,0x7d,0xf0,0x43,0xec,0x51,0x89,0x43,0x72,0x0b,0xf4,0x43,0x08,0xcd,0xd4,
14814 0x84,0x43,0x9d,0x55,0xfb,0x43,0xc9,0xe5,0x83,0x43,0x74,0x1e,0xfb,0x43,0x73,0x94,0x84,0x43,0x5a,0x90,
14815 0xf7,0x43,0x08,0xe8,0x62,0x88,0x43,0xfd,0x30,0xee,0x43,0x39,0xc5,0x86,0x43,0xdd,0xbf,0xeb,0x43,0x35,
14816 0xbe,0x81,0x43,0x40,0xde,0xed,0x43,0x08,0x4f,0x34,0x81,0x43,0x36,0x0c,0xee,0x43,0x08,0x98,0x80,0x43,
14817 0xfd,0x30,0xee,0x43,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x40,0xec,
14818 0x43,0x08,0x35,0xbe,0x81,0x43,0x06,0xf7,0xeb,0x43,0x15,0x65,0x83,0x43,0x49,0xa4,0xeb,0x43,0x1e,0x43,
14819 0x85,0x43,0xbe,0x5a,0xeb,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0x18,0xea,0x43,0x42,0x97,0x86,0x43,0x5f,
14820 0x67,0xf4,0x43,0xa9,0x98,0x87,0x43,0xd4,0x1d,0xf4,0x43,0x08,0x5c,0x25,0x8a,0x43,0xcf,0x16,0xef,0x43,
14821 0x46,0xaa,0x8d,0x43,0x5a,0x3c,0xe9,0x43,0x19,0x6c,0x88,0x43,0x53,0x5e,0xe7,0x43,0x08,0xc4,0x02,0x85,
14822 0x43,0x96,0x0b,0xe7,0x43,0x85,0x2c,0x82,0x43,0x83,0x67,0xe7,0x43,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,
14823 0x43,0x07,0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x09,0x06,0xfd,0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,
14824 0x08,0xfa,0x6c,0x78,0x43,0xd1,0xc2,0xe0,0x43,0x25,0x5c,0x6c,0x43,0x25,0x44,0xe8,0x43,0x1d,0xe5,0x7f,
14825 0x43,0x87,0x4a,0xe6,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,0x43,0x08,0xa6,0x27,0x7b,0x43,0x91,
14826 0x28,0xe8,0x43,0xbc,0xa2,0x77,0x43,0xb0,0x8d,0xe8,0x43,0xc6,0x68,0x75,0x43,0x57,0x4d,0xe8,0x43,0x08,
14827 0xe0,0xd2,0x72,0x43,0xab,0x9e,0xe7,0x43,0x50,0x9a,0x71,0x43,0x2a,0x27,0xe7,0x43,0xea,0x98,0x70,0x43,
14828 0x57,0x35,0xe4,0x43,0x08,0x94,0x3b,0x6f,0x43,0x14,0x7c,0xe2,0x43,0xff,0x13,0x6d,0x43,0x06,0xbb,0xe1,
14829 0x43,0xcf,0xfe,0x6a,0x43,0x06,0xbb,0xe1,0x43,0x08,0x44,0x9d,0x66,0x43,0x77,0x8e,0xe2,0x43,0x3b,0xef,
14830 0x6c,0x43,0x91,0x10,0xe4,0x43,0xfd,0x24,0x6c,0x43,0xb0,0x81,0xe6,0x43,0x08,0x96,0x23,0x6b,0x43,0xee,
14831 0x57,0xe9,0x43,0xca,0x0f,0x6a,0x43,0x5f,0x37,0xec,0x43,0x55,0x71,0x6e,0x43,0x9f,0x01,0xed,0x43,0x08,
14832 0xdb,0xfb,0x75,0x43,0x3b,0xef,0xec,0x43,0x09,0x3a,0x7b,0x43,0xb0,0xa5,0xec,0x43,0x1d,0xe5,0x7f,0x43,
14833 0x91,0x40,0xec,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x08,0xa9,0x16,0x7c,0x43,0xb0,0xb1,
14834 0xee,0x43,0x47,0xec,0x77,0x43,0xd9,0xe8,0xee,0x43,0x1e,0x9d,0x73,0x43,0xcf,0x16,0xef,0x43,0x08,0x0e,
14835 0xc9,0x6b,0x43,0xee,0x7b,0xef,0x43,0x7e,0x90,0x6a,0x43,0xfd,0x30,0xee,0x43,0x01,0xfc,0x68,0x43,0x4e,
14836 0x93,0xec,0x43,0x08,0x31,0xf9,0x66,0x43,0x4e,0x87,0xea,0x43,0x31,0x11,0x6b,0x43,0xd4,0xd5,0xe7,0x43,
14837 0xd9,0xc4,0x68,0x43,0xd4,0xc9,0xe5,0x43,0x08,0xe5,0x79,0x67,0x43,0x77,0x9a,0xe4,0x43,0x44,0x9d,0x66,
14838 0x43,0xab,0x86,0xe3,0x43,0x7e,0x78,0x66,0x43,0x0b,0xaa,0xe2,0x43,0x07,0x7e,0x78,0x66,0x43,0x57,0x29,
14839 0xe2,0x43,0x08,0xa7,0xaf,0x66,0x43,0xbe,0x1e,0xe1,0x43,0x87,0x56,0x68,0x43,0x77,0x82,0xe0,0x43,0xfd,
14840 0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,0x09,0x06,0xc4,0x41,0xbf,0x43,0x85,0xc0,0x72,0x42,0x08,0x73,0xdf,
14841 0xc0,0x43,0xf4,0x76,0x72,0x42,0x97,0x33,0xc2,0x43,0x85,0xc0,0x72,0x42,0xb2,0xb5,0xc3,0x43,0x64,0x56,
14842 0x75,0x42,0x08,0x03,0x24,0xc4,0x43,0x5e,0x7f,0x78,0x42,0xfa,0x51,0xc4,0x43,0x01,0x85,0x7c,0x42,0x5c,
14843 0x64,0xc4,0x43,0xa0,0xb3,0x80,0x42,0x07,0x5c,0x64,0xc4,0x43,0x10,0x93,0x83,0x42,0x08,0xc8,0x48,0xc4,
14844 0x43,0x1c,0x78,0x8a,0x42,0x27,0x6c,0xc3,0x43,0xaf,0xcf,0x94,0x42,0x23,0x7d,0xc2,0x43,0x99,0x9c,0xa4,
14845 0x42,0x08,0x3d,0xe7,0xbf,0x43,0xfb,0xfd,0xb5,0x42,0xb3,0x9d,0xbf,0x43,0x88,0x17,0xae,0x42,0xc4,0x41,
14846 0xbf,0x43,0x69,0x76,0xa3,0x42,0x07,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x08,0x4f,0x8b,0xbf,0x43,
14847 0xed,0x81,0x91,0x42,0xe4,0xa6,0xbf,0x43,0x5d,0x61,0x94,0x42,0xfa,0x39,0xc0,0x43,0x3b,0x49,0x9d,0x42,
14848 0x08,0x2b,0x43,0xc0,0x43,0x28,0xed,0xa9,0x42,0x61,0x3b,0xc1,0x43,0x00,0x9e,0xa5,0x42,0xe4,0xb2,0xc1,
14849 0x43,0x5d,0x91,0x9c,0x42,0x08,0x78,0xce,0xc1,0x43,0xfd,0x36,0x90,0x42,0x22,0x89,0xc4,0x43,0x81,0x72,
14850 0x86,0x42,0xae,0xc6,0xc2,0x43,0xa0,0xb3,0x80,0x42,0x08,0x54,0x86,0xc2,0x43,0x58,0xd1,0x7e,0x42,0x30,
14851 0x32,0xc1,0x43,0xce,0x5e,0x7b,0x42,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x07,0xc4,0x41,0xbf,0x43,
14852 0x85,0xc0,0x72,0x42,0x09,0x06,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x08,0x35,0xfd,0xbb,0x43,0xa4,
14853 0xa1,0x5c,0x42,0x5e,0x34,0xbc,0x43,0x9d,0x2a,0x70,0x42,0x5e,0x40,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,
14854 0x4c,0x9c,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,0xef,0xbe,0x43,0x0e,0x0a,0x73,0x42,0xc4,0x41,0xbf,0x43,
14855 0x85,0xc0,0x72,0x42,0x07,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x08,0xcd,0x13,0xbf,0x43,0xe8,0xf1,
14856 0x7b,0x42,0xd6,0xe5,0xbe,0x43,0x71,0x3b,0x7c,0x42,0xdf,0xb7,0xbe,0x43,0x71,0x3b,0x7c,0x42,0x08,0x08,
14857 0xe3,0xbc,0x43,0xa4,0x61,0x7d,0x42,0x28,0x3c,0xbb,0x43,0x91,0x45,0x69,0x42,0x28,0x3c,0xbb,0x43,0x58,
14858 0x71,0x6e,0x42,0x08,0xce,0xfb,0xba,0x43,0xd5,0x35,0x78,0x42,0x59,0x45,0xbb,0x43,0x58,0x23,0x82,0x42,
14859 0xa1,0xe1,0xbb,0x43,0xd7,0xbe,0x88,0x42,0x08,0xc9,0x18,0xbc,0x43,0xaf,0x9f,0x8c,0x42,0x1e,0x76,0xbd,
14860 0x43,0x51,0x7c,0x8d,0x42,0xd6,0xe5,0xbe,0x43,0xf4,0x58,0x8e,0x42,0x08,0x9c,0x0a,0xbf,0x43,0x45,0xc7,
14861 0x8e,0x42,0x30,0x26,0xbf,0x43,0x96,0x35,0x8f,0x42,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x07,0xc4,
14862 0x41,0xbf,0x43,0x69,0x76,0xa3,0x42,0x08,0x08,0xef,0xbe,0x43,0xb1,0xd6,0x99,0x42,0xe8,0x89,0xbe,0x43,
14863 0xde,0xc5,0x8d,0x42,0xc0,0x46,0xbc,0x43,0xc2,0x5b,0x90,0x42,0x08,0x9c,0xf2,0xba,0x43,0x86,0x80,0x90,
14864 0x42,0xf2,0x43,0xba,0x43,0xe8,0x73,0x87,0x42,0x8f,0x31,0xba,0x43,0xb6,0xf4,0x7d,0x42,0x07,0x8f,0x31,
14865 0xba,0x43,0x21,0xc6,0x76,0x42,0x08,0xc0,0x3a,0xba,0x43,0x5f,0x48,0x6b,0x42,0xae,0x96,0xba,0x43,0xe3,
14866 0x83,0x61,0x42,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x09,0x06,0xea,0x74,0xea,0x43,0x61,0x44,0x93,
14867 0x43,0x08,0x24,0x5c,0xec,0x43,0x31,0x3b,0x93,0x43,0xfb,0x30,0xee,0x43,0x93,0x4d,0x93,0x43,0x0d,0xe1,
14868 0xef,0x43,0x80,0xa9,0x93,0x43,0x08,0x8f,0x58,0xf0,0x43,0xd1,0x17,0x94,0x43,0xb7,0x8f,0xf0,0x43,0x10,
14869 0xe2,0x94,0x43,0xea,0x98,0xf0,0x43,0xa9,0xec,0x95,0x43,0x07,0xea,0x98,0xf0,0x43,0x38,0x25,0x97,0x43,
14870 0x08,0x23,0x74,0xf0,0x43,0x9f,0x32,0x9a,0x43,0x5a,0x60,0xef,0x43,0x53,0xcb,0x9e,0x43,0x2d,0x3a,0xee,
14871 0x43,0xfd,0x91,0xa3,0x43,0x08,0xa2,0xf0,0xed,0x43,0xdd,0x38,0xa5,0x43,0x17,0xa7,0xed,0x43,0xbe,0xdf,
14872 0xa6,0x43,0x5a,0x54,0xed,0x43,0x9f,0x86,0xa8,0x43,0x08,0xfc,0x24,0xec,0x43,0xca,0xc4,0xad,0x43,0x48,
14873 0xa4,0xeb,0x43,0x40,0x6f,0xab,0x43,0x28,0x3f,0xeb,0x43,0x1c,0x0f,0xa8,0x43,0x08,0x1f,0x6d,0xeb,0x43,
14874 0x72,0x48,0xa3,0x43,0x67,0x09,0xec,0x43,0xd1,0x53,0x9e,0x43,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,0x43,
14875 0x07,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x08,0x7e,0x90,0xea,0x43,0x8a,0x9f,0x99,0x43,0x12,0xac,
14876 0xea,0x43,0xbc,0xa8,0x99,0x43,0xa7,0xc7,0xea,0x43,0xbc,0xa8,0x99,0x43,0x08,0x51,0x76,0xeb,0x43,0x9f,
14877 0x32,0x9a,0x43,0x5e,0x37,0xec,0x43,0x49,0xed,0x9c,0x43,0xb0,0xa5,0xec,0x43,0x2a,0xa0,0xa0,0x43,0x08,
14878 0x09,0xe6,0xec,0x43,0xd1,0x77,0xa4,0x43,0x28,0x4b,0xed,0x43,0x61,0xa4,0xa3,0x43,0xab,0xc2,0xed,0x43,
14879 0x8e,0xb2,0xa0,0x43,0x08,0x70,0xe7,0xed,0x43,0xde,0x08,0x9d,0x43,0x87,0x86,0xf0,0x43,0x2f,0x53,0x97,
14880 0x43,0x87,0x7a,0xee,0x43,0xec,0x99,0x95,0x43,0x08,0xca,0x27,0xee,0x43,0xff,0x3d,0x95,0x43,0x74,0xca,
14881 0xec,0x43,0x55,0x8f,0x94,0x43,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x07,0xea,0x74,0xea,0x43,0x61,
14882 0x44,0x93,0x43,0x09,0x06,0x05,0xd3,0xe5,0x43,0x19,0x9c,0x90,0x43,0x08,0x09,0xc2,0xe6,0x43,0xd1,0xff,
14883 0x8f,0x43,0x4d,0x6f,0xe6,0x43,0x74,0xe8,0x92,0x43,0x3b,0xd7,0xe8,0x43,0xc3,0x56,0x93,0x43,0x08,0x1f,
14884 0x61,0xe9,0x43,0x93,0x4d,0x93,0x43,0x05,0xeb,0xe9,0x43,0x93,0x4d,0x93,0x43,0xea,0x74,0xea,0x43,0x61,
14885 0x44,0x93,0x43,0x07,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x08,0x24,0x50,0xea,0x43,0xe7,0xaa,0x94,
14886 0x43,0x2d,0x22,0xea,0x43,0xe7,0xaa,0x94,0x43,0x36,0xf4,0xe9,0x43,0xe7,0xaa,0x94,0x43,0x08,0xa2,0xcc,
14887 0xe7,0x43,0xe0,0xd8,0x94,0x43,0xd4,0xc9,0xe5,0x43,0x19,0xa8,0x92,0x43,0xd4,0xc9,0xe5,0x43,0x27,0x69,
14888 0x93,0x43,0x08,0x17,0x77,0xe5,0x43,0xe0,0xd8,0x94,0x43,0x67,0xe5,0xe5,0x43,0x47,0xda,0x95,0x43,0x43,
14889 0x9d,0xe6,0x43,0xe2,0xd3,0x97,0x43,0x08,0x9d,0xdd,0xe6,0x43,0xad,0xe7,0x98,0x43,0x09,0xce,0xe8,0x43,
14890 0xff,0x55,0x99,0x43,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x07,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,
14891 0x43,0x08,0x71,0xcf,0xe9,0x43,0x53,0xb3,0x9a,0x43,0xa7,0xbb,0xe8,0x43,0xdb,0x0d,0x9a,0x43,0xc6,0x14,
14892 0xe7,0x43,0xdb,0x0d,0x9a,0x43,0x08,0x48,0x80,0xe5,0x43,0xdb,0x0d,0x9a,0x43,0x0a,0xb6,0xe4,0x43,0xc3,
14893 0x6e,0x97,0x43,0x76,0x9a,0xe4,0x43,0x74,0xf4,0x94,0x43,0x07,0x76,0x9a,0xe4,0x43,0x79,0xd7,0x93,0x43,
14894 0x08,0xd8,0xac,0xe4,0x43,0x66,0x27,0x92,0x43,0x29,0x1b,0xe5,0x43,0xe0,0xc0,0x90,0x43,0x05,0xd3,0xe5,
14895 0x43,0x19,0x9c,0x90,0x43,0x09,0x06,0x1b,0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x08,0x71,0x0b,0xf4,0x42,
14896 0x00,0x0e,0x8d,0x42,0x8c,0x0f,0x01,0x43,0x3e,0xc0,0x89,0x42,0xf3,0x28,0x06,0x43,0x48,0x9e,0x8b,0x42,
14897 0x08,0x15,0x89,0x09,0x43,0x00,0x0e,0x8d,0x42,0xe0,0x9c,0x0a,0x43,0xc1,0x8b,0x98,0x42,0xa6,0xc1,0x0a,
14898 0x43,0x02,0xa5,0xaa,0x42,0x07,0xa6,0xc1,0x0a,0x43,0xf9,0xf6,0xb0,0x42,0x08,0xa6,0xc1,0x0a,0x43,0x47,
14899 0x8e,0xb4,0x42,0x42,0xaf,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0xe0,0x9c,0x0a,0x43,0xba,0x74,0xbc,0x42,0x08,
14900 0xa1,0xd2,0x09,0x43,0x40,0x47,0xd0,0x42,0x0d,0xab,0x07,0x43,0x91,0xb5,0xd0,0x42,0x3b,0xb9,0x04,0x43,
14901 0xec,0x71,0xba,0x42,0x08,0xe5,0x5b,0x03,0x43,0xe3,0x33,0xa8,0x42,0x63,0xd8,0x00,0x43,0xce,0x70,0x9f,
14902 0x42,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x07,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,0x42,0x08,0xed,
14903 0x6f,0xed,0x42,0x73,0x24,0x9d,0x42,0xd8,0x0c,0xf5,0x42,0x99,0x6c,0x9c,0x42,0x27,0xab,0xfd,0x42,0xea,
14904 0xda,0x9c,0x42,0x08,0x36,0xca,0x03,0x43,0x2b,0x94,0x9e,0x42,0x68,0xc7,0x01,0x43,0x8f,0xbe,0xa2,0x42,
14905 0xfa,0x06,0x08,0x43,0x73,0xb4,0xb5,0x42,0x08,0x8e,0x2e,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0x9d,0xe3,0x08,
14906 0x43,0xd7,0x1e,0x99,0x42,0x28,0x15,0x05,0x43,0x32,0x3b,0x93,0x42,0x08,0x63,0xf0,0x04,0x43,0x70,0xed,
14907 0x8f,0x42,0x71,0x0b,0xf4,0x42,0x32,0x3b,0x93,0x42,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x07,0x1b,
14908 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x09,0x06,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,0x08,0x8e,0x55,
14909 0xc0,0x42,0xb8,0x4d,0x86,0x42,0x60,0xbf,0xd7,0x42,0x3e,0xf0,0x91,0x42,0x63,0xf6,0xe4,0x42,0x70,0xed,
14910 0x8f,0x42,0x08,0x7a,0x89,0xe5,0x42,0xac,0xc8,0x8f,0x42,0xcc,0xf7,0xe5,0x42,0xac,0xc8,0x8f,0x42,0x1b,
14911 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x07,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x08,0x63,0xf6,0xe4,
14912 0x42,0x3b,0x19,0x95,0x42,0xe6,0x61,0xe3,0x42,0x00,0x3e,0x95,0x42,0xf4,0x16,0xe2,0x42,0xc4,0x62,0x95,
14913 0x42,0x08,0x6e,0x74,0xd6,0x42,0x15,0xd1,0x95,0x42,0x97,0x63,0xca,0x42,0xaf,0xcf,0x94,0x42,0xfb,0x2d,
14914 0xbe,0x42,0x86,0x80,0x90,0x42,0x08,0x97,0x03,0xba,0x42,0xce,0x10,0x8f,0x42,0x5e,0x28,0xba,0x42,0x3e,
14915 0xf0,0x91,0x42,0xf2,0x4f,0xbc,0x42,0x45,0xf7,0x96,0x42,0x08,0x27,0x54,0xbf,0x42,0x73,0x24,0x9d,0x42,
14916 0xa5,0xe8,0xc0,0x42,0x86,0xe0,0xa0,0x42,0xe4,0xca,0xc5,0x42,0xed,0x11,0xaa,0x42,0x08,0x54,0xaa,0xc8,
14917 0x42,0x86,0x40,0xb1,0x42,0x59,0x81,0xc5,0x42,0xa1,0x11,0xc4,0x42,0x3e,0xe7,0xbf,0x42,0xfb,0x8d,0xce,
14918 0x42,0x08,0xb4,0x6d,0xb7,0x42,0x30,0xc2,0xd9,0x42,0x46,0xf5,0xc9,0x42,0xdf,0x53,0xd9,0x42,0x38,0x40,
14919 0xcb,0x42,0x62,0x8f,0xcf,0x42,0x08,0x7d,0xf9,0xcc,0x42,0xec,0xa1,0xc2,0x42,0x07,0x43,0xcd,0x42,0x6c,
14920 0xdd,0xb8,0x42,0x2b,0x8b,0xcc,0x42,0x92,0xf5,0xaf,0x42,0x08,0xf9,0x8d,0xce,0x42,0x41,0x57,0xa7,0x42,
14921 0x5b,0xb8,0xd2,0x42,0xae,0x2f,0xa5,0x42,0x18,0x2f,0xd9,0x42,0x13,0x2a,0xa1,0x42,0x08,0x41,0x7e,0xdd,
14922 0x42,0xe3,0x03,0xa0,0x42,0x2e,0xf2,0xe1,0x42,0x7c,0x02,0x9f,0x42,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,
14923 0x42,0x07,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x08,0x4d,0x63,0xe4,0x42,0x00,0x9e,0xa5,0x42,0xf4,
14924 0x16,0xe2,0x42,0x15,0x31,0xa6,0x42,0x99,0xca,0xdf,0x42,0x2b,0xc4,0xa6,0x42,0x08,0xc0,0x82,0xc6,0x42,
14925 0xc4,0xc2,0xa5,0x42,0x57,0xe1,0xd5,0x42,0x91,0xb5,0xd0,0x42,0x54,0xda,0xd0,0x42,0x97,0x93,0xd2,0x42,
14926 0x08,0x9c,0x3a,0xc7,0x42,0x17,0x58,0xdc,0x42,0x9c,0x0a,0xbf,0x42,0x6e,0xa4,0xde,0x42,0x90,0x25,0xb8,
14927 0x42,0xdf,0x53,0xd9,0x42,0x08,0x59,0x21,0xb5,0x42,0xf2,0xdf,0xd4,0x42,0x51,0x43,0xb3,0x42,0x91,0xb5,
14928 0xd0,0x42,0xc5,0x29,0xbb,0x42,0x0e,0x1a,0xca,0x42,0x08,0x65,0x36,0xc4,0x42,0xd0,0x07,0xbd,0x42,0x3e,
14929 0xe7,0xbf,0x42,0x37,0x09,0xbe,0x42,0x0c,0xea,0xc1,0x42,0xcd,0xd0,0xaf,0x42,0x08,0x2b,0x5b,0xc4,0x42,
14930 0x18,0x08,0xa3,0x42,0x67,0xa6,0xab,0x42,0x99,0x3c,0x94,0x42,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,
14931 0x09,];
14933 private struct ThePath {
14934 public:
14935 enum Command {
14936 Bounds, // always first, has 4 args (x0, y0, x1, y1)
14937 StrokeMode,
14938 FillMode,
14939 StrokeFillMode,
14940 NormalStroke,
14941 ThinStroke,
14942 MoveTo,
14943 LineTo,
14944 CubicTo, // cubic bezier
14945 EndPath,
14948 public:
14949 const(ubyte)[] path;
14950 uint ppos;
14952 public:
14953 this (const(void)[] apath) pure nothrow @trusted @nogc {
14954 path = cast(const(ubyte)[])apath;
14957 @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (ppos >= path.length); }
14959 Command getCommand () nothrow @trusted @nogc {
14960 pragma(inline, true);
14961 if (ppos >= cast(uint)path.length) assert(0, "invalid path");
14962 return cast(Command)(path.ptr[ppos++]);
14965 // number of (x,y) pairs for this command
14966 static int argCount (in Command cmd) nothrow @safe @nogc {
14967 version(aliced) pragma(inline, true);
14968 if (cmd == Command.Bounds) return 2;
14969 else if (cmd == Command.MoveTo || cmd == Command.LineTo) return 1;
14970 else if (cmd == Command.CubicTo) return 3;
14971 else return 0;
14974 void skipArgs (int argc) nothrow @trusted @nogc {
14975 pragma(inline, true);
14976 ppos += cast(uint)(float.sizeof*2*argc);
14979 float getFloat () nothrow @trusted @nogc {
14980 pragma(inline, true);
14981 if (ppos >= cast(uint)path.length || cast(uint)path.length-ppos < float.sizeof) assert(0, "invalid path");
14982 version(LittleEndian) {
14983 float res = *cast(const(float)*)(&path.ptr[ppos]);
14984 ppos += cast(uint)float.sizeof;
14985 return res;
14986 } else {
14987 static assert(float.sizeof == 4);
14988 uint xp = path.ptr[ppos]|(path.ptr[ppos+1]<<8)|(path.ptr[ppos+2]<<16)|(path.ptr[ppos+3]<<24);
14989 ppos += cast(uint)float.sizeof;
14990 return *cast(const(float)*)(&xp);
14995 // this will add baphomet's background path to the current NanoVega path, so you can fill it.
14996 public void addBaphometBack (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc {
14997 if (nvg is null) return;
14999 auto path = ThePath(baphometPath);
15001 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15002 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15004 bool inPath = false;
15005 while (!path.empty) {
15006 auto cmd = path.getCommand();
15007 switch (cmd) {
15008 case ThePath.Command.MoveTo:
15009 inPath = true;
15010 immutable float ex = getScaledX();
15011 immutable float ey = getScaledY();
15012 nvg.moveTo(ex, ey);
15013 break;
15014 case ThePath.Command.LineTo:
15015 inPath = true;
15016 immutable float ex = getScaledX();
15017 immutable float ey = getScaledY();
15018 nvg.lineTo(ex, ey);
15019 break;
15020 case ThePath.Command.CubicTo: // cubic bezier
15021 inPath = true;
15022 immutable float x1 = getScaledX();
15023 immutable float y1 = getScaledY();
15024 immutable float x2 = getScaledX();
15025 immutable float y2 = getScaledY();
15026 immutable float ex = getScaledX();
15027 immutable float ey = getScaledY();
15028 nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15029 break;
15030 case ThePath.Command.EndPath:
15031 if (inPath) return;
15032 break;
15033 default:
15034 path.skipArgs(path.argCount(cmd));
15035 break;
15040 // this will add baphomet's pupil paths to the current NanoVega path, so you can fill it.
15041 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 {
15042 // pupils starts with "fill-and-stroke" mode
15043 if (nvg is null) return;
15045 auto path = ThePath(baphometPath);
15047 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15048 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15050 bool inPath = false;
15051 bool pupLeft = true;
15052 while (!path.empty) {
15053 auto cmd = path.getCommand();
15054 switch (cmd) {
15055 case ThePath.Command.StrokeFillMode: inPath = true; break;
15056 case ThePath.Command.MoveTo:
15057 if (!inPath) goto default;
15058 static if (!left) { if (pupLeft) goto default; }
15059 static if (!right) { if (!pupLeft) goto default; }
15060 immutable float ex = getScaledX();
15061 immutable float ey = getScaledY();
15062 nvg.moveTo(ex, ey);
15063 break;
15064 case ThePath.Command.LineTo:
15065 if (!inPath) goto default;
15066 static if (!left) { if (pupLeft) goto default; }
15067 static if (!right) { if (!pupLeft) goto default; }
15068 immutable float ex = getScaledX();
15069 immutable float ey = getScaledY();
15070 nvg.lineTo(ex, ey);
15071 break;
15072 case ThePath.Command.CubicTo: // cubic bezier
15073 if (!inPath) goto default;
15074 static if (!left) { if (pupLeft) goto default; }
15075 static if (!right) { if (!pupLeft) goto default; }
15076 immutable float x1 = getScaledX();
15077 immutable float y1 = getScaledY();
15078 immutable float x2 = getScaledX();
15079 immutable float y2 = getScaledY();
15080 immutable float ex = getScaledX();
15081 immutable float ey = getScaledY();
15082 nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15083 break;
15084 case ThePath.Command.EndPath:
15085 if (inPath) {
15086 if (pupLeft) pupLeft = false; else return;
15088 break;
15089 default:
15090 path.skipArgs(path.argCount(cmd));
15091 break;
15096 // mode: 'f' to allow fills; 's' to allow strokes; 'w' to allow stroke widths; 'c' to replace fills with strokes
15097 public void renderBaphomet(string mode="fs") (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc {
15098 template hasChar(char ch, string s) {
15099 static if (s.length == 0) enum hasChar = false;
15100 else static if (s[0] == ch) enum hasChar = true;
15101 else enum hasChar = hasChar!(ch, s[1..$]);
15103 enum AllowStroke = hasChar!('s', mode);
15104 enum AllowFill = hasChar!('f', mode);
15105 enum AllowWidth = hasChar!('w', mode);
15106 enum Contour = hasChar!('c', mode);
15107 //static assert(AllowWidth || AllowFill);
15109 if (nvg is null) return;
15111 auto path = ThePath(baphometPath);
15113 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15114 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15116 int mode = 0;
15117 int sw = ThePath.Command.NormalStroke;
15118 nvg.beginPath();
15119 while (!path.empty) {
15120 auto cmd = path.getCommand();
15121 switch (cmd) {
15122 case ThePath.Command.StrokeMode: mode = ThePath.Command.StrokeMode; break;
15123 case ThePath.Command.FillMode: mode = ThePath.Command.FillMode; break;
15124 case ThePath.Command.StrokeFillMode: mode = ThePath.Command.StrokeFillMode; break;
15125 case ThePath.Command.NormalStroke: sw = ThePath.Command.NormalStroke; break;
15126 case ThePath.Command.ThinStroke: sw = ThePath.Command.ThinStroke; break;
15127 case ThePath.Command.MoveTo:
15128 immutable float ex = getScaledX();
15129 immutable float ey = getScaledY();
15130 nvg.moveTo(ex, ey);
15131 break;
15132 case ThePath.Command.LineTo:
15133 immutable float ex = getScaledX();
15134 immutable float ey = getScaledY();
15135 nvg.lineTo(ex, ey);
15136 break;
15137 case ThePath.Command.CubicTo: // cubic bezier
15138 immutable float x1 = getScaledX();
15139 immutable float y1 = getScaledY();
15140 immutable float x2 = getScaledX();
15141 immutable float y2 = getScaledY();
15142 immutable float ex = getScaledX();
15143 immutable float ey = getScaledY();
15144 nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15145 break;
15146 case ThePath.Command.EndPath:
15147 if (mode == ThePath.Command.FillMode || mode == ThePath.Command.StrokeFillMode) {
15148 static if (AllowFill || Contour) {
15149 static if (Contour) {
15150 if (mode == ThePath.Command.FillMode) { nvg.strokeWidth = 1; nvg.stroke(); }
15151 } else {
15152 nvg.fill();
15156 if (mode == ThePath.Command.StrokeMode || mode == ThePath.Command.StrokeFillMode) {
15157 static if (AllowStroke || Contour) {
15158 static if (AllowWidth) {
15159 if (sw == ThePath.Command.NormalStroke) nvg.strokeWidth = 1;
15160 else if (sw == ThePath.Command.ThinStroke) nvg.strokeWidth = 0.5;
15161 else assert(0, "wtf?!");
15163 nvg.stroke();
15166 nvg.newPath();
15167 break;
15168 default:
15169 path.skipArgs(path.argCount(cmd));
15170 break;
15173 nvg.newPath();