1 // 2 // Would be nice: way to take output of the canvas to an image file (raster and/or svg) 3 // 4 // 5 // Copyright (c) 2013 Mikko Mononen memon@inside.org 6 // 7 // This software is provided 'as-is', without any express or implied 8 // warranty. In no event will the authors be held liable for any damages 9 // arising from the use of this software. 10 // Permission is granted to anyone to use this software for any purpose, 11 // including commercial applications, and to alter it and redistribute it 12 // freely, subject to the following restrictions: 13 // 1. The origin of this software must not be misrepresented; you must not 14 // claim that you wrote the original software. If you use this software 15 // in a product, an acknowledgment in the product documentation would be 16 // appreciated but is not required. 17 // 2. Altered source versions must be plainly marked as such, and must not be 18 // misrepresented as being the original software. 19 // 3. This notice may not be removed or altered from any source distribution. 20 // 21 // Fork developement, feature integration and new bugs: 22 // Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org> 23 // Contains code from various contributors. 24 /** 25 The NanoVega API is modeled loosely on HTML5 canvas API. 26 If you know canvas, you're up to speed with NanoVega in no time. 27 28 $(SIDE_BY_SIDE 29 30 $(COLUMN 31 32 D code with nanovega: 33 34 --- 35 import arsd.simpledisplay; 36 37 import arsd.nanovega; 38 39 void main () { 40 NVGContext nvg; // our NanoVega context 41 42 // we need at least OpenGL3 with GLSL to use NanoVega, 43 // so let's tell simpledisplay about that 44 setOpenGLContextVersion(3, 0); 45 46 // now create OpenGL window 47 auto sdmain = new SimpleWindow(800, 600, "NanoVega Simple Sample", OpenGlOptions.yes, Resizability.allowResizing); 48 49 // we need to destroy NanoVega context on window close 50 // stricly speaking, it is not necessary, as nothing fatal 51 // will happen if you'll forget it, but let's be polite. 52 // note that we cannot do that *after* our window was closed, 53 // as we need alive OpenGL context to do proper cleanup. 54 sdmain.onClosing = delegate () { 55 nvg.kill(); 56 }; 57 58 // this is called just before our window will be shown for the first time. 59 // we must create NanoVega context here, as it needs to initialize 60 // internal OpenGL subsystem with valid OpenGL context. 61 sdmain.visibleForTheFirstTime = delegate () { 62 // yes, that's all 63 nvg = nvgCreateContext(); 64 if (nvg is null) assert(0, "cannot initialize NanoVega"); 65 }; 66 67 // this callback will be called when we will need to repaint our window 68 sdmain.redrawOpenGlScene = delegate () { 69 // fix viewport (we can do this in resize event, or here, it doesn't matter) 70 glViewport(0, 0, sdmain.width, sdmain.height); 71 72 // clear window 73 glClearColor(0, 0, 0, 0); 74 glClear(glNVGClearFlags); // use NanoVega API to get flags for OpenGL call 75 76 { 77 nvg.beginFrame(sdmain.width, sdmain.height); // begin rendering 78 scope(exit) nvg.endFrame(); // and flush render queue on exit 79 80 nvg.beginPath(); // start new path 81 nvg.roundedRect(20.5, 30.5, sdmain.width-40, sdmain.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation) 82 // now set filling mode for our rectangle 83 // you can create colors using HTML syntax, or with convenient constants 84 nvg.fillPaint = nvg.linearGradient(20.5, 30.5, sdmain.width-40, sdmain.height-60, NVGColor("#f70"), NVGColor.green); 85 // now fill our rect 86 nvg.fill(); 87 // and draw a nice outline 88 nvg.strokeColor = NVGColor.white; 89 nvg.strokeWidth = 2; 90 nvg.stroke(); 91 // that's all, folks! 92 } 93 }; 94 95 sdmain.eventLoop(0, // no pulse timer required 96 delegate (KeyEvent event) { 97 if (event == "*-Q" || event == "Escape") { sdmain.close(); return; } // quit on Q, Ctrl+Q, and so on 98 }, 99 ); 100 101 flushGui(); // let OS do it's cleanup 102 } 103 --- 104 ) 105 106 $(COLUMN 107 Javascript code with HTML5 Canvas 108 109 ```html 110 <!DOCTYPE html> 111 <html> 112 <head> 113 <title>NanoVega Simple Sample (HTML5 Translation)</title> 114 <style> 115 body { background-color: black; } 116 </style> 117 </head> 118 <body> 119 <canvas id="my-canvas" width="800" height="600"></canvas> 120 <script> 121 var canvas = document.getElementById("my-canvas"); 122 var context = canvas.getContext("2d"); 123 124 context.beginPath(); 125 126 context.rect(20.5, 30.5, canvas.width - 40, canvas.height - 60); 127 128 var gradient = context.createLinearGradient(20.5, 30.5, canvas.width - 40, canvas.height - 60); 129 gradient.addColorStop(0, "#f70"); 130 gradient.addColorStop(1, "green"); 131 132 context.fillStyle = gradient; 133 context.fill(); 134 context.closePath(); 135 context.strokeStyle = "white"; 136 context.lineWidth = 2; 137 context.stroke(); 138 </script> 139 </body> 140 </html> 141 ``` 142 ) 143 ) 144 145 $(TIP 146 This library can use either inbuilt or BindBC (external dependency) provided bindings for OpenGL and FreeType. 147 Former are used by default, latter can be activated by passing the `bindbc` version specifier to the compiler. 148 ) 149 150 Creating drawing context 151 ======================== 152 153 The drawing context is created using platform specific constructor function. 154 155 --- 156 NVGContext vg = nvgCreateContext(); 157 --- 158 159 $(WARNING You must use created context ONLY in that thread where you created it. 160 There is no way to "transfer" context between threads. Trying to do so 161 will lead to UB.) 162 163 $(WARNING Never issue any commands outside of [beginFrame]/[endFrame]. Trying to 164 do so will lead to UB.) 165 166 167 Drawing shapes with NanoVega 168 ============================ 169 170 Drawing a simple shape using NanoVega consists of four steps: 171 $(LIST 172 * begin a new shape, 173 * define the path to draw, 174 * set fill or stroke, 175 * and finally fill or stroke the path. 176 ) 177 178 --- 179 vg.beginPath(); 180 vg.rect(100, 100, 120, 30); 181 vg.fillColor(nvgRGBA(255, 192, 0, 255)); 182 vg.fill(); 183 --- 184 185 Calling [beginPath] will clear any existing paths and start drawing from blank slate. 186 There are number of number of functions to define the path to draw, such as rectangle, 187 rounded rectangle and ellipse, or you can use the common moveTo, lineTo, bezierTo and 188 arcTo API to compose the paths step by step. 189 190 191 Understanding Composite Paths 192 ============================= 193 194 Because of the way the rendering backend is built in NanoVega, drawing a composite path, 195 that is path consisting from multiple paths defining holes and fills, is a bit more 196 involved. NanoVega uses non-zero filling rule and by default, and paths are wound in counter 197 clockwise order. Keep that in mind when drawing using the low level draw API. In order to 198 wind one of the predefined shapes as a hole, you should call `pathWinding(NVGSolidity.Hole)`, 199 or `pathWinding(NVGSolidity.Solid)` $(B after) defining the path. 200 201 --- 202 vg.beginPath(); 203 vg.rect(100, 100, 120, 30); 204 vg.circle(120, 120, 5); 205 vg.pathWinding(NVGSolidity.Hole); // mark circle as a hole 206 vg.fillColor(nvgRGBA(255, 192, 0, 255)); 207 vg.fill(); 208 --- 209 210 211 Rendering is wrong, what to do? 212 =============================== 213 214 $(LIST 215 * make sure you have created NanoVega context using [nvgCreateContext] call 216 * make sure you have initialised OpenGL with $(B stencil buffer) 217 * make sure you have cleared stencil buffer 218 * make sure all rendering calls happen between [beginFrame] and [endFrame] 219 * to enable more checks for OpenGL errors, add `NVGContextFlag.Debug` flag to [nvgCreateContext] 220 ) 221 222 223 OpenGL state touched by the backend 224 =================================== 225 226 The OpenGL back-end touches following states: 227 228 When textures are uploaded or updated, the following pixel store is set to defaults: 229 `GL_UNPACK_ALIGNMENT`, `GL_UNPACK_ROW_LENGTH`, `GL_UNPACK_SKIP_PIXELS`, `GL_UNPACK_SKIP_ROWS`. 230 Texture binding is also affected. Texture updates can happen when the user loads images, 231 or when new font glyphs are added. Glyphs are added as needed between calls to [beginFrame] 232 and [endFrame]. 233 234 The data for the whole frame is buffered and flushed in [endFrame]. 235 The following code illustrates the OpenGL state touched by the rendering code: 236 237 --- 238 glUseProgram(prog); 239 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 240 glEnable(GL_CULL_FACE); 241 glCullFace(GL_BACK); 242 glFrontFace(GL_CCW); 243 glEnable(GL_BLEND); 244 glDisable(GL_DEPTH_TEST); 245 glDisable(GL_SCISSOR_TEST); 246 glDisable(GL_COLOR_LOGIC_OP); 247 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 248 glStencilMask(0xffffffff); 249 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 250 glStencilFunc(GL_ALWAYS, 0, 0xffffffff); 251 glActiveTexture(GL_TEXTURE1); 252 glActiveTexture(GL_TEXTURE0); 253 glBindBuffer(GL_UNIFORM_BUFFER, buf); 254 glBindVertexArray(arr); 255 glBindBuffer(GL_ARRAY_BUFFER, buf); 256 glBindTexture(GL_TEXTURE_2D, tex); 257 glUniformBlockBinding(... , GLNVG_FRAG_BINDING); 258 --- 259 260 Symbol_groups: 261 262 context_management = 263 ## Context Management 264 265 Functions to create and destory NanoVega context. 266 267 frame_management = 268 ## Frame Management 269 270 To start drawing with NanoVega context, you have to "begin frame", and then 271 "end frame" to flush your rendering commands to GPU. 272 273 composite_operation = 274 ## Composite Operation 275 276 The composite operations in NanoVega are modeled after HTML Canvas API, and 277 the blend func is based on OpenGL (see corresponding manuals for more info). 278 The colors in the blending state have premultiplied alpha. 279 280 color_utils = 281 ## Color Utils 282 283 Colors in NanoVega are stored as ARGB. Zero alpha means "transparent color". 284 285 matrices = 286 ## Matrices and Transformations 287 288 The paths, gradients, patterns and scissor region are transformed by an transformation 289 matrix at the time when they are passed to the API. 290 The current transformation matrix is an affine matrix: 291 292 ---------------------- 293 [sx kx tx] 294 [ky sy ty] 295 [ 0 0 1] 296 ---------------------- 297 298 Where: (sx, sy) define scaling, (kx, ky) skewing, and (tx, ty) translation. 299 The last row is assumed to be (0, 0, 1) and is not stored. 300 301 Apart from [resetTransform], each transformation function first creates 302 specific transformation matrix and pre-multiplies the current transformation by it. 303 304 Current coordinate system (transformation) can be saved and restored using [save] and [restore]. 305 306 The following functions can be used to make calculations on 2x3 transformation matrices. 307 A 2x3 matrix is represented as float[6]. 308 309 state_handling = 310 ## State Handling 311 312 NanoVega contains state which represents how paths will be rendered. 313 The state contains transform, fill and stroke styles, text and font styles, 314 and scissor clipping. 315 316 render_styles = 317 ## Render Styles 318 319 Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern. 320 Solid color is simply defined as a color value, different kinds of paints can be created 321 using [linearGradient], [boxGradient], [radialGradient] and [imagePattern]. 322 323 Current render style can be saved and restored using [save] and [restore]. 324 325 Note that if you want "almost perfect" pixel rendering, you should set aspect ratio to 1, 326 and use `integerCoord+0.5f` as pixel coordinates. 327 328 render_transformations = 329 ## Render Transformations 330 331 Transformation matrix management for the current rendering style. Transformations are applied in 332 backwards order. I.e. if you first translate, and then rotate, your path will be rotated around 333 it's origin, and then translated to the destination point. 334 335 scissoring = 336 ## Scissoring 337 338 Scissoring allows you to clip the rendering into a rectangle. This is useful for various 339 user interface cases like rendering a text edit or a timeline. 340 341 images = 342 ## Images 343 344 NanoVega allows you to load image files in various formats (if arsd loaders are in place) to be used for rendering. 345 In addition you can upload your own image. 346 The parameter imageFlagsList is a list of flags defined in [NVGImageFlag]. 347 348 If you will use your image as fill pattern, it will be scaled by default. To make it repeat, pass 349 [NVGImageFlag.RepeatX] and [NVGImageFlag.RepeatY] flags to image creation function respectively. 350 351 paints = 352 ## Paints 353 354 NanoVega supports four types of paints: linear gradient, box gradient, radial gradient and image pattern. 355 These can be used as paints for strokes and fills. 356 357 gpu_affine = 358 ## Render-Time Affine Transformations 359 360 It is possible to set affine transformation matrix for GPU. That matrix will 361 be applied by the shader code. This can be used to quickly translate and rotate 362 saved paths. Call this $(B only) between [beginFrame] and [endFrame]. 363 364 Note that [beginFrame] resets this matrix to identity one. 365 366 $(WARNING Don't use this for scaling or skewing, or your image will be heavily distorted!) 367 368 paths = 369 ## Paths 370 371 Drawing a new shape starts with [beginPath], it clears all the currently defined paths. 372 Then you define one or more paths and sub-paths which describe the shape. The are functions 373 to draw common shapes like rectangles and circles, and lower level step-by-step functions, 374 which allow to define a path curve by curve. 375 376 NanoVega uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise 377 winding and holes should have counter clockwise order. To specify winding of a path you can 378 call [pathWinding]. This is useful especially for the common shapes, which are drawn CCW. 379 380 Finally you can fill the path using current fill style by calling [fill], and stroke it 381 with current stroke style by calling [stroke]. 382 383 The curve segments and sub-paths are transformed by the current transform. 384 385 picking_api = 386 ## Picking API 387 388 This is picking API that works directly on paths, without rasterizing them first. 389 390 [beginFrame] resets picking state. Then you can create paths as usual, but 391 there is a possibility to perform hit checks $(B before) rasterizing a path. 392 Call either id assigning functions ([currFillHitId]/[currStrokeHitId]), or 393 immediate hit test functions ([hitTestCurrFill]/[hitTestCurrStroke]) 394 before rasterizing (i.e. calling [fill] or [stroke]) to perform hover 395 effects, for example. 396 397 Also note that picking API is ignoring GPU affine transformation matrix. 398 You can "untransform" picking coordinates before checking with [gpuUntransformPoint]. 399 400 $(WARNING Picking API completely ignores clipping. If you want to check for 401 clip regions, you have to manually register them as fill/stroke paths, 402 and perform the necessary logic. See [hitTestForId] function.) 403 404 clipping = 405 ## Clipping with paths 406 407 If scissoring is not enough for you, you can clip rendering with arbitrary path, 408 or with combination of paths. Clip region is saved by [save] and restored by 409 [restore] NanoVega functions. You can combine clip paths with various logic 410 operations, see [NVGClipMode]. 411 412 Note that both [clip] and [clipStroke] are ignoring scissoring (i.e. clip mask 413 is created as if there was no scissor set). Actual rendering is affected by 414 scissors, though. 415 416 text_api = 417 ## Text 418 419 NanoVega allows you to load .ttf files and use the font to render text. 420 You have to load some font, and set proper font size before doing anything 421 with text, as there is no "default" font provided by NanoVega. Also, don't 422 forget to check return value of `createFont()`, 'cause NanoVega won't fail 423 if it cannot load font, it will silently try to render nothing. 424 425 The appearance of the text can be defined by setting the current text style 426 and by specifying the fill color. Common text and font settings such as 427 font size, letter spacing and text align are supported. Font blur allows you 428 to create simple text effects such as drop shadows. 429 430 At render time the font face can be set based on the font handles or name. 431 432 Font measure functions return values in local space, the calculations are 433 carried in the same resolution as the final rendering. This is done because 434 the text glyph positions are snapped to the nearest pixels sharp rendering. 435 436 The local space means that values are not rotated or scale as per the current 437 transformation. For example if you set font size to 12, which would mean that 438 line height is 16, then regardless of the current scaling and rotation, the 439 returned line height is always 16. Some measures may vary because of the scaling 440 since aforementioned pixel snapping. 441 442 While this may sound a little odd, the setup allows you to always render the 443 same way regardless of scaling. I.e. following works regardless of scaling: 444 445 ---------------------- 446 string txt = "Text me up."; 447 vg.textBounds(x, y, txt, bounds); 448 vg.beginPath(); 449 vg.roundedRect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1], 6); 450 vg.fill(); 451 ---------------------- 452 453 Note: currently only solid color fill is supported for text. 454 455 font_stash = 456 ## Low-Level Font Engine (FontStash) 457 458 FontStash is used to load fonts, to manage font atlases, and to get various text metrics. 459 You don't need any graphics context to use FontStash, so you can do things like text 460 layouting outside of your rendering code. Loaded fonts are refcounted, so it is cheap 461 to create new FontStash, copy fonts from NanoVega context into it, and use that new 462 FontStash to do some UI layouting, for example. Also note that you can get text metrics 463 without creating glyph bitmaps (by using [FONSTextBoundsIterator], for example); this way 464 you don't need to waste CPU and memory resources to render unneeded images into font atlas, 465 and you can layout alot of text very fast. 466 467 Note that "FontStash" is abbrevated as "FONS". So when you see some API that contains 468 word "fons" in it, this is not a typo, and it should not read "font" intead. 469 470 TODO for Ketmar: write some nice example code here, and finish documenting FontStash API. 471 */ 472 module arsd.nanovega; 473 private: 474 475 version(aliced) { 476 import iv.meta; 477 import iv.vfs; 478 } else { 479 private alias usize = size_t; 480 // i fear phobos! 481 private template Unqual(T) { 482 static if (is(T U == immutable U)) alias Unqual = U; 483 else static if (is(T U == shared inout const U)) alias Unqual = U; 484 else static if (is(T U == shared inout U)) alias Unqual = U; 485 else static if (is(T U == shared const U)) alias Unqual = U; 486 else static if (is(T U == shared U)) alias Unqual = U; 487 else static if (is(T U == inout const U)) alias Unqual = U; 488 else static if (is(T U == inout U)) alias Unqual = U; 489 else static if (is(T U == const U)) alias Unqual = U; 490 else alias Unqual = T; 491 } 492 private template isAnyCharType(T, bool unqual=false) { 493 static if (unqual) private alias UT = Unqual!T; else private alias UT = T; 494 enum isAnyCharType = is(UT == char) || is(UT == wchar) || is(UT == dchar); 495 } 496 private template isWideCharType(T, bool unqual=false) { 497 static if (unqual) private alias UT = Unqual!T; else private alias UT = T; 498 enum isWideCharType = is(UT == wchar) || is(UT == dchar); 499 } 500 } 501 version(nanovg_disable_vfs) { 502 enum NanoVegaHasIVVFS = false; 503 } else { 504 static if (is(typeof((){import iv.vfs;}))) { 505 enum NanoVegaHasIVVFS = true; 506 import iv.vfs; 507 } else { 508 enum NanoVegaHasIVVFS = false; 509 } 510 } 511 512 // ////////////////////////////////////////////////////////////////////////// // 513 // engine 514 // ////////////////////////////////////////////////////////////////////////// // 515 import core.stdc.stdlib : malloc, realloc, free; 516 import core.stdc.string : memset, memcpy, strlen; 517 import std.math : PI; 518 519 //version = nanovg_force_stb_ttf; 520 521 version(Posix) { 522 version = nanovg_use_freetype; 523 } else { 524 version = nanovg_disable_fontconfig; 525 } 526 527 version (bindbc) { 528 version = nanovg_builtin_fontconfig_bindings; 529 version = nanovg_bindbc_opengl_bindings; 530 version = nanovg_bindbc_freetype_bindings; 531 version(BindFT_Dynamic) 532 static assert(0, "AsumFace was too lazy to write the code for the dynamic bindbc freetype bindings"); 533 else { 534 version(BindFT_Static) {} 535 else 536 static assert(0, "well, duh. you got to pass the BindFT_Static version identifier to the compiler"); 537 } 538 } else version(aliced) { 539 version = nanovg_default_no_font_aa; 540 version = nanovg_builtin_fontconfig_bindings; 541 version = nanovg_builtin_freetype_bindings; 542 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings 543 } else { 544 version = nanovg_builtin_fontconfig_bindings; 545 version = nanovg_builtin_freetype_bindings; 546 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings 547 } 548 549 version(nanovg_disable_fontconfig) { 550 public enum NanoVegaHasFontConfig = false; 551 } else { 552 public enum NanoVegaHasFontConfig = true; 553 version(nanovg_builtin_fontconfig_bindings) {} else import iv.fontconfig; 554 } 555 556 //version = nanovg_bench_flatten; 557 558 /++ 559 Annotation to indicate the marked function is compatible with [arsd.script]. 560 561 562 Any function that takes a [Color] argument will be passed a string instead. 563 564 Scriptable Functions 565 ==================== 566 567 $(UDA_USES) 568 569 $(ALWAYS_DOCUMENT) 570 +/ 571 private enum scriptable = "arsd_jsvar_compatible"; 572 573 public: 574 alias NVG_PI = PI; 575 576 enum NanoVegaHasArsdColor = (is(typeof((){ import arsd.color; }))); 577 enum NanoVegaHasArsdImage = (is(typeof((){ import arsd.color; import arsd.image; }))); 578 579 static if (NanoVegaHasArsdColor) private import arsd.color; 580 static if (NanoVegaHasArsdImage) { 581 private import arsd.image; 582 } else { 583 void stbi_set_unpremultiply_on_load (int flag_true_if_should_unpremultiply) {} 584 void stbi_convert_iphone_png_to_rgb (int flag_true_if_should_convert) {} 585 ubyte* stbi_load (const(char)* filename, int* x, int* y, int* comp, int req_comp) { return null; } 586 ubyte* stbi_load_from_memory (const(void)* buffer, int len, int* x, int* y, int* comp, int req_comp) { return null; } 587 void stbi_image_free (void* retval_from_stbi_load) {} 588 } 589 590 version(nanovg_default_no_font_aa) { 591 __gshared bool NVG_INVERT_FONT_AA = false; 592 } else { 593 __gshared bool NVG_INVERT_FONT_AA = true; 594 } 595 596 597 /// this is branchless for ints on x86, and even for longs on x86_64 598 public ubyte nvgClampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) { 599 static if (__VERSION__ > 2067) pragma(inline, true); 600 static if (T.sizeof == 2 || T.sizeof == 4) { 601 static if (__traits(isUnsigned, T)) { 602 return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24))); 603 } else { 604 n &= -cast(int)(n >= 0); 605 return cast(ubyte)(n|((255-cast(int)n)>>31)); 606 } 607 } else static if (T.sizeof == 1) { 608 static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?"); 609 return cast(ubyte)n; 610 } else static if (T.sizeof == 8) { 611 static if (__traits(isUnsigned, T)) { 612 return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56))); 613 } else { 614 n &= -cast(long)(n >= 0); 615 return cast(ubyte)(n|((255-cast(long)n)>>63)); 616 } 617 } else { 618 static assert(false, "clampToByte: integer too big"); 619 } 620 } 621 622 623 /// NanoVega RGBA color 624 /// Group: color_utils 625 public align(1) struct NVGColor { 626 align(1): 627 public: 628 float[4] rgba = 0; /// default color is transparent (a=1 is opaque) 629 630 public: 631 @property string toString () const @safe { import std.string : format; return "NVGColor(%s,%s,%s,%s)".format(r, g, b, a); } 632 633 public: 634 enum transparent = NVGColor(0.0f, 0.0f, 0.0f, 0.0f); 635 enum k8orange = NVGColor(1.0f, 0.5f, 0.0f, 1.0f); 636 637 enum aliceblue = NVGColor(240, 248, 255); 638 enum antiquewhite = NVGColor(250, 235, 215); 639 enum aqua = NVGColor(0, 255, 255); 640 enum aquamarine = NVGColor(127, 255, 212); 641 enum azure = NVGColor(240, 255, 255); 642 enum beige = NVGColor(245, 245, 220); 643 enum bisque = NVGColor(255, 228, 196); 644 enum black = NVGColor(0, 0, 0); // basic color 645 enum blanchedalmond = NVGColor(255, 235, 205); 646 enum blue = NVGColor(0, 0, 255); // basic color 647 enum blueviolet = NVGColor(138, 43, 226); 648 enum brown = NVGColor(165, 42, 42); 649 enum burlywood = NVGColor(222, 184, 135); 650 enum cadetblue = NVGColor(95, 158, 160); 651 enum chartreuse = NVGColor(127, 255, 0); 652 enum chocolate = NVGColor(210, 105, 30); 653 enum coral = NVGColor(255, 127, 80); 654 enum cornflowerblue = NVGColor(100, 149, 237); 655 enum cornsilk = NVGColor(255, 248, 220); 656 enum crimson = NVGColor(220, 20, 60); 657 enum cyan = NVGColor(0, 255, 255); // basic color 658 enum darkblue = NVGColor(0, 0, 139); 659 enum darkcyan = NVGColor(0, 139, 139); 660 enum darkgoldenrod = NVGColor(184, 134, 11); 661 enum darkgray = NVGColor(169, 169, 169); 662 enum darkgreen = NVGColor(0, 100, 0); 663 enum darkgrey = NVGColor(169, 169, 169); 664 enum darkkhaki = NVGColor(189, 183, 107); 665 enum darkmagenta = NVGColor(139, 0, 139); 666 enum darkolivegreen = NVGColor(85, 107, 47); 667 enum darkorange = NVGColor(255, 140, 0); 668 enum darkorchid = NVGColor(153, 50, 204); 669 enum darkred = NVGColor(139, 0, 0); 670 enum darksalmon = NVGColor(233, 150, 122); 671 enum darkseagreen = NVGColor(143, 188, 143); 672 enum darkslateblue = NVGColor(72, 61, 139); 673 enum darkslategray = NVGColor(47, 79, 79); 674 enum darkslategrey = NVGColor(47, 79, 79); 675 enum darkturquoise = NVGColor(0, 206, 209); 676 enum darkviolet = NVGColor(148, 0, 211); 677 enum deeppink = NVGColor(255, 20, 147); 678 enum deepskyblue = NVGColor(0, 191, 255); 679 enum dimgray = NVGColor(105, 105, 105); 680 enum dimgrey = NVGColor(105, 105, 105); 681 enum dodgerblue = NVGColor(30, 144, 255); 682 enum firebrick = NVGColor(178, 34, 34); 683 enum floralwhite = NVGColor(255, 250, 240); 684 enum forestgreen = NVGColor(34, 139, 34); 685 enum fuchsia = NVGColor(255, 0, 255); 686 enum gainsboro = NVGColor(220, 220, 220); 687 enum ghostwhite = NVGColor(248, 248, 255); 688 enum gold = NVGColor(255, 215, 0); 689 enum goldenrod = NVGColor(218, 165, 32); 690 enum gray = NVGColor(128, 128, 128); // basic color 691 enum green = NVGColor(0, 128, 0); // basic color 692 enum greenyellow = NVGColor(173, 255, 47); 693 enum grey = NVGColor(128, 128, 128); // basic color 694 enum honeydew = NVGColor(240, 255, 240); 695 enum hotpink = NVGColor(255, 105, 180); 696 enum indianred = NVGColor(205, 92, 92); 697 enum indigo = NVGColor(75, 0, 130); 698 enum ivory = NVGColor(255, 255, 240); 699 enum khaki = NVGColor(240, 230, 140); 700 enum lavender = NVGColor(230, 230, 250); 701 enum lavenderblush = NVGColor(255, 240, 245); 702 enum lawngreen = NVGColor(124, 252, 0); 703 enum lemonchiffon = NVGColor(255, 250, 205); 704 enum lightblue = NVGColor(173, 216, 230); 705 enum lightcoral = NVGColor(240, 128, 128); 706 enum lightcyan = NVGColor(224, 255, 255); 707 enum lightgoldenrodyellow = NVGColor(250, 250, 210); 708 enum lightgray = NVGColor(211, 211, 211); 709 enum lightgreen = NVGColor(144, 238, 144); 710 enum lightgrey = NVGColor(211, 211, 211); 711 enum lightpink = NVGColor(255, 182, 193); 712 enum lightsalmon = NVGColor(255, 160, 122); 713 enum lightseagreen = NVGColor(32, 178, 170); 714 enum lightskyblue = NVGColor(135, 206, 250); 715 enum lightslategray = NVGColor(119, 136, 153); 716 enum lightslategrey = NVGColor(119, 136, 153); 717 enum lightsteelblue = NVGColor(176, 196, 222); 718 enum lightyellow = NVGColor(255, 255, 224); 719 enum lime = NVGColor(0, 255, 0); 720 enum limegreen = NVGColor(50, 205, 50); 721 enum linen = NVGColor(250, 240, 230); 722 enum magenta = NVGColor(255, 0, 255); // basic color 723 enum maroon = NVGColor(128, 0, 0); 724 enum mediumaquamarine = NVGColor(102, 205, 170); 725 enum mediumblue = NVGColor(0, 0, 205); 726 enum mediumorchid = NVGColor(186, 85, 211); 727 enum mediumpurple = NVGColor(147, 112, 219); 728 enum mediumseagreen = NVGColor(60, 179, 113); 729 enum mediumslateblue = NVGColor(123, 104, 238); 730 enum mediumspringgreen = NVGColor(0, 250, 154); 731 enum mediumturquoise = NVGColor(72, 209, 204); 732 enum mediumvioletred = NVGColor(199, 21, 133); 733 enum midnightblue = NVGColor(25, 25, 112); 734 enum mintcream = NVGColor(245, 255, 250); 735 enum mistyrose = NVGColor(255, 228, 225); 736 enum moccasin = NVGColor(255, 228, 181); 737 enum navajowhite = NVGColor(255, 222, 173); 738 enum navy = NVGColor(0, 0, 128); 739 enum oldlace = NVGColor(253, 245, 230); 740 enum olive = NVGColor(128, 128, 0); 741 enum olivedrab = NVGColor(107, 142, 35); 742 enum orange = NVGColor(255, 165, 0); 743 enum orangered = NVGColor(255, 69, 0); 744 enum orchid = NVGColor(218, 112, 214); 745 enum palegoldenrod = NVGColor(238, 232, 170); 746 enum palegreen = NVGColor(152, 251, 152); 747 enum paleturquoise = NVGColor(175, 238, 238); 748 enum palevioletred = NVGColor(219, 112, 147); 749 enum papayawhip = NVGColor(255, 239, 213); 750 enum peachpuff = NVGColor(255, 218, 185); 751 enum peru = NVGColor(205, 133, 63); 752 enum pink = NVGColor(255, 192, 203); 753 enum plum = NVGColor(221, 160, 221); 754 enum powderblue = NVGColor(176, 224, 230); 755 enum purple = NVGColor(128, 0, 128); 756 enum red = NVGColor(255, 0, 0); // basic color 757 enum rosybrown = NVGColor(188, 143, 143); 758 enum royalblue = NVGColor(65, 105, 225); 759 enum saddlebrown = NVGColor(139, 69, 19); 760 enum salmon = NVGColor(250, 128, 114); 761 enum sandybrown = NVGColor(244, 164, 96); 762 enum seagreen = NVGColor(46, 139, 87); 763 enum seashell = NVGColor(255, 245, 238); 764 enum sienna = NVGColor(160, 82, 45); 765 enum silver = NVGColor(192, 192, 192); 766 enum skyblue = NVGColor(135, 206, 235); 767 enum slateblue = NVGColor(106, 90, 205); 768 enum slategray = NVGColor(112, 128, 144); 769 enum slategrey = NVGColor(112, 128, 144); 770 enum snow = NVGColor(255, 250, 250); 771 enum springgreen = NVGColor(0, 255, 127); 772 enum steelblue = NVGColor(70, 130, 180); 773 enum tan = NVGColor(210, 180, 140); 774 enum teal = NVGColor(0, 128, 128); 775 enum thistle = NVGColor(216, 191, 216); 776 enum tomato = NVGColor(255, 99, 71); 777 enum turquoise = NVGColor(64, 224, 208); 778 enum violet = NVGColor(238, 130, 238); 779 enum wheat = NVGColor(245, 222, 179); 780 enum white = NVGColor(255, 255, 255); // basic color 781 enum whitesmoke = NVGColor(245, 245, 245); 782 enum yellow = NVGColor(255, 255, 0); // basic color 783 enum yellowgreen = NVGColor(154, 205, 50); 784 785 nothrow @safe @nogc: 786 public: 787 /// 788 this (ubyte ar, ubyte ag, ubyte ab, ubyte aa=255) pure { 789 pragma(inline, true); 790 r = ar/255.0f; 791 g = ag/255.0f; 792 b = ab/255.0f; 793 a = aa/255.0f; 794 } 795 796 /// 797 this (float ar, float ag, float ab, float aa=1.0f) pure { 798 pragma(inline, true); 799 r = ar; 800 g = ag; 801 b = ab; 802 a = aa; 803 } 804 805 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 806 this (uint c) pure { 807 pragma(inline, true); 808 r = (c&0xff)/255.0f; 809 g = ((c>>8)&0xff)/255.0f; 810 b = ((c>>16)&0xff)/255.0f; 811 a = ((c>>24)&0xff)/255.0f; 812 } 813 814 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb" 815 this (const(char)[] srgb) { 816 static int c2d (char ch) pure nothrow @safe @nogc { 817 pragma(inline, true); 818 return 819 ch >= '0' && ch <= '9' ? ch-'0' : 820 ch >= 'A' && ch <= 'F' ? ch-'A'+10 : 821 ch >= 'a' && ch <= 'f' ? ch-'a'+10 : 822 -1; 823 } 824 int[8] digs; 825 int dc = -1; 826 foreach (immutable char ch; srgb) { 827 if (ch <= ' ') continue; 828 if (ch == '#') { 829 if (dc != -1) { dc = -1; break; } 830 dc = 0; 831 } else { 832 if (dc >= digs.length) { dc = -1; break; } 833 if ((digs[dc++] = c2d(ch)) < 0) { dc = -1; break; } 834 } 835 } 836 switch (dc) { 837 case 3: // rgb 838 a = 1.0f; 839 r = digs[0]/15.0f; 840 g = digs[1]/15.0f; 841 b = digs[2]/15.0f; 842 break; 843 case 4: // argb 844 a = digs[0]/15.0f; 845 r = digs[1]/15.0f; 846 g = digs[2]/15.0f; 847 b = digs[3]/15.0f; 848 break; 849 case 6: // rrggbb 850 a = 1.0f; 851 r = (digs[0]*16+digs[1])/255.0f; 852 g = (digs[2]*16+digs[3])/255.0f; 853 b = (digs[4]*16+digs[5])/255.0f; 854 break; 855 case 8: // aarrggbb 856 a = (digs[0]*16+digs[1])/255.0f; 857 r = (digs[2]*16+digs[3])/255.0f; 858 g = (digs[4]*16+digs[5])/255.0f; 859 b = (digs[6]*16+digs[7])/255.0f; 860 break; 861 default: 862 break; 863 } 864 } 865 866 /// Is this color completely opaque? 867 @property bool isOpaque () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] >= 1.0f); } 868 /// Is this color completely transparent? 869 @property bool isTransparent () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] <= 0.0f); } 870 871 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 872 @property uint asUint () const pure { 873 pragma(inline, true); 874 return 875 cast(uint)(r*255)| 876 (cast(uint)(g*255)<<8)| 877 (cast(uint)(b*255)<<16)| 878 (cast(uint)(a*255)<<24); 879 } 880 881 alias asUintABGR = asUint; /// Ditto. 882 883 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 884 static NVGColor fromUint (uint c) pure { pragma(inline, true); return NVGColor(c); } 885 886 alias fromUintABGR = fromUint; /// Ditto. 887 888 /// AARRGGBB 889 @property uint asUintARGB () const pure { 890 pragma(inline, true); 891 return 892 cast(uint)(b*255)| 893 (cast(uint)(g*255)<<8)| 894 (cast(uint)(r*255)<<16)| 895 (cast(uint)(a*255)<<24); 896 } 897 898 /// AARRGGBB 899 static NVGColor fromUintARGB (uint c) pure { pragma(inline, true); return NVGColor((c>>16)&0xff, (c>>8)&0xff, c&0xff, (c>>24)&0xff); } 900 901 @property ref inout(float) r () inout pure @trusted { pragma(inline, true); return rgba.ptr[0]; } /// 902 @property ref inout(float) g () inout pure @trusted { pragma(inline, true); return rgba.ptr[1]; } /// 903 @property ref inout(float) b () inout pure @trusted { pragma(inline, true); return rgba.ptr[2]; } /// 904 @property ref inout(float) a () inout pure @trusted { pragma(inline, true); return rgba.ptr[3]; } /// 905 906 ref NVGColor applyTint() (in auto ref NVGColor tint) nothrow @trusted @nogc { 907 if (tint.a == 0) return this; 908 foreach (immutable idx, ref float v; rgba[0..4]) { 909 v = nvg__clamp(v*tint.rgba.ptr[idx], 0.0f, 1.0f); 910 } 911 return this; 912 } 913 914 NVGHSL asHSL() (bool useWeightedLightness=false) const { pragma(inline, true); return NVGHSL.fromColor(this, useWeightedLightness); } /// 915 static fromHSL() (in auto ref NVGHSL hsl) { pragma(inline, true); return hsl.asColor; } /// 916 917 static if (NanoVegaHasArsdColor) { 918 Color toArsd () const { pragma(inline, true); return Color(cast(int)(r*255), cast(int)(g*255), cast(int)(b*255), cast(int)(a*255)); } /// 919 static NVGColor fromArsd (in Color c) { pragma(inline, true); return NVGColor(c.r, c.g, c.b, c.a); } /// 920 /// 921 this (in Color c) { 922 version(aliced) pragma(inline, true); 923 r = c.r/255.0f; 924 g = c.g/255.0f; 925 b = c.b/255.0f; 926 a = c.a/255.0f; 927 } 928 } 929 } 930 931 932 /// NanoVega A-HSL color 933 /// Group: color_utils 934 public align(1) struct NVGHSL { 935 align(1): 936 float h=0, s=0, l=1, a=1; /// 937 938 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)); } 939 940 nothrow @safe @nogc: 941 public: 942 /// 943 this (float ah, float as, float al, float aa=1) pure { pragma(inline, true); h = ah; s = as; l = al; a = aa; } 944 945 NVGColor asColor () const { pragma(inline, true); return nvgHSLA(h, s, l, a); } /// 946 947 // taken from Adam's arsd.color 948 /** Converts an RGB color into an HSL triplet. 949 * [useWeightedLightness] will try to get a better value for luminosity for the human eye, 950 * which is more sensitive to green than red and more to red than blue. 951 * If it is false, it just does average of the rgb. */ 952 static NVGHSL fromColor() (in auto ref NVGColor c, bool useWeightedLightness=false) pure { 953 NVGHSL res; 954 res.a = c.a; 955 float r1 = c.r; 956 float g1 = c.g; 957 float b1 = c.b; 958 959 float maxColor = r1; 960 if (g1 > maxColor) maxColor = g1; 961 if (b1 > maxColor) maxColor = b1; 962 float minColor = r1; 963 if (g1 < minColor) minColor = g1; 964 if (b1 < minColor) minColor = b1; 965 966 res.l = (maxColor+minColor)/2; 967 if (useWeightedLightness) { 968 // the colors don't affect the eye equally 969 // this is a little more accurate than plain HSL numbers 970 res.l = 0.2126*r1+0.7152*g1+0.0722*b1; 971 } 972 if (maxColor != minColor) { 973 if (res.l < 0.5) { 974 res.s = (maxColor-minColor)/(maxColor+minColor); 975 } else { 976 res.s = (maxColor-minColor)/(2.0-maxColor-minColor); 977 } 978 if (r1 == maxColor) { 979 res.h = (g1-b1)/(maxColor-minColor); 980 } else if(g1 == maxColor) { 981 res.h = 2.0+(b1-r1)/(maxColor-minColor); 982 } else { 983 res.h = 4.0+(r1-g1)/(maxColor-minColor); 984 } 985 } 986 987 res.h = res.h*60; 988 if (res.h < 0) res.h += 360; 989 res.h /= 360; 990 991 return res; 992 } 993 } 994 995 996 //version = nanovega_debug_image_manager; 997 //version = nanovega_debug_image_manager_rc; 998 999 /** NanoVega image handle. 1000 * 1001 * This is refcounted struct, so you don't need to do anything special to free it once it is allocated. 1002 * 1003 * Group: images 1004 */ 1005 struct NVGImage { 1006 enum isOpaqueStruct = true; 1007 private: 1008 NVGContext ctx; 1009 int id; // backend image id 1010 1011 public: 1012 /// 1013 this() (in auto ref NVGImage src) nothrow @trusted @nogc { 1014 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); } 1015 if (src.id > 0 && src.ctx !is null) { 1016 ctx = cast(NVGContext)src.ctx; 1017 id = src.id; 1018 ctx.nvg__imageIncRef(id); 1019 } 1020 } 1021 1022 /// 1023 ~this () nothrow @trusted @nogc { version(aliced) pragma(inline, true); clear(); } 1024 1025 /// 1026 this (this) nothrow @trusted @nogc { 1027 version(aliced) pragma(inline, true); 1028 if (id > 0 && ctx !is null) { 1029 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p postblit (imgid=%d)\n", &this, id); } 1030 ctx.nvg__imageIncRef(id); 1031 } 1032 } 1033 1034 /// 1035 void opAssign() (in auto ref NVGImage src) nothrow @trusted @nogc { 1036 if (src.id <= 0 || src.ctx is null) { 1037 clear(); 1038 } else { 1039 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); } 1040 if (src.id > 0 && src.ctx !is null) (cast(NVGContext)src.ctx).nvg__imageIncRef(src.id); 1041 if (id > 0 && ctx !is null) ctx.nvg__imageDecRef(id); 1042 ctx = cast(NVGContext)src.ctx; 1043 id = src.id; 1044 } 1045 } 1046 1047 /// Free this image. 1048 void clear () nothrow @trusted @nogc { 1049 if (id > 0 && ctx !is null) { 1050 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p cleared (imgid=%d)\n", &this, id); } 1051 ctx.nvg__imageDecRef(id); 1052 } 1053 id = 0; 1054 ctx = null; 1055 } 1056 1057 /// Is this image valid? 1058 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (id > 0 && ctx.valid); } 1059 1060 /// Is the given image valid and comes from the same context? 1061 @property bool isSameContext (const(NVGContext) actx) const pure nothrow @safe @nogc { pragma(inline, true); return (actx !is null && ctx is actx); } 1062 1063 /// Returns image width, or zero for invalid image. 1064 int width () const nothrow @trusted @nogc { 1065 int w = 0; 1066 if (valid) { 1067 int h = void; 1068 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h); 1069 } 1070 return w; 1071 } 1072 1073 /// Returns image height, or zero for invalid image. 1074 int height () const nothrow @trusted @nogc { 1075 int h = 0; 1076 if (valid) { 1077 int w = void; 1078 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h); 1079 } 1080 return h; 1081 } 1082 } 1083 1084 1085 /// Paint parameters for various fills. Don't change anything here! 1086 /// Group: render_styles 1087 public struct NVGPaint { 1088 enum isOpaqueStruct = true; 1089 1090 NVGMatrix xform; 1091 float[2] extent = 0.0f; 1092 float radius = 0.0f; 1093 float feather = 0.0f; 1094 NVGColor innerColor; /// this can be used to modulate images (fill/font) 1095 NVGColor middleColor; 1096 NVGColor outerColor; 1097 float midp = -1; // middle stop for 3-color gradient 1098 NVGImage image; 1099 bool simpleColor; /// if `true`, only innerColor is used, and this is solid-color paint 1100 1101 this() (in auto ref NVGPaint p) nothrow @trusted @nogc { 1102 xform = p.xform; 1103 extent[] = p.extent[]; 1104 radius = p.radius; 1105 feather = p.feather; 1106 innerColor = p.innerColor; 1107 middleColor = p.middleColor; 1108 midp = p.midp; 1109 outerColor = p.outerColor; 1110 image = p.image; 1111 simpleColor = p.simpleColor; 1112 } 1113 1114 void opAssign() (in auto ref NVGPaint p) nothrow @trusted @nogc { 1115 xform = p.xform; 1116 extent[] = p.extent[]; 1117 radius = p.radius; 1118 feather = p.feather; 1119 innerColor = p.innerColor; 1120 middleColor = p.middleColor; 1121 midp = p.midp; 1122 outerColor = p.outerColor; 1123 image = p.image; 1124 simpleColor = p.simpleColor; 1125 } 1126 1127 void clear () nothrow @trusted @nogc { 1128 version(aliced) pragma(inline, true); 1129 import core.stdc.string : memset; 1130 image.clear(); 1131 memset(&this, 0, this.sizeof); 1132 simpleColor = true; 1133 } 1134 } 1135 1136 /// Path winding. 1137 /// Group: paths 1138 public enum NVGWinding { 1139 CCW = 1, /// Winding for solid shapes 1140 CW = 2, /// Winding for holes 1141 } 1142 1143 /// Path solidity. 1144 /// Group: paths 1145 public enum NVGSolidity { 1146 Solid = 1, /// Solid shape (CCW winding). 1147 Hole = 2, /// Hole (CW winding). 1148 } 1149 1150 /// Line cap style. 1151 /// Group: render_styles 1152 public enum NVGLineCap { 1153 Butt, /// 1154 Round, /// 1155 Square, /// 1156 Bevel, /// 1157 Miter, /// 1158 } 1159 1160 /// Text align. 1161 /// Group: text_api 1162 public align(1) struct NVGTextAlign { 1163 align(1): 1164 /// Horizontal align. 1165 enum H : ubyte { 1166 Left = 0, /// Default, align text horizontally to left. 1167 Center = 1, /// Align text horizontally to center. 1168 Right = 2, /// Align text horizontally to right. 1169 } 1170 1171 /// Vertical align. 1172 enum V : ubyte { 1173 Baseline = 0, /// Default, align text vertically to baseline. 1174 Top = 1, /// Align text vertically to top. 1175 Middle = 2, /// Align text vertically to middle. 1176 Bottom = 3, /// Align text vertically to bottom. 1177 } 1178 1179 pure nothrow @safe @nogc: 1180 public: 1181 this (H h) { pragma(inline, true); value = h; } /// 1182 this (V v) { pragma(inline, true); value = cast(ubyte)(v<<4); } /// 1183 this (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1184 this (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1185 void reset () { pragma(inline, true); value = 0; } /// 1186 void reset (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1187 void reset (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1188 @property: 1189 bool left () const { pragma(inline, true); return ((value&0x0f) == H.Left); } /// 1190 void left (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Left : 0)); } /// 1191 bool center () const { pragma(inline, true); return ((value&0x0f) == H.Center); } /// 1192 void center (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Center : 0)); } /// 1193 bool right () const { pragma(inline, true); return ((value&0x0f) == H.Right); } /// 1194 void right (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Right : 0)); } /// 1195 // 1196 bool baseline () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Baseline); } /// 1197 void baseline (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Baseline<<4 : 0)); } /// 1198 bool top () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Top); } /// 1199 void top (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Top<<4 : 0)); } /// 1200 bool middle () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Middle); } /// 1201 void middle (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Middle<<4 : 0)); } /// 1202 bool bottom () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Bottom); } /// 1203 void bottom (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Bottom<<4 : 0)); } /// 1204 // 1205 H horizontal () const { pragma(inline, true); return cast(H)(value&0x0f); } /// 1206 void horizontal (H v) { pragma(inline, true); value = (value&0xf0)|v; } /// 1207 // 1208 V vertical () const { pragma(inline, true); return cast(V)((value>>4)&0x0f); } /// 1209 void vertical (V v) { pragma(inline, true); value = (value&0x0f)|cast(ubyte)(v<<4); } /// 1210 // 1211 private: 1212 ubyte value = 0; // low nibble: horizontal; high nibble: vertical 1213 } 1214 1215 /// Blending type. 1216 /// Group: composite_operation 1217 public enum NVGBlendFactor { 1218 Zero = 1<<0, /// 1219 One = 1<<1, /// 1220 SrcColor = 1<<2, /// 1221 OneMinusSrcColor = 1<<3, /// 1222 DstColor = 1<<4, /// 1223 OneMinusDstColor = 1<<5, /// 1224 SrcAlpha = 1<<6, /// 1225 OneMinusSrcAlpha = 1<<7, /// 1226 DstAlpha = 1<<8, /// 1227 OneMinusDstAlpha = 1<<9, /// 1228 SrcAlphaSaturate = 1<<10, /// 1229 } 1230 1231 /// Composite operation (HTML5-alike). 1232 /// Group: composite_operation 1233 public enum NVGCompositeOperation { 1234 SourceOver, /// 1235 SourceIn, /// 1236 SourceOut, /// 1237 SourceAtop, /// 1238 DestinationOver, /// 1239 DestinationIn, /// 1240 DestinationOut, /// 1241 DestinationAtop, /// 1242 Lighter, /// 1243 Copy, /// 1244 Xor, /// 1245 } 1246 1247 /// Composite operation state. 1248 /// Group: composite_operation 1249 public struct NVGCompositeOperationState { 1250 bool simple; /// `true`: use `glBlendFunc()` instead of `glBlendFuncSeparate()` 1251 NVGBlendFactor srcRGB; /// 1252 NVGBlendFactor dstRGB; /// 1253 NVGBlendFactor srcAlpha; /// 1254 NVGBlendFactor dstAlpha; /// 1255 } 1256 1257 /// Mask combining more 1258 /// Group: clipping 1259 public enum NVGClipMode { 1260 None, /// normal rendering (i.e. render path instead of modifying clip region) 1261 Union, /// old mask will be masked with the current one; this is the default mode for [clip] 1262 Or, /// new mask will be added to the current one (logical `OR` operation); 1263 Xor, /// new mask will be logically `XOR`ed with the current one 1264 Sub, /// "subtract" current path from mask 1265 Replace, /// replace current mask 1266 Add = Or, /// Synonym 1267 } 1268 1269 /// Glyph position info. 1270 /// Group: text_api 1271 public struct NVGGlyphPosition { 1272 usize strpos; /// Position of the glyph in the input string. 1273 float x; /// The x-coordinate of the logical glyph position. 1274 float minx, maxx; /// The bounds of the glyph shape. 1275 } 1276 1277 /// Text row storage. 1278 /// Group: text_api 1279 public struct NVGTextRow(CT) if (isAnyCharType!CT) { 1280 alias CharType = CT; 1281 const(CT)[] s; 1282 int start; /// Index in the input text where the row starts. 1283 int end; /// Index in the input text where the row ends (one past the last character). 1284 float width; /// Logical width of the row. 1285 float minx, maxx; /// Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending. 1286 /// Get rest of the string. 1287 @property const(CT)[] rest () const pure nothrow @trusted @nogc { pragma(inline, true); return (end <= s.length ? s[end..$] : null); } 1288 /// Get current row. 1289 @property const(CT)[] row () const pure nothrow @trusted @nogc { pragma(inline, true); return s[start..end]; } 1290 @property const(CT)[] string () const pure nothrow @trusted @nogc { pragma(inline, true); return s; } 1291 @property void string(CT) (const(CT)[] v) pure nothrow @trusted @nogc { pragma(inline, true); s = v; } 1292 } 1293 1294 /// Image creation flags. 1295 /// Group: images 1296 public enum NVGImageFlag : uint { 1297 None = 0, /// Nothing special. 1298 GenerateMipmaps = 1<<0, /// Generate mipmaps during creation of the image. 1299 RepeatX = 1<<1, /// Repeat image in X direction. 1300 RepeatY = 1<<2, /// Repeat image in Y direction. 1301 FlipY = 1<<3, /// Flips (inverses) image in Y direction when rendered. 1302 Premultiplied = 1<<4, /// Image data has premultiplied alpha. 1303 ClampToBorderX = 1<<5, /// Clamp image to border (instead of clamping to edge by default) 1304 ClampToBorderY = 1<<6, /// Clamp image to border (instead of clamping to edge by default) 1305 NoFiltering = 1<<8, /// use GL_NEAREST instead of GL_LINEAR. Only affects upscaling if GenerateMipmaps is active. 1306 Nearest = NoFiltering, /// compatibility with original NanoVG 1307 NoDelete = 1<<16,/// Do not delete GL texture handle. 1308 } 1309 1310 alias NVGImageFlags = NVGImageFlag; /// Backwards compatibility for [NVGImageFlag]. 1311 1312 1313 // ////////////////////////////////////////////////////////////////////////// // 1314 private: 1315 1316 static T* xdup(T) (const(T)* ptr, int count) nothrow @trusted @nogc { 1317 import core.stdc.stdlib : malloc; 1318 import core.stdc.string : memcpy; 1319 if (count == 0) return null; 1320 T* res = cast(T*)malloc(T.sizeof*count); 1321 if (res is null) assert(0, "NanoVega: out of memory"); 1322 memcpy(res, ptr, T.sizeof*count); 1323 return res; 1324 } 1325 1326 // Internal Render API 1327 enum NVGtexture { 1328 Alpha = 0x01, 1329 RGBA = 0x02, 1330 } 1331 1332 struct NVGscissor { 1333 NVGMatrix xform; 1334 float[2] extent = -1.0f; 1335 } 1336 1337 /// General NanoVega vertex struct. Contains geometry coordinates, and (sometimes unused) texture coordinates. 1338 public struct NVGVertex { 1339 float x, y, u, v; 1340 } 1341 1342 struct NVGpath { 1343 int first; 1344 int count; 1345 bool closed; 1346 int nbevel; 1347 NVGVertex* fill; 1348 int nfill; 1349 NVGVertex* stroke; 1350 int nstroke; 1351 NVGWinding mWinding; 1352 bool convex; 1353 bool cloned; 1354 1355 @disable this (this); // no copies 1356 void opAssign() (in auto ref NVGpath a) { static assert(0, "no copies!"); } 1357 1358 void clear () nothrow @trusted @nogc { 1359 import core.stdc.stdlib : free; 1360 import core.stdc.string : memset; 1361 if (cloned) { 1362 if (stroke !is null && stroke !is fill) free(stroke); 1363 if (fill !is null) free(fill); 1364 } 1365 memset(&this, 0, this.sizeof); 1366 } 1367 1368 // won't clear current path 1369 void copyFrom (const NVGpath* src) nothrow @trusted @nogc { 1370 import core.stdc.string : memcpy; 1371 assert(src !is null); 1372 memcpy(&this, src, NVGpath.sizeof); 1373 this.fill = xdup(src.fill, src.nfill); 1374 if (src.stroke is src.fill) { 1375 this.stroke = this.fill; 1376 } else { 1377 this.stroke = xdup(src.stroke, src.nstroke); 1378 } 1379 this.cloned = true; 1380 } 1381 1382 public @property const(NVGVertex)[] fillVertices () const pure nothrow @trusted @nogc { 1383 pragma(inline, true); 1384 return (nfill > 0 ? fill[0..nfill] : null); 1385 } 1386 1387 public @property const(NVGVertex)[] strokeVertices () const pure nothrow @trusted @nogc { 1388 pragma(inline, true); 1389 return (nstroke > 0 ? stroke[0..nstroke] : null); 1390 } 1391 1392 public @property NVGWinding winding () const pure nothrow @trusted @nogc { pragma(inline, true); return mWinding; } 1393 public @property bool complex () const pure nothrow @trusted @nogc { pragma(inline, true); return !convex; } 1394 } 1395 1396 1397 struct NVGparams { 1398 void* userPtr; 1399 bool edgeAntiAlias; 1400 bool fontAA; 1401 bool function (void* uptr) nothrow @trusted @nogc renderCreate; 1402 int function (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc renderCreateTexture; 1403 bool function (void* uptr, int image) nothrow @trusted @nogc renderTextureIncRef; 1404 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 1405 bool function (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc renderUpdateTexture; 1406 bool function (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc renderGetTextureSize; 1407 void function (void* uptr, int width, int height) nothrow @trusted @nogc renderViewport; // called in [beginFrame] 1408 void function (void* uptr) nothrow @trusted @nogc renderCancel; 1409 void function (void* uptr) nothrow @trusted @nogc renderFlush; 1410 void function (void* uptr) nothrow @trusted @nogc renderPushClip; // backend should support stack of at least [NVG_MAX_STATES] elements 1411 void function (void* uptr) nothrow @trusted @nogc renderPopClip; // backend should support stack of at least [NVG_MAX_STATES] elements 1412 void function (void* uptr) nothrow @trusted @nogc renderResetClip; // reset current clip region to `non-clipped` 1413 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; 1414 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; 1415 void function (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc renderTriangles; 1416 void function (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc renderSetAffine; 1417 void function (void* uptr) nothrow @trusted @nogc renderDelete; 1418 } 1419 1420 // ////////////////////////////////////////////////////////////////////////// // 1421 private: 1422 1423 enum NVG_INIT_FONTIMAGE_SIZE = 512; 1424 enum NVG_MAX_FONTIMAGE_SIZE = 2048; 1425 enum NVG_MAX_FONTIMAGES = 4; 1426 1427 enum NVG_INIT_COMMANDS_SIZE = 256; 1428 enum NVG_INIT_POINTS_SIZE = 128; 1429 enum NVG_INIT_PATHS_SIZE = 16; 1430 enum NVG_INIT_VERTS_SIZE = 256; 1431 enum NVG_MAX_STATES = 32; 1432 1433 public enum NVG_KAPPA90 = 0.5522847493f; /// Length proportional to radius of a cubic bezier handle for 90deg arcs. 1434 enum NVG_MIN_FEATHER = 0.001f; // it should be greater than zero, 'cause it is used in shader for divisions 1435 1436 enum Command { 1437 MoveTo = 0, 1438 LineTo = 1, 1439 BezierTo = 2, 1440 Close = 3, 1441 Winding = 4, 1442 } 1443 1444 enum PointFlag : int { 1445 Corner = 0x01, 1446 Left = 0x02, 1447 Bevel = 0x04, 1448 InnerBevelPR = 0x08, 1449 } 1450 1451 struct NVGstate { 1452 NVGCompositeOperationState compositeOperation; 1453 bool shapeAntiAlias = true; 1454 NVGPaint fill; 1455 NVGPaint stroke; 1456 float strokeWidth = 1.0f; 1457 float miterLimit = 10.0f; 1458 NVGLineCap lineJoin = NVGLineCap.Miter; 1459 NVGLineCap lineCap = NVGLineCap.Butt; 1460 float alpha = 1.0f; 1461 NVGMatrix xform; 1462 NVGscissor scissor; 1463 float fontSize = 16.0f; 1464 float letterSpacing = 0.0f; 1465 float lineHeight = 1.0f; 1466 float fontBlur = 0.0f; 1467 NVGTextAlign textAlign; 1468 int fontId = 0; 1469 bool evenOddMode = false; // use even-odd filling rule (required for some svgs); otherwise use non-zero fill 1470 // dashing 1471 enum MaxDashes = 32; // max 16 dashes 1472 float[MaxDashes] dashes; 1473 uint dashCount = 0; 1474 uint lastFlattenDashCount = 0; 1475 float dashStart = 0; 1476 float totalDashLen; 1477 bool firstDashIsGap = false; 1478 // dasher state for flattener 1479 bool dasherActive = false; 1480 1481 void clearPaint () nothrow @trusted @nogc { 1482 fill.clear(); 1483 stroke.clear(); 1484 } 1485 } 1486 1487 struct NVGpoint { 1488 float x, y; 1489 float dx, dy; 1490 float len; 1491 float dmx, dmy; 1492 ubyte flags; 1493 } 1494 1495 struct NVGpathCache { 1496 NVGpoint* points; 1497 int npoints; 1498 int cpoints; 1499 NVGpath* paths; 1500 int npaths; 1501 int cpaths; 1502 NVGVertex* verts; 1503 int nverts; 1504 int cverts; 1505 float[4] bounds; 1506 // this is required for saved paths 1507 bool strokeReady; 1508 bool fillReady; 1509 float strokeAlphaMul; 1510 float strokeWidth; 1511 float fringeWidth; 1512 bool evenOddMode; 1513 NVGClipMode clipmode; 1514 // non-saved path will not have this 1515 float* commands; 1516 int ncommands; 1517 1518 @disable this (this); // no copies 1519 void opAssign() (in auto ref NVGpathCache a) { static assert(0, "no copies!"); } 1520 1521 // won't clear current path 1522 void copyFrom (const NVGpathCache* src) nothrow @trusted @nogc { 1523 import core.stdc.stdlib : malloc; 1524 import core.stdc.string : memcpy, memset; 1525 assert(src !is null); 1526 memcpy(&this, src, NVGpathCache.sizeof); 1527 this.points = xdup(src.points, src.npoints); 1528 this.cpoints = src.npoints; 1529 this.verts = xdup(src.verts, src.nverts); 1530 this.cverts = src.nverts; 1531 this.commands = xdup(src.commands, src.ncommands); 1532 if (src.npaths > 0) { 1533 this.paths = cast(NVGpath*)malloc(src.npaths*NVGpath.sizeof); 1534 memset(this.paths, 0, npaths*NVGpath.sizeof); 1535 foreach (immutable pidx; 0..npaths) this.paths[pidx].copyFrom(&src.paths[pidx]); 1536 this.cpaths = src.npaths; 1537 } else { 1538 this.npaths = this.cpaths = 0; 1539 } 1540 } 1541 1542 void clear () nothrow @trusted @nogc { 1543 import core.stdc.stdlib : free; 1544 import core.stdc.string : memset; 1545 if (paths !is null) { 1546 foreach (ref p; paths[0..npaths]) p.clear(); 1547 free(paths); 1548 } 1549 if (points !is null) free(points); 1550 if (verts !is null) free(verts); 1551 if (commands !is null) free(commands); 1552 memset(&this, 0, this.sizeof); 1553 } 1554 } 1555 1556 /// Pointer to opaque NanoVega context structure. 1557 /// Group: context_management 1558 public alias NVGContext = NVGcontextinternal*; 1559 1560 /// FontStash context 1561 /// Group: font_stash 1562 public alias FONSContext = FONScontextInternal*; 1563 1564 /// Returns FontStash context of the given NanoVega context. 1565 /// Group: font_stash 1566 public FONSContext fonsContext (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.fs : null); } 1567 1568 /// Returns scale that should be applied to FontStash parameters due to matrix transformations on the context (or 1) 1569 /// Group: font_stash 1570 public float fonsScale (NVGContext ctx) /*pure*/ nothrow @trusted @nogc { 1571 pragma(inline, true); 1572 return (ctx !is null && ctx.contextAlive && ctx.nstates > 0 ? nvg__getFontScale(&ctx.states.ptr[ctx.nstates-1])*ctx.devicePxRatio : 1); 1573 } 1574 1575 /// Setup FontStash from the given NanoVega context font parameters. Note that this will apply transformation scale too. 1576 /// Returns `false` if FontStash or NanoVega context is not active. 1577 /// Group: font_stash 1578 public bool setupFonsFrom (FONSContext stash, NVGContext ctx) nothrow @trusted @nogc { 1579 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false; 1580 NVGstate* state = nvg__getState(ctx); 1581 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 1582 stash.size = state.fontSize*scale; 1583 stash.spacing = state.letterSpacing*scale; 1584 stash.blur = state.fontBlur*scale; 1585 stash.textAlign = state.textAlign; 1586 stash.fontId = state.fontId; 1587 return true; 1588 } 1589 1590 /// Setup NanoVega context font parameters from the given FontStash. Note that NanoVega can apply transformation scale later. 1591 /// Returns `false` if FontStash or NanoVega context is not active. 1592 /// Group: font_stash 1593 public bool setupCtxFrom (NVGContext ctx, FONSContext stash) nothrow @trusted @nogc { 1594 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false; 1595 NVGstate* state = nvg__getState(ctx); 1596 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 1597 state.fontSize = stash.size; 1598 state.letterSpacing = stash.spacing; 1599 state.fontBlur = stash.blur; 1600 state.textAlign = stash.textAlign; 1601 state.fontId = stash.fontId; 1602 return true; 1603 } 1604 1605 /** Bezier curve rasterizer. 1606 * 1607 * De Casteljau Bezier rasterizer is faster, but currently rasterizing curves with cusps sligtly wrong. 1608 * It doesn't really matter in practice. 1609 * 1610 * AFD tesselator is somewhat slower, but does cusps better. 1611 * 1612 * McSeem rasterizer should have the best quality, bit it is the slowest method. Basically, you will 1613 * never notice any visial difference (and this code is not really debugged), so you probably should 1614 * not use it. It is there for further experiments. 1615 */ 1616 public enum NVGTesselation { 1617 DeCasteljau, /// default: standard well-known tesselation algorithm 1618 AFD, /// adaptive forward differencing 1619 DeCasteljauMcSeem, /// standard well-known tesselation algorithm, with improvements from Maxim Shemanarev; slowest one, but should give best results 1620 } 1621 1622 /// Default tesselator for Bezier curves. 1623 public __gshared NVGTesselation NVG_DEFAULT_TESSELATOR = NVGTesselation.DeCasteljau; 1624 1625 1626 // some public info 1627 1628 /// valid only inside [beginFrame]/[endFrame] 1629 /// Group: context_management 1630 public int width (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mWidth : 0); } 1631 1632 /// valid only inside [beginFrame]/[endFrame] 1633 /// Group: context_management 1634 public int height (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mHeight : 0); } 1635 1636 /// valid only inside [beginFrame]/[endFrame] 1637 /// Group: context_management 1638 public float devicePixelRatio (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.devicePxRatio : float.nan); } 1639 1640 /// Returns `true` if [beginFrame] was called, and neither [endFrame], nor [cancelFrame] were. 1641 /// Group: context_management 1642 public bool inFrame (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.nstates > 0 : false); } 1643 1644 // path autoregistration 1645 1646 /// [pickid] to stop autoregistration. 1647 /// Group: context_management 1648 public enum NVGNoPick = -1; 1649 1650 /// >=0: this pickid will be assigned to all filled/stroked paths 1651 /// Group: context_management 1652 public int pickid (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickId : NVGNoPick); } 1653 1654 /// >=0: this pickid will be assigned to all filled/stroked paths 1655 /// Group: context_management 1656 public void pickid (NVGContext ctx, int v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.pathPickId = v; } 1657 1658 /// pick autoregistration mode; see [NVGPickKind] 1659 /// Group: context_management 1660 public uint pickmode (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickRegistered&NVGPickKind.All : 0); } 1661 1662 /// pick autoregistration mode; see [NVGPickKind] 1663 /// Group: context_management 1664 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); } 1665 1666 // tesselator options 1667 1668 /// Get current Bezier tesselation mode. See [NVGTesselation]. 1669 /// Group: context_management 1670 public NVGTesselation tesselation (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.tesselatortype : NVGTesselation.DeCasteljau); } 1671 1672 /// Set current Bezier tesselation mode. See [NVGTesselation]. 1673 /// Group: context_management 1674 public void tesselation (NVGContext ctx, NVGTesselation v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.tesselatortype = v; } 1675 1676 1677 private struct NVGcontextinternal { 1678 private: 1679 NVGparams params; 1680 float* commands; 1681 int ccommands; 1682 int ncommands; 1683 float commandx, commandy; 1684 NVGstate[NVG_MAX_STATES] states; 1685 int nstates; 1686 NVGpathCache* cache; 1687 public float tessTol; 1688 public float angleTol; // 0.0f -- angle tolerance for McSeem Bezier rasterizer 1689 public float cuspLimit; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps) 1690 float distTol; 1691 public float fringeWidth; 1692 float devicePxRatio; 1693 FONSContext fs; 1694 NVGImage[NVG_MAX_FONTIMAGES] fontImages; 1695 int fontImageIdx; 1696 int drawCallCount; 1697 int fillTriCount; 1698 int strokeTriCount; 1699 int textTriCount; 1700 NVGTesselation tesselatortype; 1701 // picking API 1702 NVGpickScene* pickScene; 1703 int pathPickId; // >=0: register all paths for picking using this id 1704 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 1705 // path recording 1706 NVGPathSet recset; 1707 int recstart; // used to cancel recording 1708 bool recblockdraw; 1709 // internals 1710 NVGMatrix gpuAffine; 1711 int mWidth, mHeight; 1712 // image manager 1713 shared int imageCount; // number of alive images in this context 1714 bool contextAlive; // context can be dead, but still contain some images 1715 1716 @disable this (this); // no copies 1717 void opAssign() (in auto ref NVGcontextinternal a) { static assert(0, "no copies!"); } 1718 1719 // debug feature 1720 public @property int getImageCount () nothrow @trusted @nogc { 1721 import core.atomic; 1722 return atomicLoad(imageCount); 1723 } 1724 } 1725 1726 /** Returns number of tesselated pathes in context. 1727 * 1728 * Tesselated pathes are either triangle strips (for strokes), or 1729 * triangle fans (for fills). Note that NanoVega can generate some 1730 * surprising pathes (like fringe stroke for antialiasing, for example). 1731 * 1732 * One render path can contain vertices both for fill, and for stroke triangles. 1733 */ 1734 public int renderPathCount (NVGContext ctx) pure nothrow @trusted @nogc { 1735 pragma(inline, true); 1736 return (ctx !is null && ctx.contextAlive ? ctx.cache.npaths : 0); 1737 } 1738 1739 /** Get vertices of "fill" triangle fan for the given render path. Can return empty slice. 1740 * 1741 * $(WARNING Returned slice can be invalidated by any other NanoVega API call 1742 * (except the calls to render path accessors), and using it in such 1743 * case is UB. So copy vertices to freshly allocated array if you want 1744 * to keep them for further processing.) 1745 */ 1746 public const(NVGVertex)[] renderPathFillVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1747 pragma(inline, true); 1748 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].fillVertices : null); 1749 } 1750 1751 /** Get vertices of "stroke" triangle strip for the given render path. Can return empty slice. 1752 * 1753 * $(WARNING Returned slice can be invalidated by any other NanoVega API call 1754 * (except the calls to render path accessors), and using it in such 1755 * case is UB. So copy vertices to freshly allocated array if you want 1756 * to keep them for further processing.) 1757 */ 1758 public const(NVGVertex)[] renderPathStrokeVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1759 pragma(inline, true); 1760 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].strokeVertices : null); 1761 1762 } 1763 1764 /// Returns winding for the given render path. 1765 public NVGWinding renderPathWinding (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1766 pragma(inline, true); 1767 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].winding : NVGWinding.CCW); 1768 1769 } 1770 1771 /// Returns "complex path" flag for the given render path. 1772 public bool renderPathComplex (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1773 pragma(inline, true); 1774 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].complex : false); 1775 1776 } 1777 1778 void nvg__imageIncRef (NVGContext ctx, int imgid, bool increfInGL=true) nothrow @trusted @nogc { 1779 if (ctx !is null && imgid > 0) { 1780 import core.atomic : atomicOp; 1781 atomicOp!"+="(ctx.imageCount, 1); 1782 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[++]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); } 1783 if (ctx.contextAlive && increfInGL) ctx.params.renderTextureIncRef(ctx.params.userPtr, imgid); 1784 } 1785 } 1786 1787 void nvg__imageDecRef (NVGContext ctx, int imgid) nothrow @trusted @nogc { 1788 if (ctx !is null && imgid > 0) { 1789 import core.atomic : atomicOp; 1790 int icnt = atomicOp!"-="(ctx.imageCount, 1); 1791 if (icnt < 0) assert(0, "NanoVega: internal image refcounting error"); 1792 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[--]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); } 1793 if (ctx.contextAlive) ctx.params.renderDeleteTexture(ctx.params.userPtr, imgid); 1794 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); } 1795 if (!ctx.contextAlive && icnt == 0) { 1796 // it is finally safe to free context memory 1797 import core.stdc.stdlib : free; 1798 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("killed zombie context %p\n", ctx); } 1799 free(ctx); 1800 } 1801 } 1802 } 1803 1804 1805 public import core.stdc.math : 1806 nvg__sqrtf = sqrtf, 1807 nvg__modf = fmodf, 1808 nvg__sinf = sinf, 1809 nvg__cosf = cosf, 1810 nvg__tanf = tanf, 1811 nvg__atan2f = atan2f, 1812 nvg__acosf = acosf, 1813 nvg__ceilf = ceilf; 1814 1815 version(Windows) { 1816 public int nvg__lrintf (float f) nothrow @trusted @nogc { pragma(inline, true); return cast(int)(f+0.5); } 1817 } else { 1818 public import core.stdc.math : nvg__lrintf = lrintf; 1819 } 1820 1821 public auto nvg__min(T) (T a, T b) { pragma(inline, true); return (a < b ? a : b); } 1822 public auto nvg__max(T) (T a, T b) { pragma(inline, true); return (a > b ? a : b); } 1823 public auto nvg__clamp(T) (T a, T mn, T mx) { pragma(inline, true); return (a < mn ? mn : (a > mx ? mx : a)); } 1824 //float nvg__absf() (float a) { pragma(inline, true); return (a >= 0.0f ? a : -a); } 1825 public auto nvg__sign(T) (T a) { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); } 1826 public float nvg__cross() (float dx0, float dy0, float dx1, float dy1) { pragma(inline, true); return (dx1*dy0-dx0*dy1); } 1827 1828 //public import core.stdc.math : nvg__absf = fabsf; 1829 public import core.math : nvg__absf = fabs; 1830 1831 1832 float nvg__normalize (float* x, float* y) nothrow @safe @nogc { 1833 float d = nvg__sqrtf((*x)*(*x)+(*y)*(*y)); 1834 if (d > 1e-6f) { 1835 immutable float id = 1.0f/d; 1836 *x *= id; 1837 *y *= id; 1838 } 1839 return d; 1840 } 1841 1842 void nvg__deletePathCache (ref NVGpathCache* c) nothrow @trusted @nogc { 1843 if (c !is null) { 1844 c.clear(); 1845 free(c); 1846 } 1847 } 1848 1849 NVGpathCache* nvg__allocPathCache () nothrow @trusted @nogc { 1850 NVGpathCache* c = cast(NVGpathCache*)malloc(NVGpathCache.sizeof); 1851 if (c is null) goto error; 1852 memset(c, 0, NVGpathCache.sizeof); 1853 1854 c.points = cast(NVGpoint*)malloc(NVGpoint.sizeof*NVG_INIT_POINTS_SIZE); 1855 if (c.points is null) goto error; 1856 assert(c.npoints == 0); 1857 c.cpoints = NVG_INIT_POINTS_SIZE; 1858 1859 c.paths = cast(NVGpath*)malloc(NVGpath.sizeof*NVG_INIT_PATHS_SIZE); 1860 if (c.paths is null) goto error; 1861 assert(c.npaths == 0); 1862 c.cpaths = NVG_INIT_PATHS_SIZE; 1863 1864 c.verts = cast(NVGVertex*)malloc(NVGVertex.sizeof*NVG_INIT_VERTS_SIZE); 1865 if (c.verts is null) goto error; 1866 assert(c.nverts == 0); 1867 c.cverts = NVG_INIT_VERTS_SIZE; 1868 1869 return c; 1870 1871 error: 1872 nvg__deletePathCache(c); 1873 return null; 1874 } 1875 1876 void nvg__setDevicePixelRatio (NVGContext ctx, float ratio) pure nothrow @safe @nogc { 1877 ctx.tessTol = 0.25f/ratio; 1878 ctx.distTol = 0.01f/ratio; 1879 ctx.fringeWidth = 1.0f/ratio; 1880 ctx.devicePxRatio = ratio; 1881 } 1882 1883 NVGCompositeOperationState nvg__compositeOperationState (NVGCompositeOperation op) pure nothrow @safe @nogc { 1884 NVGCompositeOperationState state; 1885 NVGBlendFactor sfactor, dfactor; 1886 1887 if (op == NVGCompositeOperation.SourceOver) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha;} 1888 else if (op == NVGCompositeOperation.SourceIn) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.Zero; } 1889 else if (op == NVGCompositeOperation.SourceOut) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.Zero; } 1890 else if (op == NVGCompositeOperation.SourceAtop) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } 1891 else if (op == NVGCompositeOperation.DestinationOver) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.One; } 1892 else if (op == NVGCompositeOperation.DestinationIn) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.SrcAlpha; } 1893 else if (op == NVGCompositeOperation.DestinationOut) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } 1894 else if (op == NVGCompositeOperation.DestinationAtop) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.SrcAlpha; } 1895 else if (op == NVGCompositeOperation.Lighter) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.One; } 1896 else if (op == NVGCompositeOperation.Copy) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.Zero; } 1897 else if (op == NVGCompositeOperation.Xor) { 1898 state.simple = false; 1899 state.srcRGB = NVGBlendFactor.OneMinusDstColor; 1900 state.srcAlpha = NVGBlendFactor.OneMinusDstAlpha; 1901 state.dstRGB = NVGBlendFactor.OneMinusSrcColor; 1902 state.dstAlpha = NVGBlendFactor.OneMinusSrcAlpha; 1903 return state; 1904 } 1905 else { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } // default value for invalid op: SourceOver 1906 1907 state.simple = true; 1908 state.srcAlpha = sfactor; 1909 state.dstAlpha = dfactor; 1910 return state; 1911 } 1912 1913 NVGstate* nvg__getState (NVGContext ctx) pure nothrow @trusted @nogc { 1914 pragma(inline, true); 1915 if (ctx is null || !ctx.contextAlive || ctx.nstates == 0) assert(0, "NanoVega: cannot perform commands on inactive context"); 1916 return &ctx.states.ptr[ctx.nstates-1]; 1917 } 1918 1919 // Constructor called by the render back-end. 1920 NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc { 1921 FONSParams fontParams; 1922 NVGContext ctx = cast(NVGContext)malloc(NVGcontextinternal.sizeof); 1923 if (ctx is null) goto error; 1924 memset(ctx, 0, NVGcontextinternal.sizeof); 1925 1926 ctx.angleTol = 0; // angle tolerance for McSeem Bezier rasterizer 1927 ctx.cuspLimit = 0; // cusp limit for McSeem Bezier rasterizer (0: real cusps) 1928 1929 ctx.contextAlive = true; 1930 1931 ctx.params = *params; 1932 //ctx.fontImages[0..NVG_MAX_FONTIMAGES] = 0; 1933 1934 ctx.commands = cast(float*)malloc(float.sizeof*NVG_INIT_COMMANDS_SIZE); 1935 if (ctx.commands is null) goto error; 1936 ctx.ncommands = 0; 1937 ctx.ccommands = NVG_INIT_COMMANDS_SIZE; 1938 1939 ctx.cache = nvg__allocPathCache(); 1940 if (ctx.cache is null) goto error; 1941 1942 ctx.save(); 1943 ctx.reset(); 1944 1945 nvg__setDevicePixelRatio(ctx, 1.0f); 1946 ctx.mWidth = ctx.mHeight = 0; 1947 1948 if (!ctx.params.renderCreate(ctx.params.userPtr)) goto error; 1949 1950 // init font rendering 1951 memset(&fontParams, 0, fontParams.sizeof); 1952 fontParams.width = NVG_INIT_FONTIMAGE_SIZE; 1953 fontParams.height = NVG_INIT_FONTIMAGE_SIZE; 1954 fontParams.flags = FONSParams.Flag.ZeroTopLeft; 1955 fontParams.renderCreate = null; 1956 fontParams.renderUpdate = null; 1957 fontParams.renderDelete = null; 1958 fontParams.userPtr = null; 1959 ctx.fs = FONSContext.create(fontParams); 1960 if (ctx.fs is null) goto error; 1961 1962 // create font texture 1963 ctx.fontImages[0].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, fontParams.width, fontParams.height, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); 1964 if (ctx.fontImages[0].id == 0) goto error; 1965 ctx.fontImages[0].ctx = ctx; 1966 ctx.nvg__imageIncRef(ctx.fontImages[0].id, false); // don't increment driver refcount 1967 ctx.fontImageIdx = 0; 1968 1969 ctx.pathPickId = -1; 1970 ctx.tesselatortype = NVG_DEFAULT_TESSELATOR; 1971 1972 return ctx; 1973 1974 error: 1975 ctx.deleteInternal(); 1976 return null; 1977 } 1978 1979 // Called by render backend. 1980 NVGparams* internalParams (NVGContext ctx) nothrow @trusted @nogc { 1981 return &ctx.params; 1982 } 1983 1984 // Destructor called by the render back-end. 1985 void deleteInternal (ref NVGContext ctx) nothrow @trusted @nogc { 1986 if (ctx is null) return; 1987 if (ctx.contextAlive) { 1988 if (ctx.commands !is null) free(ctx.commands); 1989 nvg__deletePathCache(ctx.cache); 1990 1991 if (ctx.fs) ctx.fs.kill(); 1992 1993 foreach (uint i; 0..NVG_MAX_FONTIMAGES) ctx.fontImages[i].clear(); 1994 1995 if (ctx.params.renderDelete !is null) ctx.params.renderDelete(ctx.params.userPtr); 1996 1997 if (ctx.pickScene !is null) nvg__deletePickScene(ctx.pickScene); 1998 1999 ctx.contextAlive = false; 2000 2001 import core.atomic : atomicLoad; 2002 if (atomicLoad(ctx.imageCount) == 0) { 2003 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("destroyed context %p\n", ctx); } 2004 free(ctx); 2005 } else { 2006 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("context %p is zombie now (%d image refs)\n", ctx, ctx.imageCount); } 2007 } 2008 } 2009 } 2010 2011 /// Delete NanoVega context. 2012 /// Group: context_management 2013 public void kill (ref NVGContext ctx) nothrow @trusted @nogc { 2014 if (ctx !is null) { 2015 ctx.deleteInternal(); 2016 ctx = null; 2017 } 2018 } 2019 2020 /// Returns `true` if the given context is not `null` and can be used for painting. 2021 /// Group: context_management 2022 public bool valid (in NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive); } 2023 2024 2025 // ////////////////////////////////////////////////////////////////////////// // 2026 // Frame Management 2027 2028 /** Begin drawing a new frame. 2029 * 2030 * Calls to NanoVega drawing API should be wrapped in [beginFrame] and [endFrame] 2031 * 2032 * [beginFrame] defines the size of the window to render to in relation currently 2033 * set viewport (i.e. glViewport on GL backends). Device pixel ration allows to 2034 * control the rendering on Hi-DPI devices. 2035 * 2036 * For example, GLFW returns two dimension for an opened window: window size and 2037 * frame buffer size. In that case you would set windowWidth/windowHeight to the window size, 2038 * devicePixelRatio to: `windowWidth/windowHeight`. 2039 * 2040 * Default ratio is `1`. 2041 * 2042 * Note that fractional ratio can (and will) distort your fonts and images. 2043 * 2044 * This call also resets pick marks (see picking API for non-rasterized paths), 2045 * path recording, and GPU affine transformatin matrix. 2046 * 2047 * see also [glNVGClearFlags], which returns necessary flags for [glClear]. 2048 * 2049 * Group: frame_management 2050 */ 2051 public void beginFrame (NVGContext ctx, int windowWidth, int windowHeight, float devicePixelRatio=1.0f) nothrow @trusted @nogc { 2052 import std.math : isNaN; 2053 /* 2054 printf("Tris: draws:%d fill:%d stroke:%d text:%d TOT:%d\n", 2055 ctx.drawCallCount, ctx.fillTriCount, ctx.strokeTriCount, ctx.textTriCount, 2056 ctx.fillTriCount+ctx.strokeTriCount+ctx.textTriCount); 2057 */ 2058 if (ctx.nstates > 0) ctx.cancelFrame(); 2059 2060 if (windowWidth < 1) windowWidth = 1; 2061 if (windowHeight < 1) windowHeight = 1; 2062 2063 if (isNaN(devicePixelRatio)) devicePixelRatio = (windowHeight > 0 ? cast(float)windowWidth/cast(float)windowHeight : 1024.0/768.0); 2064 2065 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2066 ctx.nstates = 0; 2067 ctx.save(); 2068 ctx.reset(); 2069 2070 nvg__setDevicePixelRatio(ctx, devicePixelRatio); 2071 2072 ctx.params.renderViewport(ctx.params.userPtr, windowWidth, windowHeight); 2073 ctx.mWidth = windowWidth; 2074 ctx.mHeight = windowHeight; 2075 2076 ctx.recset = null; 2077 ctx.recstart = -1; 2078 2079 ctx.pathPickId = NVGNoPick; 2080 ctx.pathPickRegistered = 0; 2081 2082 ctx.drawCallCount = 0; 2083 ctx.fillTriCount = 0; 2084 ctx.strokeTriCount = 0; 2085 ctx.textTriCount = 0; 2086 2087 ctx.ncommands = 0; 2088 ctx.pathPickRegistered = 0; 2089 nvg__clearPathCache(ctx); 2090 2091 ctx.gpuAffine = NVGMatrix.Identity; 2092 2093 nvg__pickBeginFrame(ctx, windowWidth, windowHeight); 2094 } 2095 2096 /// Cancels drawing the current frame. Cancels path recording. 2097 /// Group: frame_management 2098 public void cancelFrame (NVGContext ctx) nothrow @trusted @nogc { 2099 ctx.cancelRecording(); 2100 //ctx.mWidth = 0; 2101 //ctx.mHeight = 0; 2102 // cancel render queue 2103 ctx.params.renderCancel(ctx.params.userPtr); 2104 // clear saved states (this may free some textures) 2105 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2106 ctx.nstates = 0; 2107 } 2108 2109 /// Ends drawing the current frame (flushing remaining render state). Commits recorded paths. 2110 /// Group: frame_management 2111 public void endFrame (NVGContext ctx) nothrow @trusted @nogc { 2112 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx); 2113 ctx.stopRecording(); 2114 //ctx.mWidth = 0; 2115 //ctx.mHeight = 0; 2116 // flush render queue 2117 NVGstate* state = nvg__getState(ctx); 2118 ctx.params.renderFlush(ctx.params.userPtr); 2119 if (ctx.fontImageIdx != 0) { 2120 auto fontImage = ctx.fontImages[ctx.fontImageIdx]; 2121 int j = 0, iw, ih; 2122 // delete images that smaller than current one 2123 if (!fontImage.valid) return; 2124 ctx.imageSize(fontImage, iw, ih); 2125 foreach (int i; 0..ctx.fontImageIdx) { 2126 if (ctx.fontImages[i].valid) { 2127 int nw, nh; 2128 ctx.imageSize(ctx.fontImages[i], nw, nh); 2129 if (nw < iw || nh < ih) { 2130 ctx.deleteImage(ctx.fontImages[i]); 2131 } else { 2132 ctx.fontImages[j++] = ctx.fontImages[i]; 2133 } 2134 } 2135 } 2136 // make current font image to first 2137 ctx.fontImages[j++] = ctx.fontImages[0]; 2138 ctx.fontImages[0] = fontImage; 2139 ctx.fontImageIdx = 0; 2140 // clear all images after j 2141 ctx.fontImages[j..NVG_MAX_FONTIMAGES] = NVGImage.init; 2142 } 2143 // clear saved states (this may free some textures) 2144 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2145 ctx.nstates = 0; 2146 } 2147 2148 2149 // ////////////////////////////////////////////////////////////////////////// // 2150 // Recording and Replaying Pathes 2151 2152 // Saved path set. 2153 // Group: path_recording 2154 public alias NVGPathSet = NVGPathSetS*; 2155 2156 2157 //TODO: save scissor info? 2158 struct NVGPathSetS { 2159 private: 2160 // either path cache, or text item 2161 static struct Node { 2162 NVGPaint paint; 2163 NVGpathCache* path; 2164 } 2165 2166 private: 2167 Node* nodes; 2168 int nnodes, cnodes; 2169 NVGpickScene* pickscene; 2170 //int npickscenes, cpickscenes; 2171 NVGContext svctx; // used to do some sanity checks, and to free resources 2172 2173 private: 2174 Node* allocNode () nothrow @trusted @nogc { 2175 import core.stdc.string : memset; 2176 // grow buffer if necessary 2177 if (nnodes+1 > cnodes) { 2178 import core.stdc.stdlib : realloc; 2179 int newsz = (cnodes == 0 ? 8 : cnodes <= 1024 ? cnodes*2 : cnodes+1024); 2180 nodes = cast(Node*)realloc(nodes, newsz*Node.sizeof); 2181 if (nodes is null) assert(0, "NanoVega: out of memory"); 2182 //memset(svp.caches+svp.ccaches, 0, (newsz-svp.ccaches)*NVGpathCache.sizeof); 2183 cnodes = newsz; 2184 } 2185 assert(nnodes < cnodes); 2186 memset(nodes+nnodes, 0, Node.sizeof); 2187 return &nodes[nnodes++]; 2188 } 2189 2190 Node* allocPathNode () nothrow @trusted @nogc { 2191 import core.stdc.stdlib : malloc; 2192 import core.stdc.string : memset; 2193 auto node = allocNode(); 2194 // allocate path cache 2195 auto pc = cast(NVGpathCache*)malloc(NVGpathCache.sizeof); 2196 if (pc is null) assert(0, "NanoVega: out of memory"); 2197 node.path = pc; 2198 return node; 2199 } 2200 2201 void clearNode (int idx) nothrow @trusted @nogc { 2202 if (idx < 0 || idx >= nnodes) return; 2203 Node* node = &nodes[idx]; 2204 if (svctx !is null && node.paint.image.valid) node.paint.image.clear(); 2205 if (node.path !is null) node.path.clear(); 2206 } 2207 2208 private: 2209 void takeCurrentPickScene (NVGContext ctx) nothrow @trusted @nogc { 2210 NVGpickScene* ps = ctx.pickScene; 2211 if (ps is null) return; // nothing to do 2212 if (ps.npaths == 0) return; // pick scene is empty 2213 ctx.pickScene = null; 2214 pickscene = ps; 2215 } 2216 2217 void replay (NVGContext ctx, in ref NVGColor fillTint, in ref NVGColor strokeTint) nothrow @trusted @nogc { 2218 NVGstate* state = nvg__getState(ctx); 2219 foreach (ref node; nodes[0..nnodes]) { 2220 if (auto cc = node.path) { 2221 if (cc.npaths <= 0) continue; 2222 2223 if (cc.fillReady) { 2224 NVGPaint fillPaint = node.paint; 2225 2226 // apply global alpha 2227 fillPaint.innerColor.a *= state.alpha; 2228 fillPaint.middleColor.a *= state.alpha; 2229 fillPaint.outerColor.a *= state.alpha; 2230 2231 fillPaint.innerColor.applyTint(fillTint); 2232 fillPaint.middleColor.applyTint(fillTint); 2233 fillPaint.outerColor.applyTint(fillTint); 2234 2235 ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &fillPaint, &state.scissor, cc.fringeWidth, cc.bounds.ptr, cc.paths, cc.npaths, cc.evenOddMode); 2236 2237 // count triangles 2238 foreach (int i; 0..cc.npaths) { 2239 NVGpath* path = &cc.paths[i]; 2240 ctx.fillTriCount += path.nfill-2; 2241 ctx.fillTriCount += path.nstroke-2; 2242 ctx.drawCallCount += 2; 2243 } 2244 } 2245 2246 if (cc.strokeReady) { 2247 NVGPaint strokePaint = node.paint; 2248 2249 strokePaint.innerColor.a *= cc.strokeAlphaMul; 2250 strokePaint.middleColor.a *= cc.strokeAlphaMul; 2251 strokePaint.outerColor.a *= cc.strokeAlphaMul; 2252 2253 // apply global alpha 2254 strokePaint.innerColor.a *= state.alpha; 2255 strokePaint.middleColor.a *= state.alpha; 2256 strokePaint.outerColor.a *= state.alpha; 2257 2258 strokePaint.innerColor.applyTint(strokeTint); 2259 strokePaint.middleColor.applyTint(strokeTint); 2260 strokePaint.outerColor.applyTint(strokeTint); 2261 2262 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &strokePaint, &state.scissor, cc.fringeWidth, cc.strokeWidth, cc.paths, cc.npaths); 2263 2264 // count triangles 2265 foreach (int i; 0..cc.npaths) { 2266 NVGpath* path = &cc.paths[i]; 2267 ctx.strokeTriCount += path.nstroke-2; 2268 ++ctx.drawCallCount; 2269 } 2270 } 2271 } 2272 } 2273 } 2274 2275 public: 2276 @disable this (this); // no copies 2277 void opAssign() (in auto ref NVGPathSetS a) { static assert(0, "no copies!"); } 2278 2279 // pick test 2280 // Call delegate [dg] for each path under the specified position (in no particular order). 2281 // Returns the id of the path for which delegate [dg] returned true or -1. 2282 // dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending). 2283 int hitTestDG(bool bestOrder=false, DG) (in float x, in float y, NVGPickKind kind, scope DG dg) if (IsGoodHitTestDG!DG || IsGoodHitTestInternalDG!DG) { 2284 if (pickscene is null) return -1; 2285 2286 NVGpickScene* ps = pickscene; 2287 int levelwidth = 1<<(ps.nlevels-1); 2288 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth); 2289 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth); 2290 int npicked = 0; 2291 2292 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) { 2293 NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; 2294 while (pp !is null) { 2295 if (nvg__pickPathTestBounds(svctx, ps, pp, x, y)) { 2296 int hit = 0; 2297 if ((kind&NVGPickKind.Stroke) && (pp.flags&NVGPathFlags.Stroke)) hit = nvg__pickPathStroke(ps, pp, x, y); 2298 if (!hit && (kind&NVGPickKind.Fill) && (pp.flags&NVGPathFlags.Fill)) hit = nvg__pickPath(ps, pp, x, y); 2299 if (hit) { 2300 static if (IsGoodHitTestDG!DG) { 2301 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) { 2302 if (dg(pp.id, cast(int)pp.order)) return pp.id; 2303 } else { 2304 dg(pp.id, cast(int)pp.order); 2305 } 2306 } else { 2307 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) { 2308 if (dg(pp)) return pp.id; 2309 } else { 2310 dg(pp); 2311 } 2312 } 2313 } 2314 } 2315 pp = pp.next; 2316 } 2317 cellx >>= 1; 2318 celly >>= 1; 2319 levelwidth >>= 1; 2320 } 2321 2322 return -1; 2323 } 2324 2325 // Fills ids with a list of the top most hit ids under the specified position. 2326 // Returns the slice of [ids]. 2327 int[] hitTestAll (in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc { 2328 if (pickscene is null || ids.length == 0) return ids[0..0]; 2329 2330 int npicked = 0; 2331 NVGpickScene* ps = pickscene; 2332 2333 hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 2334 if (npicked == ps.cpicked) { 2335 int cpicked = ps.cpicked+ps.cpicked; 2336 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked); 2337 if (picked is null) return true; // abort 2338 ps.cpicked = cpicked; 2339 ps.picked = picked; 2340 } 2341 ps.picked[npicked] = pp; 2342 ++npicked; 2343 return false; // go on 2344 }); 2345 2346 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths); 2347 2348 assert(npicked >= 0); 2349 if (npicked > ids.length) npicked = cast(int)ids.length; 2350 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id; 2351 2352 return ids[0..npicked]; 2353 } 2354 2355 // Returns the id of the pickable shape containing x,y or -1 if no shape was found. 2356 int hitTest (in float x, in float y, NVGPickKind kind) nothrow @trusted @nogc { 2357 if (pickscene is null) return -1; 2358 2359 int bestOrder = -1; 2360 int bestID = -1; 2361 2362 hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 2363 if (pp.order > bestOrder) { 2364 bestOrder = pp.order; 2365 bestID = pp.id; 2366 } 2367 }); 2368 2369 return bestID; 2370 } 2371 } 2372 2373 // Append current path to existing path set. Is is safe to call this with `null` [svp]. 2374 void appendCurrentPathToCache (NVGContext ctx, NVGPathSet svp, in ref NVGPaint paint) nothrow @trusted @nogc { 2375 if (ctx is null || svp is null) return; 2376 if (ctx !is svp.svctx) assert(0, "NanoVega: cannot save paths from different contexts"); 2377 if (ctx.ncommands == 0) { 2378 assert(ctx.cache.npaths == 0); 2379 return; 2380 } 2381 if (!ctx.cache.fillReady && !ctx.cache.strokeReady) return; 2382 2383 // tesselate current path 2384 //if (!ctx.cache.fillReady) nvg__prepareFill(ctx); 2385 //if (!ctx.cache.strokeReady) nvg__prepareStroke(ctx); 2386 2387 auto node = svp.allocPathNode(); 2388 NVGpathCache* cc = node.path; 2389 cc.copyFrom(ctx.cache); 2390 node.paint = paint; 2391 // copy path commands (we may need 'em for picking) 2392 version(all) { 2393 cc.ncommands = ctx.ncommands; 2394 if (cc.ncommands) { 2395 import core.stdc.stdlib : malloc; 2396 import core.stdc.string : memcpy; 2397 cc.commands = cast(float*)malloc(ctx.ncommands*float.sizeof); 2398 if (cc.commands is null) assert(0, "NanoVega: out of memory"); 2399 memcpy(cc.commands, ctx.commands, ctx.ncommands*float.sizeof); 2400 } else { 2401 cc.commands = null; 2402 } 2403 } 2404 } 2405 2406 // Create new empty path set. 2407 // Group: path_recording 2408 public NVGPathSet newPathSet (NVGContext ctx) nothrow @trusted @nogc { 2409 import core.stdc.stdlib : malloc; 2410 import core.stdc.string : memset; 2411 if (ctx is null) return null; 2412 NVGPathSet res = cast(NVGPathSet)malloc(NVGPathSetS.sizeof); 2413 if (res is null) assert(0, "NanoVega: out of memory"); 2414 memset(res, 0, NVGPathSetS.sizeof); 2415 res.svctx = ctx; 2416 return res; 2417 } 2418 2419 // Is the given path set empty? Empty path set can be `null`. 2420 // Group: path_recording 2421 public bool empty (NVGPathSet svp) pure nothrow @safe @nogc { pragma(inline, true); return (svp is null || svp.nnodes == 0); } 2422 2423 // Clear path set contents. Will release $(B some) allocated memory (this function is meant to clear something that will be reused). 2424 // Group: path_recording 2425 public void clear (NVGPathSet svp) nothrow @trusted @nogc { 2426 if (svp !is null) { 2427 import core.stdc.stdlib : free; 2428 foreach (immutable idx; 0.. svp.nnodes) svp.clearNode(idx); 2429 svp.nnodes = 0; 2430 } 2431 } 2432 2433 // Destroy path set (frees all allocated memory). 2434 // Group: path_recording 2435 public void kill (ref NVGPathSet svp) nothrow @trusted @nogc { 2436 if (svp !is null) { 2437 import core.stdc.stdlib : free; 2438 svp.clear(); 2439 if (svp.nodes !is null) free(svp.nodes); 2440 free(svp); 2441 if (svp.pickscene !is null) nvg__deletePickScene(svp.pickscene); 2442 svp = null; 2443 } 2444 } 2445 2446 // Start path recording. [svp] should be alive until recording is cancelled or stopped. 2447 // Group: path_recording 2448 public void startRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { 2449 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2450 ctx.stopRecording(); 2451 ctx.recset = svp; 2452 ctx.recstart = (svp !is null ? svp.nnodes : -1); 2453 ctx.recblockdraw = false; 2454 } 2455 2456 /* Start path recording. [svp] should be alive until recording is cancelled or stopped. 2457 * 2458 * This will block all rendering, so you can call your rendering functions to record paths without actual drawing. 2459 * Commiting or cancelling will re-enable rendering. 2460 * You can call this with `null` svp to block rendering without recording any paths. 2461 * 2462 * Group: path_recording 2463 */ 2464 public void startBlockingRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { 2465 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2466 ctx.stopRecording(); 2467 ctx.recset = svp; 2468 ctx.recstart = (svp !is null ? svp.nnodes : -1); 2469 ctx.recblockdraw = true; 2470 } 2471 2472 // Commit recorded paths. It is safe to call this when recording is not started. 2473 // Group: path_recording 2474 public void stopRecording (NVGContext ctx) nothrow @trusted @nogc { 2475 if (ctx.recset !is null && ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2476 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx); 2477 ctx.recset = null; 2478 ctx.recstart = -1; 2479 ctx.recblockdraw = false; 2480 } 2481 2482 // Cancel path recording. 2483 // Group: path_recording 2484 public void cancelRecording (NVGContext ctx) nothrow @trusted @nogc { 2485 if (ctx.recset !is null) { 2486 if (ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2487 assert(ctx.recstart >= 0 && ctx.recstart <= ctx.recset.nnodes); 2488 foreach (immutable idx; ctx.recstart..ctx.recset.nnodes) ctx.recset.clearNode(idx); 2489 ctx.recset.nnodes = ctx.recstart; 2490 ctx.recset = null; 2491 ctx.recstart = -1; 2492 } 2493 ctx.recblockdraw = false; 2494 } 2495 2496 /* Replay saved path set. 2497 * 2498 * Replaying record while you're recording another one is undefined behavior. 2499 * 2500 * Group: path_recording 2501 */ 2502 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint, in auto ref NVGColor strokeTint) nothrow @trusted @nogc { 2503 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2504 svp.replay(ctx, fillTint, strokeTint); 2505 } 2506 2507 /// Ditto. 2508 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint) nothrow @trusted @nogc { ctx.replayRecording(svp, fillTint, NVGColor.transparent); } 2509 2510 /// Ditto. 2511 public void replayRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { ctx.replayRecording(svp, NVGColor.transparent, NVGColor.transparent); } 2512 2513 2514 // ////////////////////////////////////////////////////////////////////////// // 2515 // Composite operation 2516 2517 /// Sets the composite operation. 2518 /// Group: composite_operation 2519 public void globalCompositeOperation (NVGContext ctx, NVGCompositeOperation op) nothrow @trusted @nogc { 2520 NVGstate* state = nvg__getState(ctx); 2521 state.compositeOperation = nvg__compositeOperationState(op); 2522 } 2523 2524 /// Sets the composite operation with custom pixel arithmetic. 2525 /// Group: composite_operation 2526 public void globalCompositeBlendFunc (NVGContext ctx, NVGBlendFactor sfactor, NVGBlendFactor dfactor) nothrow @trusted @nogc { 2527 ctx.globalCompositeBlendFuncSeparate(sfactor, dfactor, sfactor, dfactor); 2528 } 2529 2530 /// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. 2531 /// Group: composite_operation 2532 public void globalCompositeBlendFuncSeparate (NVGContext ctx, NVGBlendFactor srcRGB, NVGBlendFactor dstRGB, NVGBlendFactor srcAlpha, NVGBlendFactor dstAlpha) nothrow @trusted @nogc { 2533 NVGCompositeOperationState op; 2534 op.simple = false; 2535 op.srcRGB = srcRGB; 2536 op.dstRGB = dstRGB; 2537 op.srcAlpha = srcAlpha; 2538 op.dstAlpha = dstAlpha; 2539 NVGstate* state = nvg__getState(ctx); 2540 state.compositeOperation = op; 2541 } 2542 2543 2544 // ////////////////////////////////////////////////////////////////////////// // 2545 // Color utils 2546 2547 /// Returns a color value from string form. 2548 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb" 2549 /// Group: color_utils 2550 public NVGColor nvgRGB (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); } 2551 2552 /// Ditto. 2553 public NVGColor nvgRGBA (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); } 2554 2555 /// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f). 2556 /// Group: color_utils 2557 public NVGColor nvgRGB (int r, int g, int b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(nvgClampToByte(r), nvgClampToByte(g), nvgClampToByte(b), 255); } 2558 2559 /// Returns a color value from red, green, blue values. Alpha will be set to 1.0f. 2560 /// Group: color_utils 2561 public NVGColor nvgRGBf (float r, float g, float b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(r, g, b, 1.0f); } 2562 2563 /// Returns a color value from red, green, blue and alpha values. 2564 /// Group: color_utils 2565 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)); } 2566 2567 /// Returns a color value from red, green, blue and alpha values. 2568 /// Group: color_utils 2569 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); } 2570 2571 /// Returns new color with transparency (alpha) set to [a]. 2572 /// Group: color_utils 2573 public NVGColor nvgTransRGBA (NVGColor c, ubyte a) nothrow @trusted @nogc { 2574 pragma(inline, true); 2575 c.a = a/255.0f; 2576 return c; 2577 } 2578 2579 /// Ditto. 2580 public NVGColor nvgTransRGBAf (NVGColor c, float a) nothrow @trusted @nogc { 2581 pragma(inline, true); 2582 c.a = a; 2583 return c; 2584 } 2585 2586 /// Linearly interpolates from color c0 to c1, and returns resulting color value. 2587 /// Group: color_utils 2588 public NVGColor nvgLerpRGBA() (in auto ref NVGColor c0, in auto ref NVGColor c1, float u) nothrow @trusted @nogc { 2589 NVGColor cint = void; 2590 u = nvg__clamp(u, 0.0f, 1.0f); 2591 float oneminu = 1.0f-u; 2592 foreach (uint i; 0..4) cint.rgba.ptr[i] = c0.rgba.ptr[i]*oneminu+c1.rgba.ptr[i]*u; 2593 return cint; 2594 } 2595 2596 /* see below 2597 public NVGColor nvgHSL() (float h, float s, float l) { 2598 //pragma(inline, true); // alas 2599 return nvgHSLA(h, s, l, 255); 2600 } 2601 */ 2602 2603 float nvg__hue (float h, float m1, float m2) pure nothrow @safe @nogc { 2604 if (h < 0) h += 1; 2605 if (h > 1) h -= 1; 2606 if (h < 1.0f/6.0f) return m1+(m2-m1)*h*6.0f; 2607 if (h < 3.0f/6.0f) return m2; 2608 if (h < 4.0f/6.0f) return m1+(m2-m1)*(2.0f/3.0f-h)*6.0f; 2609 return m1; 2610 } 2611 2612 /// Returns color value specified by hue, saturation and lightness. 2613 /// HSL values are all in range [0..1], alpha will be set to 255. 2614 /// Group: color_utils 2615 public alias nvgHSL = nvgHSLA; // trick to allow inlining 2616 2617 /// Returns color value specified by hue, saturation and lightness and alpha. 2618 /// HSL values are all in range [0..1], alpha in range [0..255]. 2619 /// Group: color_utils 2620 public NVGColor nvgHSLA (float h, float s, float l, ubyte a=255) nothrow @trusted @nogc { 2621 pragma(inline, true); 2622 NVGColor col = void; 2623 h = nvg__modf(h, 1.0f); 2624 if (h < 0.0f) h += 1.0f; 2625 s = nvg__clamp(s, 0.0f, 1.0f); 2626 l = nvg__clamp(l, 0.0f, 1.0f); 2627 immutable float m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s); 2628 immutable float m1 = 2*l-m2; 2629 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2630 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f); 2631 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2632 col.a = a/255.0f; 2633 return col; 2634 } 2635 2636 /// Returns color value specified by hue, saturation and lightness and alpha. 2637 /// HSL values and alpha are all in range [0..1]. 2638 /// Group: color_utils 2639 public NVGColor nvgHSLA (float h, float s, float l, float a) nothrow @trusted @nogc { 2640 // sorry for copypasta, it is for inliner 2641 static if (__VERSION__ >= 2072) pragma(inline, true); 2642 NVGColor col = void; 2643 h = nvg__modf(h, 1.0f); 2644 if (h < 0.0f) h += 1.0f; 2645 s = nvg__clamp(s, 0.0f, 1.0f); 2646 l = nvg__clamp(l, 0.0f, 1.0f); 2647 immutable m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s); 2648 immutable m1 = 2*l-m2; 2649 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2650 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f); 2651 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2652 col.a = a; 2653 return col; 2654 } 2655 2656 2657 // ////////////////////////////////////////////////////////////////////////// // 2658 // Matrices and Transformations 2659 2660 /** Matrix class. 2661 * 2662 * Group: matrices 2663 */ 2664 public align(1) struct NVGMatrix { 2665 align(1): 2666 private: 2667 static immutable float[6] IdentityMat = [ 2668 1.0f, 0.0f, 2669 0.0f, 1.0f, 2670 0.0f, 0.0f, 2671 ]; 2672 2673 public: 2674 /// Matrix values. Initial value is identity matrix. 2675 float[6] mat = [ 2676 1.0f, 0.0f, 2677 0.0f, 1.0f, 2678 0.0f, 0.0f, 2679 ]; 2680 2681 public nothrow @trusted @nogc: 2682 /// Create Matrix with the given values. 2683 this (const(float)[] amat...) { 2684 pragma(inline, true); 2685 if (amat.length >= 6) { 2686 mat.ptr[0..6] = amat.ptr[0..6]; 2687 } else { 2688 mat.ptr[0..6] = 0; 2689 mat.ptr[0..amat.length] = amat[]; 2690 } 2691 } 2692 2693 /// Can be used to check validity of [inverted] result 2694 @property bool valid () const { import core.stdc.math : isfinite; return (isfinite(mat.ptr[0]) != 0); } 2695 2696 /// Returns `true` if this matrix is identity matrix. 2697 @property bool isIdentity () const { version(aliced) pragma(inline, true); return (mat[] == IdentityMat[]); } 2698 2699 /// Returns new inverse matrix. 2700 /// If inverted matrix cannot be calculated, `res.valid` fill be `false`. 2701 NVGMatrix inverted () const { 2702 NVGMatrix res = this; 2703 res.invert; 2704 return res; 2705 } 2706 2707 /// Inverts this matrix. 2708 /// If inverted matrix cannot be calculated, `this.valid` fill be `false`. 2709 ref NVGMatrix invert () { 2710 float[6] inv = void; 2711 immutable double det = cast(double)mat.ptr[0]*mat.ptr[3]-cast(double)mat.ptr[2]*mat.ptr[1]; 2712 if (det > -1e-6 && det < 1e-6) { 2713 inv[] = float.nan; 2714 } else { 2715 immutable double invdet = 1.0/det; 2716 inv.ptr[0] = cast(float)(mat.ptr[3]*invdet); 2717 inv.ptr[2] = cast(float)(-mat.ptr[2]*invdet); 2718 inv.ptr[4] = cast(float)((cast(double)mat.ptr[2]*mat.ptr[5]-cast(double)mat.ptr[3]*mat.ptr[4])*invdet); 2719 inv.ptr[1] = cast(float)(-mat.ptr[1]*invdet); 2720 inv.ptr[3] = cast(float)(mat.ptr[0]*invdet); 2721 inv.ptr[5] = cast(float)((cast(double)mat.ptr[1]*mat.ptr[4]-cast(double)mat.ptr[0]*mat.ptr[5])*invdet); 2722 } 2723 mat.ptr[0..6] = inv.ptr[0..6]; 2724 return this; 2725 } 2726 2727 /// Sets this matrix to identity matrix. 2728 ref NVGMatrix identity () { version(aliced) pragma(inline, true); mat[] = IdentityMat[]; return this; } 2729 2730 /// Translate this matrix. 2731 ref NVGMatrix translate (in float tx, in float ty) { 2732 version(aliced) pragma(inline, true); 2733 return this.mul(Translated(tx, ty)); 2734 } 2735 2736 /// Scale this matrix. 2737 ref NVGMatrix scale (in float sx, in float sy) { 2738 version(aliced) pragma(inline, true); 2739 return this.mul(Scaled(sx, sy)); 2740 } 2741 2742 /// Rotate this matrix. 2743 ref NVGMatrix rotate (in float a) { 2744 version(aliced) pragma(inline, true); 2745 return this.mul(Rotated(a)); 2746 } 2747 2748 /// Skew this matrix by X axis. 2749 ref NVGMatrix skewX (in float a) { 2750 version(aliced) pragma(inline, true); 2751 return this.mul(SkewedX(a)); 2752 } 2753 2754 /// Skew this matrix by Y axis. 2755 ref NVGMatrix skewY (in float a) { 2756 version(aliced) pragma(inline, true); 2757 return this.mul(SkewedY(a)); 2758 } 2759 2760 /// Skew this matrix by both axes. 2761 ref NVGMatrix skewY (in float ax, in float ay) { 2762 version(aliced) pragma(inline, true); 2763 return this.mul(SkewedXY(ax, ay)); 2764 } 2765 2766 /// Transform point with this matrix. `null` destinations are allowed. 2767 /// [sx] and [sy] is the source point. [dx] and [dy] may point to the same variables. 2768 void point (float* dx, float* dy, float sx, float sy) nothrow @trusted @nogc { 2769 version(aliced) pragma(inline, true); 2770 if (dx !is null) *dx = sx*mat.ptr[0]+sy*mat.ptr[2]+mat.ptr[4]; 2771 if (dy !is null) *dy = sx*mat.ptr[1]+sy*mat.ptr[3]+mat.ptr[5]; 2772 } 2773 2774 /// Transform point with this matrix. 2775 void point (ref float x, ref float y) nothrow @trusted @nogc { 2776 version(aliced) pragma(inline, true); 2777 immutable float nx = x*mat.ptr[0]+y*mat.ptr[2]+mat.ptr[4]; 2778 immutable float ny = x*mat.ptr[1]+y*mat.ptr[3]+mat.ptr[5]; 2779 x = nx; 2780 y = ny; 2781 } 2782 2783 /// Sets this matrix to the result of multiplication of `this` and [s] (this * S). 2784 ref NVGMatrix mul() (in auto ref NVGMatrix s) { 2785 immutable float t0 = mat.ptr[0]*s.mat.ptr[0]+mat.ptr[1]*s.mat.ptr[2]; 2786 immutable float t2 = mat.ptr[2]*s.mat.ptr[0]+mat.ptr[3]*s.mat.ptr[2]; 2787 immutable float t4 = mat.ptr[4]*s.mat.ptr[0]+mat.ptr[5]*s.mat.ptr[2]+s.mat.ptr[4]; 2788 mat.ptr[1] = mat.ptr[0]*s.mat.ptr[1]+mat.ptr[1]*s.mat.ptr[3]; 2789 mat.ptr[3] = mat.ptr[2]*s.mat.ptr[1]+mat.ptr[3]*s.mat.ptr[3]; 2790 mat.ptr[5] = mat.ptr[4]*s.mat.ptr[1]+mat.ptr[5]*s.mat.ptr[3]+s.mat.ptr[5]; 2791 mat.ptr[0] = t0; 2792 mat.ptr[2] = t2; 2793 mat.ptr[4] = t4; 2794 return this; 2795 } 2796 2797 /// Sets this matrix to the result of multiplication of [s] and `this` (S * this). 2798 /// Sets the transform to the result of multiplication of two transforms, of A = B*A. 2799 /// Group: matrices 2800 ref NVGMatrix premul() (in auto ref NVGMatrix s) { 2801 NVGMatrix s2 = s; 2802 s2.mul(this); 2803 mat[] = s2.mat[]; 2804 return this; 2805 } 2806 2807 /// Multiply this matrix by [s], return result as new matrix. 2808 /// Performs operations in this left-to-right order. 2809 NVGMatrix opBinary(string op="*") (in auto ref NVGMatrix s) const { 2810 version(aliced) pragma(inline, true); 2811 NVGMatrix res = this; 2812 res.mul(s); 2813 return res; 2814 } 2815 2816 /// Multiply this matrix by [s]. 2817 /// Performs operations in this left-to-right order. 2818 ref NVGMatrix opOpAssign(string op="*") (in auto ref NVGMatrix s) { 2819 version(aliced) pragma(inline, true); 2820 return this.mul(s); 2821 } 2822 2823 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. 2824 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. 2825 float rotation () const { pragma(inline, true); return nvg__atan2f(mat.ptr[1], mat.ptr[0]); } /// Returns rotation of this matrix. 2826 float tx () const { pragma(inline, true); return mat.ptr[4]; } /// Returns x translation of this matrix. 2827 float ty () const { pragma(inline, true); return mat.ptr[5]; } /// Returns y translation of this matrix. 2828 2829 ref NVGMatrix scaleX (in float v) { pragma(inline, true); return scaleRotateTransform(v, scaleY, rotation, tx, ty); } /// Sets x scaling of this matrix. 2830 ref NVGMatrix scaleY (in float v) { pragma(inline, true); return scaleRotateTransform(scaleX, v, rotation, tx, ty); } /// Sets y scaling of this matrix. 2831 ref NVGMatrix rotation (in float v) { pragma(inline, true); return scaleRotateTransform(scaleX, scaleY, v, tx, ty); } /// Sets rotation of this matrix. 2832 ref NVGMatrix tx (in float v) { pragma(inline, true); mat.ptr[4] = v; return this; } /// Sets x translation of this matrix. 2833 ref NVGMatrix ty (in float v) { pragma(inline, true); mat.ptr[5] = v; return this; } /// Sets y translation of this matrix. 2834 2835 /// Utility function to be used in `setXXX()`. 2836 /// This is the same as doing: `mat.identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster 2837 ref NVGMatrix scaleRotateTransform (in float xscale, in float yscale, in float a, in float tx, in float ty) { 2838 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2839 mat.ptr[0] = xscale*cs; mat.ptr[1] = yscale*sn; 2840 mat.ptr[2] = xscale*-sn; mat.ptr[3] = yscale*cs; 2841 mat.ptr[4] = tx; mat.ptr[5] = ty; 2842 return this; 2843 } 2844 2845 /// This is the same as doing: `mat.identity.rotate(a).translate(tx, ty)`, only faster 2846 ref NVGMatrix rotateTransform (in float a, in float tx, in float ty) { 2847 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2848 mat.ptr[0] = cs; mat.ptr[1] = sn; 2849 mat.ptr[2] = -sn; mat.ptr[3] = cs; 2850 mat.ptr[4] = tx; mat.ptr[5] = ty; 2851 return this; 2852 } 2853 2854 /// Returns new identity matrix. 2855 static NVGMatrix Identity () { pragma(inline, true); return NVGMatrix.init; } 2856 2857 /// Returns new translation matrix. 2858 static NVGMatrix Translated (in float tx, in float ty) { 2859 version(aliced) pragma(inline, true); 2860 NVGMatrix res = void; 2861 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f; 2862 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f; 2863 res.mat.ptr[4] = tx; res.mat.ptr[5] = ty; 2864 return res; 2865 } 2866 2867 /// Returns new scaling matrix. 2868 static NVGMatrix Scaled (in float sx, in float sy) { 2869 version(aliced) pragma(inline, true); 2870 NVGMatrix res = void; 2871 res.mat.ptr[0] = sx; res.mat.ptr[1] = 0.0f; 2872 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = sy; 2873 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2874 return res; 2875 } 2876 2877 /// Returns new rotation matrix. Angle is specified in radians. 2878 static NVGMatrix Rotated (in float a) { 2879 version(aliced) pragma(inline, true); 2880 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2881 NVGMatrix res = void; 2882 res.mat.ptr[0] = cs; res.mat.ptr[1] = sn; 2883 res.mat.ptr[2] = -sn; res.mat.ptr[3] = cs; 2884 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2885 return res; 2886 } 2887 2888 /// Returns new x-skewing matrix. Angle is specified in radians. 2889 static NVGMatrix SkewedX (in float a) { 2890 version(aliced) pragma(inline, true); 2891 NVGMatrix res = void; 2892 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f; 2893 res.mat.ptr[2] = nvg__tanf(a); res.mat.ptr[3] = 1.0f; 2894 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2895 return res; 2896 } 2897 2898 /// Returns new y-skewing matrix. Angle is specified in radians. 2899 static NVGMatrix SkewedY (in float a) { 2900 version(aliced) pragma(inline, true); 2901 NVGMatrix res = void; 2902 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(a); 2903 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f; 2904 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2905 return res; 2906 } 2907 2908 /// Returns new xy-skewing matrix. Angles are specified in radians. 2909 static NVGMatrix SkewedXY (in float ax, in float ay) { 2910 version(aliced) pragma(inline, true); 2911 NVGMatrix res = void; 2912 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(ay); 2913 res.mat.ptr[2] = nvg__tanf(ax); res.mat.ptr[3] = 1.0f; 2914 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2915 return res; 2916 } 2917 2918 /// Utility function to be used in `setXXX()`. 2919 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster 2920 static NVGMatrix ScaledRotatedTransformed (in float xscale, in float yscale, in float a, in float tx, in float ty) { 2921 NVGMatrix res = void; 2922 res.scaleRotateTransform(xscale, yscale, a, tx, ty); 2923 return res; 2924 } 2925 2926 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).translate(tx, ty)`, only faster 2927 static NVGMatrix RotatedTransformed (in float a, in float tx, in float ty) { 2928 NVGMatrix res = void; 2929 res.rotateTransform(a, tx, ty); 2930 return res; 2931 } 2932 } 2933 2934 2935 /// Converts degrees to radians. 2936 /// Group: matrices 2937 public float nvgDegToRad() (in float deg) pure nothrow @safe @nogc { pragma(inline, true); return deg/180.0f*NVG_PI; } 2938 2939 /// Converts radians to degrees. 2940 /// Group: matrices 2941 public float nvgRadToDeg() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad/NVG_PI*180.0f; } 2942 2943 public alias nvgDegrees = nvgDegToRad; /// Use this like `42.nvgDegrees` 2944 public float nvgRadians() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad; } /// Use this like `0.1.nvgRadians` 2945 2946 2947 // ////////////////////////////////////////////////////////////////////////// // 2948 void nvg__setPaintColor() (ref NVGPaint p, in auto ref NVGColor color) nothrow @trusted @nogc { 2949 p.clear(); 2950 p.xform.identity; 2951 p.radius = 0.0f; 2952 p.feather = 1.0f; 2953 p.innerColor = p.middleColor = p.outerColor = color; 2954 p.midp = -1; 2955 p.simpleColor = true; 2956 } 2957 2958 2959 // ////////////////////////////////////////////////////////////////////////// // 2960 // State handling 2961 2962 version(nanovega_debug_clipping) { 2963 public void nvgClipDumpOn (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, true); } 2964 public void nvgClipDumpOff (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, false); } 2965 } 2966 2967 /** Pushes and saves the current render state into a state stack. 2968 * A matching [restore] must be used to restore the state. 2969 * Returns `false` if state stack overflowed. 2970 * 2971 * Group: state_handling 2972 */ 2973 public bool save (NVGContext ctx) nothrow @trusted @nogc { 2974 if (ctx.nstates >= NVG_MAX_STATES) return false; 2975 if (ctx.nstates > 0) { 2976 //memcpy(&ctx.states[ctx.nstates], &ctx.states[ctx.nstates-1], NVGstate.sizeof); 2977 ctx.states[ctx.nstates] = ctx.states[ctx.nstates-1]; 2978 ctx.params.renderPushClip(ctx.params.userPtr); 2979 } 2980 ++ctx.nstates; 2981 return true; 2982 } 2983 2984 /// Pops and restores current render state. 2985 /// Group: state_handling 2986 public bool restore (NVGContext ctx) nothrow @trusted @nogc { 2987 if (ctx.nstates <= 1) return false; 2988 ctx.states[ctx.nstates-1].clearPaint(); 2989 ctx.params.renderPopClip(ctx.params.userPtr); 2990 --ctx.nstates; 2991 return true; 2992 } 2993 2994 /// Resets current render state to default values. Does not affect the render state stack. 2995 /// Group: state_handling 2996 public void reset (NVGContext ctx) nothrow @trusted @nogc { 2997 NVGstate* state = nvg__getState(ctx); 2998 state.clearPaint(); 2999 3000 nvg__setPaintColor(state.fill, nvgRGBA(255, 255, 255, 255)); 3001 nvg__setPaintColor(state.stroke, nvgRGBA(0, 0, 0, 255)); 3002 state.compositeOperation = nvg__compositeOperationState(NVGCompositeOperation.SourceOver); 3003 state.shapeAntiAlias = true; 3004 state.strokeWidth = 1.0f; 3005 state.miterLimit = 10.0f; 3006 state.lineCap = NVGLineCap.Butt; 3007 state.lineJoin = NVGLineCap.Miter; 3008 state.alpha = 1.0f; 3009 state.xform.identity; 3010 3011 state.scissor.extent[] = -1.0f; 3012 3013 state.fontSize = 16.0f; 3014 state.letterSpacing = 0.0f; 3015 state.lineHeight = 1.0f; 3016 state.fontBlur = 0.0f; 3017 state.textAlign.reset; 3018 state.fontId = 0; 3019 state.evenOddMode = false; 3020 state.dashCount = 0; 3021 state.lastFlattenDashCount = 0; 3022 state.dashStart = 0; 3023 state.firstDashIsGap = false; 3024 state.dasherActive = false; 3025 3026 ctx.params.renderResetClip(ctx.params.userPtr); 3027 } 3028 3029 /** Returns `true` if we have any room in state stack. 3030 * It is guaranteed to have at least 32 stack slots. 3031 * 3032 * Group: state_handling 3033 */ 3034 public bool canSave (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates < NVG_MAX_STATES); } 3035 3036 /** Returns `true` if we have any saved state. 3037 * 3038 * Group: state_handling 3039 */ 3040 public bool canRestore (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates > 1); } 3041 3042 /// Returns `true` if rendering is currently blocked. 3043 /// Group: state_handling 3044 public bool renderBlocked (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.recblockdraw : false); } 3045 3046 /// Blocks/unblocks rendering 3047 /// Group: state_handling 3048 public void renderBlocked (NVGContext ctx, bool v) pure nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null && ctx.contextAlive) ctx.recblockdraw = v; } 3049 3050 /// Blocks/unblocks rendering; returns previous state. 3051 /// Group: state_handling 3052 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; } 3053 3054 3055 // ////////////////////////////////////////////////////////////////////////// // 3056 // Render styles 3057 3058 /// Sets filling mode to "even-odd". 3059 /// Group: render_styles 3060 public void evenOddFill (NVGContext ctx) nothrow @trusted @nogc { 3061 NVGstate* state = nvg__getState(ctx); 3062 state.evenOddMode = true; 3063 } 3064 3065 /// Sets filling mode to "non-zero" (this is default mode). 3066 /// Group: render_styles 3067 public void nonZeroFill (NVGContext ctx) nothrow @trusted @nogc { 3068 NVGstate* state = nvg__getState(ctx); 3069 state.evenOddMode = false; 3070 } 3071 3072 /// Sets whether to draw antialias for [stroke] and [fill]. It's enabled by default. 3073 /// Group: render_styles 3074 public void shapeAntiAlias (NVGContext ctx, bool enabled) { 3075 NVGstate* state = nvg__getState(ctx); 3076 state.shapeAntiAlias = enabled; 3077 } 3078 3079 /// Sets the stroke width of the stroke style. 3080 /// Group: render_styles 3081 @scriptable 3082 public void strokeWidth (NVGContext ctx, float width) nothrow @trusted @nogc { 3083 NVGstate* state = nvg__getState(ctx); 3084 state.strokeWidth = width; 3085 } 3086 3087 /// Sets the miter limit of the stroke style. Miter limit controls when a sharp corner is beveled. 3088 /// Group: render_styles 3089 public void miterLimit (NVGContext ctx, float limit) nothrow @trusted @nogc { 3090 NVGstate* state = nvg__getState(ctx); 3091 state.miterLimit = limit; 3092 } 3093 3094 /// Sets how the end of the line (cap) is drawn, 3095 /// Can be one of: NVGLineCap.Butt (default), NVGLineCap.Round, NVGLineCap.Square. 3096 /// Group: render_styles 3097 public void lineCap (NVGContext ctx, NVGLineCap cap) nothrow @trusted @nogc { 3098 NVGstate* state = nvg__getState(ctx); 3099 state.lineCap = cap; 3100 } 3101 3102 /// Sets how sharp path corners are drawn. 3103 /// Can be one of NVGLineCap.Miter (default), NVGLineCap.Round, NVGLineCap.Bevel. 3104 /// Group: render_styles 3105 public void lineJoin (NVGContext ctx, NVGLineCap join) nothrow @trusted @nogc { 3106 NVGstate* state = nvg__getState(ctx); 3107 state.lineJoin = join; 3108 } 3109 3110 /// Sets stroke dashing, using (dash_length, gap_length) pairs. 3111 /// Current limit is 16 pairs. 3112 /// Resets dash start to zero. 3113 /// Group: render_styles 3114 public void setLineDash (NVGContext ctx, const(float)[] dashdata) nothrow @trusted @nogc { 3115 NVGstate* state = nvg__getState(ctx); 3116 state.dashCount = 0; 3117 state.dashStart = 0; 3118 state.firstDashIsGap = false; 3119 if (dashdata.length >= 2) { 3120 bool curFIsGap = true; // trick 3121 foreach (immutable idx, float f; dashdata) { 3122 curFIsGap = !curFIsGap; 3123 if (f < 0.01f) continue; // skip it 3124 if (idx == 0) { 3125 // register first dash 3126 state.firstDashIsGap = curFIsGap; 3127 state.dashes.ptr[state.dashCount++] = f; 3128 } else { 3129 if ((idx&1) != ((state.dashCount&1)^cast(uint)state.firstDashIsGap)) { 3130 // oops, continuation 3131 state.dashes[state.dashCount-1] += f; 3132 } else { 3133 if (state.dashCount == state.dashes.length) break; 3134 state.dashes[state.dashCount++] = f; 3135 } 3136 } 3137 } 3138 if (state.dashCount&1) { 3139 if (state.dashCount == 1) { 3140 state.dashCount = 0; 3141 } else { 3142 assert(state.dashCount < state.dashes.length); 3143 state.dashes[state.dashCount++] = 0; 3144 } 3145 } 3146 // calculate total dash path length 3147 state.totalDashLen = 0; 3148 foreach (float f; state.dashes.ptr[0..state.dashCount]) state.totalDashLen += f; 3149 if (state.totalDashLen < 0.01f) { 3150 state.dashCount = 0; // nothing to do 3151 } else { 3152 if (state.lastFlattenDashCount != 0) state.lastFlattenDashCount = uint.max; // force re-flattening 3153 } 3154 } 3155 } 3156 3157 public alias lineDash = setLineDash; /// Ditto. 3158 3159 /// Sets stroke dashing, using (dash_length, gap_length) pairs. 3160 /// Current limit is 16 pairs. 3161 /// Group: render_styles 3162 public void setLineDashStart (NVGContext ctx, in float dashStart) nothrow @trusted @nogc { 3163 NVGstate* state = nvg__getState(ctx); 3164 if (state.lastFlattenDashCount != 0 && state.dashStart != dashStart) { 3165 state.lastFlattenDashCount = uint.max; // force re-flattening 3166 } 3167 state.dashStart = dashStart; 3168 } 3169 3170 public alias lineDashStart = setLineDashStart; /// Ditto. 3171 3172 /// Sets the transparency applied to all rendered shapes. 3173 /// Already transparent paths will get proportionally more transparent as well. 3174 /// Group: render_styles 3175 public void globalAlpha (NVGContext ctx, float alpha) nothrow @trusted @nogc { 3176 NVGstate* state = nvg__getState(ctx); 3177 state.alpha = alpha; 3178 } 3179 3180 private void strokeColor() {} 3181 3182 static if (NanoVegaHasArsdColor) { 3183 /// Sets current stroke style to a solid color. 3184 /// Group: render_styles 3185 @scriptable 3186 public void strokeColor (NVGContext ctx, Color color) nothrow @trusted @nogc { 3187 NVGstate* state = nvg__getState(ctx); 3188 nvg__setPaintColor(state.stroke, NVGColor(color)); 3189 } 3190 } 3191 3192 /// Sets current stroke style to a solid color. 3193 /// Group: render_styles 3194 public void strokeColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc { 3195 NVGstate* state = nvg__getState(ctx); 3196 nvg__setPaintColor(state.stroke, color); 3197 } 3198 3199 @scriptable 3200 public void strokePaint(NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc { 3201 strokePaint(ctx, *paint); 3202 } 3203 3204 /// Sets current stroke style to a paint, which can be a one of the gradients or a pattern. 3205 /// Group: render_styles 3206 @scriptable 3207 public void strokePaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc { 3208 NVGstate* state = nvg__getState(ctx); 3209 state.stroke = paint; 3210 //nvgTransformMultiply(state.stroke.xform[], state.xform[]); 3211 state.stroke.xform.mul(state.xform); 3212 } 3213 3214 // this is a hack to work around https://issues.dlang.org/show_bug.cgi?id=16206 3215 // for scriptable reflection. it just needs to be declared first among the overloads 3216 private void fillColor (NVGContext ctx) nothrow @trusted @nogc { } 3217 3218 static if (NanoVegaHasArsdColor) { 3219 /// Sets current fill style to a solid color. 3220 /// Group: render_styles 3221 @scriptable 3222 public void fillColor (NVGContext ctx, Color color) nothrow @trusted @nogc { 3223 NVGstate* state = nvg__getState(ctx); 3224 nvg__setPaintColor(state.fill, NVGColor(color)); 3225 } 3226 } 3227 3228 /// Sets current fill style to a solid color. 3229 /// Group: render_styles 3230 public void fillColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc { 3231 NVGstate* state = nvg__getState(ctx); 3232 nvg__setPaintColor(state.fill, color); 3233 3234 } 3235 3236 @scriptable // kinda a hack for bug 16206 but also because jsvar deals in opaque NVGPaint* instead of auto refs (which it doesn't know how to reflect on) 3237 public void fillPaint (NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc { 3238 fillPaint(ctx, *paint); 3239 } 3240 3241 /// Sets current fill style to a paint, which can be a one of the gradients or a pattern. 3242 /// Group: render_styles 3243 @scriptable 3244 public void fillPaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc { 3245 NVGstate* state = nvg__getState(ctx); 3246 state.fill = paint; 3247 //nvgTransformMultiply(state.fill.xform[], state.xform[]); 3248 state.fill.xform.mul(state.xform); 3249 } 3250 3251 /// Sets current fill style to a multistop linear gradient. 3252 /// Group: render_styles 3253 public void fillPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc { 3254 if (!lgs.valid) { 3255 NVGPaint p = void; 3256 memset(&p, 0, p.sizeof); 3257 nvg__setPaintColor(p, NVGColor.red); 3258 ctx.fillPaint = p; 3259 } else if (lgs.midp >= -1) { 3260 //{ import core.stdc.stdio; printf("SIMPLE! midp=%f\n", cast(double)lgs.midp); } 3261 ctx.fillPaint = ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc); 3262 } else { 3263 ctx.fillPaint = ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid); 3264 } 3265 } 3266 3267 /// Returns current transformation matrix. 3268 /// Group: render_transformations 3269 public NVGMatrix currTransform (NVGContext ctx) pure nothrow @trusted @nogc { 3270 NVGstate* state = nvg__getState(ctx); 3271 return state.xform; 3272 } 3273 3274 /// Sets current transformation matrix. 3275 /// Group: render_transformations 3276 public void currTransform() (NVGContext ctx, in auto ref NVGMatrix m) nothrow @trusted @nogc { 3277 NVGstate* state = nvg__getState(ctx); 3278 state.xform = m; 3279 } 3280 3281 /// Resets current transform to an identity matrix. 3282 /// Group: render_transformations 3283 @scriptable 3284 public void resetTransform (NVGContext ctx) nothrow @trusted @nogc { 3285 NVGstate* state = nvg__getState(ctx); 3286 state.xform.identity; 3287 } 3288 3289 /// Premultiplies current coordinate system by specified matrix. 3290 /// Group: render_transformations 3291 public void transform() (NVGContext ctx, in auto ref NVGMatrix mt) nothrow @trusted @nogc { 3292 NVGstate* state = nvg__getState(ctx); 3293 //nvgTransformPremultiply(state.xform[], t[]); 3294 state.xform *= mt; 3295 } 3296 3297 /// Translates current coordinate system. 3298 /// Group: render_transformations 3299 @scriptable 3300 public void translate (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 3301 NVGstate* state = nvg__getState(ctx); 3302 //NVGMatrix t = void; 3303 //nvgTransformTranslate(t[], x, y); 3304 //nvgTransformPremultiply(state.xform[], t[]); 3305 state.xform.premul(NVGMatrix.Translated(x, y)); 3306 } 3307 3308 /// Rotates current coordinate system. Angle is specified in radians. 3309 /// Group: render_transformations 3310 @scriptable 3311 public void rotate (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3312 NVGstate* state = nvg__getState(ctx); 3313 //NVGMatrix t = void; 3314 //nvgTransformRotate(t[], angle); 3315 //nvgTransformPremultiply(state.xform[], t[]); 3316 state.xform.premul(NVGMatrix.Rotated(angle)); 3317 } 3318 3319 /// Skews the current coordinate system along X axis. Angle is specified in radians. 3320 /// Group: render_transformations 3321 @scriptable 3322 public void skewX (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3323 NVGstate* state = nvg__getState(ctx); 3324 //NVGMatrix t = void; 3325 //nvgTransformSkewX(t[], angle); 3326 //nvgTransformPremultiply(state.xform[], t[]); 3327 state.xform.premul(NVGMatrix.SkewedX(angle)); 3328 } 3329 3330 /// Skews the current coordinate system along Y axis. Angle is specified in radians. 3331 /// Group: render_transformations 3332 @scriptable 3333 public void skewY (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3334 NVGstate* state = nvg__getState(ctx); 3335 //NVGMatrix t = void; 3336 //nvgTransformSkewY(t[], angle); 3337 //nvgTransformPremultiply(state.xform[], t[]); 3338 state.xform.premul(NVGMatrix.SkewedY(angle)); 3339 } 3340 3341 /// Scales the current coordinate system. 3342 /// Group: render_transformations 3343 @scriptable 3344 public void scale (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 3345 NVGstate* state = nvg__getState(ctx); 3346 //NVGMatrix t = void; 3347 //nvgTransformScale(t[], x, y); 3348 //nvgTransformPremultiply(state.xform[], t[]); 3349 state.xform.premul(NVGMatrix.Scaled(x, y)); 3350 } 3351 3352 3353 // ////////////////////////////////////////////////////////////////////////// // 3354 // Images 3355 3356 /// Creates image by loading it from the disk from specified file name. 3357 /// Returns handle to the image or 0 on error. 3358 /// Group: images 3359 public NVGImage createImage() (NVGContext ctx, const(char)[] filename, const(NVGImageFlag)[] imageFlagsList...) { 3360 static if (NanoVegaHasArsdImage) { 3361 import arsd.image; 3362 // do we have new arsd API to load images? 3363 static if (!is(typeof(MemoryImage.fromImageFile)) || !is(typeof(MemoryImage.clearInternal))) { 3364 static assert(0, "Sorry, your ARSD is too old. Please, update it."); 3365 } 3366 try { 3367 auto oimg = MemoryImage.fromImageFile(filename); 3368 if (auto img = cast(TrueColorImage)oimg) { 3369 scope(exit) oimg.clearInternal(); 3370 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList); 3371 } else { 3372 TrueColorImage img = oimg.getAsTrueColorImage; 3373 scope(exit) img.clearInternal(); 3374 oimg.clearInternal(); // drop original image, as `getAsTrueColorImage()` MUST create a new one here 3375 oimg = null; 3376 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList); 3377 } 3378 } catch (Exception) {} 3379 return NVGImage.init; 3380 } else { 3381 import std.internal.cstring; 3382 ubyte* img; 3383 int w, h, n; 3384 stbi_set_unpremultiply_on_load(1); 3385 stbi_convert_iphone_png_to_rgb(1); 3386 img = stbi_load(filename.tempCString, &w, &h, &n, 4); 3387 if (img is null) { 3388 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); 3389 return NVGImage.init; 3390 } 3391 auto image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList); 3392 stbi_image_free(img); 3393 return image; 3394 } 3395 } 3396 3397 static if (NanoVegaHasArsdImage) { 3398 /// Creates image by loading it from the specified memory image. 3399 /// Returns handle to the image or 0 on error. 3400 /// Group: images 3401 public NVGImage createImageFromMemoryImage() (NVGContext ctx, MemoryImage img, const(NVGImageFlag)[] imageFlagsList...) { 3402 if (img is null) return NVGImage.init; 3403 if (auto tc = cast(TrueColorImage)img) { 3404 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList); 3405 } else { 3406 auto tc = img.getAsTrueColorImage; 3407 scope(exit) tc.clearInternal(); // here, it is guaranteed that `tc` is newly allocated image, so it is safe to kill it 3408 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList); 3409 } 3410 } 3411 } else { 3412 /// Creates image by loading it from the specified chunk of memory. 3413 /// Returns handle to the image or 0 on error. 3414 /// Group: images 3415 public NVGImage createImageMem() (NVGContext ctx, const(ubyte)* data, int ndata, const(NVGImageFlag)[] imageFlagsList...) { 3416 int w, h, n, image; 3417 ubyte* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4); 3418 if (img is null) { 3419 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); 3420 return NVGImage.init; 3421 } 3422 image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList); 3423 stbi_image_free(img); 3424 return image; 3425 } 3426 } 3427 3428 /// Creates image from specified image data. 3429 /// Returns handle to the image or 0 on error. 3430 /// Group: images 3431 public NVGImage createImageRGBA (NVGContext ctx, int w, int h, const(void)[] data, const(NVGImageFlag)[] imageFlagsList...) nothrow @trusted @nogc { 3432 if (w < 1 || h < 1 || data.length < w*h*4) return NVGImage.init; 3433 uint imageFlags = 0; 3434 foreach (immutable uint flag; imageFlagsList) imageFlags |= flag; 3435 NVGImage res; 3436 res.id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.RGBA, w, h, imageFlags, cast(const(ubyte)*)data.ptr); 3437 if (res.id > 0) { 3438 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); } 3439 res.ctx = ctx; 3440 ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount 3441 } 3442 return res; 3443 } 3444 3445 /// Updates image data specified by image handle. 3446 /// Group: images 3447 public void updateImage() (NVGContext ctx, auto ref NVGImage image, const(void)[] data) nothrow @trusted @nogc { 3448 if (image.valid) { 3449 int w, h; 3450 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3451 ctx.params.renderGetTextureSize(ctx.params.userPtr, image.id, &w, &h); 3452 ctx.params.renderUpdateTexture(ctx.params.userPtr, image.id, 0, 0, w, h, cast(const(ubyte)*)data.ptr); 3453 } 3454 } 3455 3456 /// Returns the dimensions of a created image. 3457 /// Group: images 3458 public void imageSize() (NVGContext ctx, in auto ref NVGImage image, out int w, out int h) nothrow @trusted @nogc { 3459 if (image.valid) { 3460 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3461 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, image.id, &w, &h); 3462 } 3463 } 3464 3465 /// Deletes created image. 3466 /// Group: images 3467 public void deleteImage() (NVGContext ctx, ref NVGImage image) nothrow @trusted @nogc { 3468 if (ctx is null || !image.valid) return; 3469 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3470 image.clear(); 3471 } 3472 3473 3474 // ////////////////////////////////////////////////////////////////////////// // 3475 // Paints 3476 3477 private void linearGradient() {} // hack for dmd bug 3478 3479 static if (NanoVegaHasArsdColor) { 3480 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates 3481 * of the linear gradient, icol specifies the start color and ocol the end color. 3482 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3483 * 3484 * Group: paints 3485 */ 3486 @scriptable 3487 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 { 3488 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), NVGColor(ocol)); 3489 } 3490 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start 3491 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in 3492 * range `(0..1)`, and ocol the end color. 3493 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3494 * 3495 * Group: paints 3496 */ 3497 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 { 3498 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), midp, NVGColor(mcol), NVGColor(ocol)); 3499 } 3500 } 3501 3502 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates 3503 * of the linear gradient, icol specifies the start color and ocol the end color. 3504 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3505 * 3506 * Group: paints 3507 */ 3508 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 { 3509 enum large = 1e5f; 3510 3511 NVGPaint p = void; 3512 memset(&p, 0, p.sizeof); 3513 p.simpleColor = false; 3514 3515 // Calculate transform aligned to the line 3516 float dx = ex-sx; 3517 float dy = ey-sy; 3518 immutable float d = nvg__sqrtf(dx*dx+dy*dy); 3519 if (d > 0.0001f) { 3520 dx /= d; 3521 dy /= d; 3522 } else { 3523 dx = 0; 3524 dy = 1; 3525 } 3526 3527 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx; 3528 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy; 3529 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large; 3530 3531 p.extent.ptr[0] = large; 3532 p.extent.ptr[1] = large+d*0.5f; 3533 3534 p.radius = 0.0f; 3535 3536 p.feather = nvg__max(NVG_MIN_FEATHER, d); 3537 3538 p.innerColor = p.middleColor = icol; 3539 p.outerColor = ocol; 3540 p.midp = -1; 3541 3542 return p; 3543 } 3544 3545 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start 3546 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in 3547 * range `(0..1)`, and ocol the end color. 3548 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3549 * 3550 * Group: paints 3551 */ 3552 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 { 3553 enum large = 1e5f; 3554 3555 NVGPaint p = void; 3556 memset(&p, 0, p.sizeof); 3557 p.simpleColor = false; 3558 3559 // Calculate transform aligned to the line 3560 float dx = ex-sx; 3561 float dy = ey-sy; 3562 immutable float d = nvg__sqrtf(dx*dx+dy*dy); 3563 if (d > 0.0001f) { 3564 dx /= d; 3565 dy /= d; 3566 } else { 3567 dx = 0; 3568 dy = 1; 3569 } 3570 3571 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx; 3572 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy; 3573 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large; 3574 3575 p.extent.ptr[0] = large; 3576 p.extent.ptr[1] = large+d*0.5f; 3577 3578 p.radius = 0.0f; 3579 3580 p.feather = nvg__max(NVG_MIN_FEATHER, d); 3581 3582 if (midp <= 0) { 3583 p.innerColor = p.middleColor = mcol; 3584 p.midp = -1; 3585 } else if (midp > 1) { 3586 p.innerColor = p.middleColor = icol; 3587 p.midp = -1; 3588 } else { 3589 p.innerColor = icol; 3590 p.middleColor = mcol; 3591 p.midp = midp; 3592 } 3593 p.outerColor = ocol; 3594 3595 return p; 3596 } 3597 3598 static if (NanoVegaHasArsdColor) { 3599 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify 3600 * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. 3601 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3602 * 3603 * Group: paints 3604 */ 3605 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 { 3606 return ctx.radialGradient(cx, cy, inr, outr, NVGColor(icol), NVGColor(ocol)); 3607 } 3608 } 3609 3610 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify 3611 * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. 3612 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3613 * 3614 * Group: paints 3615 */ 3616 public NVGPaint radialGradient() (NVGContext ctx, float cx, float cy, float inr, float outr, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc { 3617 immutable float r = (inr+outr)*0.5f; 3618 immutable float f = (outr-inr); 3619 3620 NVGPaint p = void; 3621 memset(&p, 0, p.sizeof); 3622 p.simpleColor = false; 3623 3624 p.xform.identity; 3625 p.xform.mat.ptr[4] = cx; 3626 p.xform.mat.ptr[5] = cy; 3627 3628 p.extent.ptr[0] = r; 3629 p.extent.ptr[1] = r; 3630 3631 p.radius = r; 3632 3633 p.feather = nvg__max(NVG_MIN_FEATHER, f); 3634 3635 p.innerColor = p.middleColor = icol; 3636 p.outerColor = ocol; 3637 p.midp = -1; 3638 3639 return p; 3640 } 3641 3642 static if (NanoVegaHasArsdColor) { 3643 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering 3644 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle, 3645 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry 3646 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. 3647 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3648 * 3649 * Group: paints 3650 */ 3651 public NVGPaint 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 { 3652 return ctx.boxGradient(x, y, w, h, r, f, NVGColor(icol), NVGColor(ocol)); 3653 } 3654 } 3655 3656 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering 3657 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle, 3658 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry 3659 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. 3660 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3661 * 3662 * Group: paints 3663 */ 3664 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 { 3665 NVGPaint p = void; 3666 memset(&p, 0, p.sizeof); 3667 p.simpleColor = false; 3668 3669 p.xform.identity; 3670 p.xform.mat.ptr[4] = x+w*0.5f; 3671 p.xform.mat.ptr[5] = y+h*0.5f; 3672 3673 p.extent.ptr[0] = w*0.5f; 3674 p.extent.ptr[1] = h*0.5f; 3675 3676 p.radius = r; 3677 3678 p.feather = nvg__max(NVG_MIN_FEATHER, f); 3679 3680 p.innerColor = p.middleColor = icol; 3681 p.outerColor = ocol; 3682 p.midp = -1; 3683 3684 return p; 3685 } 3686 3687 /** Creates and returns an image pattern. Parameters `(cx, cy)` specify the left-top location of the image pattern, 3688 * `(w, h)` the size of one image, [angle] rotation around the top-left corner, [image] is handle to the image to render. 3689 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3690 * 3691 * Group: paints 3692 */ 3693 public NVGPaint imagePattern() (NVGContext ctx, float cx, float cy, float w, float h, float angle, in auto ref NVGImage image, float alpha=1) nothrow @trusted @nogc { 3694 NVGPaint p = void; 3695 memset(&p, 0, p.sizeof); 3696 p.simpleColor = false; 3697 3698 p.xform.identity.rotate(angle); 3699 p.xform.mat.ptr[4] = cx; 3700 p.xform.mat.ptr[5] = cy; 3701 3702 p.extent.ptr[0] = w; 3703 p.extent.ptr[1] = h; 3704 3705 p.image = image; 3706 3707 p.innerColor = p.middleColor = p.outerColor = nvgRGBAf(1, 1, 1, alpha); 3708 p.midp = -1; 3709 3710 return p; 3711 } 3712 3713 /// Linear gradient with multiple stops. 3714 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3715 /// Group: paints 3716 public struct NVGLGS { 3717 private: 3718 NVGColor ic, mc, oc; // inner, middle, out 3719 float midp; 3720 NVGImage imgid; 3721 // [imagePattern] arguments 3722 float cx, cy, dimx, dimy; // dimx and dimy are ex and ey for simple gradients 3723 public float angle; /// 3724 3725 public: 3726 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (imgid.valid || midp >= -1); } /// 3727 void clear () nothrow @safe @nogc { pragma(inline, true); imgid.clear(); midp = float.nan; } /// 3728 } 3729 3730 /** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops]. 3731 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3732 * 3733 * $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3734 * Group: paints 3735 */ 3736 public NVGPaint asPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc { 3737 if (!lgs.valid) { 3738 NVGPaint p = void; 3739 memset(&p, 0, p.sizeof); 3740 nvg__setPaintColor(p, NVGColor.red); 3741 return p; 3742 } else if (lgs.midp >= -1) { 3743 return ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc); 3744 } else { 3745 return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid); 3746 } 3747 } 3748 3749 /// Gradient Stop Point. 3750 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3751 /// Group: paints 3752 public struct NVGGradientStop { 3753 float offset = 0; /// [0..1] 3754 NVGColor color; /// 3755 3756 this() (in float aofs, in auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } /// 3757 static if (NanoVegaHasArsdColor) { 3758 this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } /// 3759 } 3760 } 3761 3762 /// Create linear gradient data suitable to use with `linearGradient(res)`. 3763 /// Don't forget to destroy the result when you don't need it anymore with `ctx.kill(res);`. 3764 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3765 /// Group: paints 3766 public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, const(NVGGradientStop)[] stops...) nothrow @trusted @nogc { 3767 // based on the code by Jorge Acereda <jacereda@gmail.com> 3768 enum NVG_GRADIENT_SAMPLES = 1024; 3769 static void gradientSpan (uint* dst, const(NVGGradientStop)* s0, const(NVGGradientStop)* s1) nothrow @trusted @nogc { 3770 immutable float s0o = nvg__clamp(s0.offset, 0.0f, 1.0f); 3771 immutable float s1o = nvg__clamp(s1.offset, 0.0f, 1.0f); 3772 uint s = cast(uint)(s0o*NVG_GRADIENT_SAMPLES); 3773 uint e = cast(uint)(s1o*NVG_GRADIENT_SAMPLES); 3774 uint sc = 0xffffffffU; 3775 uint sh = 24; 3776 uint r = cast(uint)(s0.color.rgba[0]*sc); 3777 uint g = cast(uint)(s0.color.rgba[1]*sc); 3778 uint b = cast(uint)(s0.color.rgba[2]*sc); 3779 uint a = cast(uint)(s0.color.rgba[3]*sc); 3780 uint dr = cast(uint)((s1.color.rgba[0]*sc-r)/(e-s)); 3781 uint dg = cast(uint)((s1.color.rgba[1]*sc-g)/(e-s)); 3782 uint db = cast(uint)((s1.color.rgba[2]*sc-b)/(e-s)); 3783 uint da = cast(uint)((s1.color.rgba[3]*sc-a)/(e-s)); 3784 dst += s; 3785 foreach (immutable _; s..e) { 3786 version(BigEndian) { 3787 *dst++ = ((r>>sh)<<24)+((g>>sh)<<16)+((b>>sh)<<8)+((a>>sh)<<0); 3788 } else { 3789 *dst++ = ((a>>sh)<<24)+((b>>sh)<<16)+((g>>sh)<<8)+((r>>sh)<<0); 3790 } 3791 r += dr; 3792 g += dg; 3793 b += db; 3794 a += da; 3795 } 3796 } 3797 3798 NVGLGS res; 3799 res.cx = sx; 3800 res.cy = sy; 3801 3802 if (stops.length == 2 && stops.ptr[0].offset <= 0 && stops.ptr[1].offset >= 1) { 3803 // create simple linear gradient 3804 res.ic = res.mc = stops.ptr[0].color; 3805 res.oc = stops.ptr[1].color; 3806 res.midp = -1; 3807 res.dimx = ex; 3808 res.dimy = ey; 3809 } else if (stops.length == 3 && stops.ptr[0].offset <= 0 && stops.ptr[2].offset >= 1) { 3810 // create simple linear gradient with middle stop 3811 res.ic = stops.ptr[0].color; 3812 res.mc = stops.ptr[1].color; 3813 res.oc = stops.ptr[2].color; 3814 res.midp = stops.ptr[1].offset; 3815 res.dimx = ex; 3816 res.dimy = ey; 3817 } else { 3818 // create image gradient 3819 uint[NVG_GRADIENT_SAMPLES] data = void; 3820 immutable float w = ex-sx; 3821 immutable float h = ey-sy; 3822 res.dimx = nvg__sqrtf(w*w+h*h); 3823 res.dimy = 1; //??? 3824 3825 res.angle = 3826 (/*nvg__absf(h) < 0.0001 ? 0 : 3827 nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/ 3828 nvg__atan2f(h/*ey-sy*/, w/*ex-sx*/)); 3829 3830 if (stops.length > 0) { 3831 auto s0 = NVGGradientStop(0, nvgRGBAf(0, 0, 0, 1)); 3832 auto s1 = NVGGradientStop(1, nvgRGBAf(1, 1, 1, 1)); 3833 if (stops.length > 64) stops = stops[0..64]; 3834 if (stops.length) { 3835 s0.color = stops[0].color; 3836 s1.color = stops[$-1].color; 3837 } 3838 gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1)); 3839 foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1); 3840 gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1); 3841 res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[]/*, NVGImageFlag.RepeatX, NVGImageFlag.RepeatY*/); 3842 } 3843 } 3844 return res; 3845 } 3846 3847 3848 // ////////////////////////////////////////////////////////////////////////// // 3849 // Scissoring 3850 3851 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform. 3852 /// Group: scissoring 3853 public void scissor (NVGContext ctx, in float x, in float y, float w, float h) nothrow @trusted @nogc { 3854 NVGstate* state = nvg__getState(ctx); 3855 3856 w = nvg__max(0.0f, w); 3857 h = nvg__max(0.0f, h); 3858 3859 state.scissor.xform.identity; 3860 state.scissor.xform.mat.ptr[4] = x+w*0.5f; 3861 state.scissor.xform.mat.ptr[5] = y+h*0.5f; 3862 //nvgTransformMultiply(state.scissor.xform[], state.xform[]); 3863 state.scissor.xform.mul(state.xform); 3864 3865 state.scissor.extent.ptr[0] = w*0.5f; 3866 state.scissor.extent.ptr[1] = h*0.5f; 3867 } 3868 3869 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform. 3870 /// Arguments: [x, y, w, h]* 3871 /// Group: scissoring 3872 public void scissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 3873 enum ArgC = 4; 3874 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [scissor] call"); 3875 if (args.length < ArgC) return; 3876 NVGstate* state = nvg__getState(ctx); 3877 const(float)* aptr = args.ptr; 3878 foreach (immutable idx; 0..args.length/ArgC) { 3879 immutable x = *aptr++; 3880 immutable y = *aptr++; 3881 immutable w = nvg__max(0.0f, *aptr++); 3882 immutable h = nvg__max(0.0f, *aptr++); 3883 3884 state.scissor.xform.identity; 3885 state.scissor.xform.mat.ptr[4] = x+w*0.5f; 3886 state.scissor.xform.mat.ptr[5] = y+h*0.5f; 3887 //nvgTransformMultiply(state.scissor.xform[], state.xform[]); 3888 state.scissor.xform.mul(state.xform); 3889 3890 state.scissor.extent.ptr[0] = w*0.5f; 3891 state.scissor.extent.ptr[1] = h*0.5f; 3892 } 3893 } 3894 3895 void nvg__isectRects (float* dst, float ax, float ay, float aw, float ah, float bx, float by, float bw, float bh) nothrow @trusted @nogc { 3896 immutable float minx = nvg__max(ax, bx); 3897 immutable float miny = nvg__max(ay, by); 3898 immutable float maxx = nvg__min(ax+aw, bx+bw); 3899 immutable float maxy = nvg__min(ay+ah, by+bh); 3900 dst[0] = minx; 3901 dst[1] = miny; 3902 dst[2] = nvg__max(0.0f, maxx-minx); 3903 dst[3] = nvg__max(0.0f, maxy-miny); 3904 } 3905 3906 /** Intersects current scissor rectangle with the specified rectangle. 3907 * The scissor rectangle is transformed by the current transform. 3908 * Note: in case the rotation of previous scissor rect differs from 3909 * the current one, the intersection will be done between the specified 3910 * rectangle and the previous scissor rectangle transformed in the current 3911 * transform space. The resulting shape is always rectangle. 3912 * 3913 * Group: scissoring 3914 */ 3915 public void intersectScissor (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc { 3916 NVGstate* state = nvg__getState(ctx); 3917 3918 // If no previous scissor has been set, set the scissor as current scissor. 3919 if (state.scissor.extent.ptr[0] < 0) { 3920 ctx.scissor(x, y, w, h); 3921 return; 3922 } 3923 3924 NVGMatrix pxform = void; 3925 NVGMatrix invxorm = void; 3926 float[4] rect = void; 3927 3928 // Transform the current scissor rect into current transform space. 3929 // If there is difference in rotation, this will be approximation. 3930 //memcpy(pxform.mat.ptr, state.scissor.xform.ptr, float.sizeof*6); 3931 pxform = state.scissor.xform; 3932 immutable float ex = state.scissor.extent.ptr[0]; 3933 immutable float ey = state.scissor.extent.ptr[1]; 3934 //nvgTransformInverse(invxorm[], state.xform[]); 3935 invxorm = state.xform.inverted; 3936 //nvgTransformMultiply(pxform[], invxorm[]); 3937 pxform.mul(invxorm); 3938 immutable float tex = ex*nvg__absf(pxform.mat.ptr[0])+ey*nvg__absf(pxform.mat.ptr[2]); 3939 immutable float tey = ex*nvg__absf(pxform.mat.ptr[1])+ey*nvg__absf(pxform.mat.ptr[3]); 3940 3941 // Intersect rects. 3942 nvg__isectRects(rect.ptr, pxform.mat.ptr[4]-tex, pxform.mat.ptr[5]-tey, tex*2, tey*2, x, y, w, h); 3943 3944 //ctx.scissor(rect.ptr[0], rect.ptr[1], rect.ptr[2], rect.ptr[3]); 3945 ctx.scissor(rect.ptr[0..4]); 3946 } 3947 3948 /** Intersects current scissor rectangle with the specified rectangle. 3949 * The scissor rectangle is transformed by the current transform. 3950 * Note: in case the rotation of previous scissor rect differs from 3951 * the current one, the intersection will be done between the specified 3952 * rectangle and the previous scissor rectangle transformed in the current 3953 * transform space. The resulting shape is always rectangle. 3954 * 3955 * Arguments: [x, y, w, h]* 3956 * 3957 * Group: scissoring 3958 */ 3959 public void intersectScissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 3960 enum ArgC = 4; 3961 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [intersectScissor] call"); 3962 if (args.length < ArgC) return; 3963 const(float)* aptr = args.ptr; 3964 foreach (immutable idx; 0..args.length/ArgC) { 3965 immutable x = *aptr++; 3966 immutable y = *aptr++; 3967 immutable w = *aptr++; 3968 immutable h = *aptr++; 3969 ctx.intersectScissor(x, y, w, h); 3970 } 3971 } 3972 3973 /// Reset and disables scissoring. 3974 /// Group: scissoring 3975 public void resetScissor (NVGContext ctx) nothrow @trusted @nogc { 3976 NVGstate* state = nvg__getState(ctx); 3977 state.scissor.xform.mat[] = 0.0f; 3978 state.scissor.extent[] = -1.0f; 3979 } 3980 3981 3982 // ////////////////////////////////////////////////////////////////////////// // 3983 // Render-Time Affine Transformations 3984 3985 /// Sets GPU affine transformatin matrix. Don't do scaling or skewing here. 3986 /// This matrix won't be saved/restored with context state save/restore operations, as it is not a part of that state. 3987 /// Group: gpu_affine 3988 public void affineGPU() (NVGContext ctx, in auto ref NVGMatrix mat) nothrow @trusted @nogc { 3989 ctx.gpuAffine = mat; 3990 ctx.params.renderSetAffine(ctx.params.userPtr, ctx.gpuAffine); 3991 } 3992 3993 /// Get current GPU affine transformatin matrix. 3994 /// Group: gpu_affine 3995 public NVGMatrix affineGPU (NVGContext ctx) nothrow @safe @nogc { 3996 pragma(inline, true); 3997 return ctx.gpuAffine; 3998 } 3999 4000 /// "Untransform" point using current GPU affine matrix. 4001 /// Group: gpu_affine 4002 public void gpuUntransformPoint (NVGContext ctx, float *dx, float *dy, in float x, in float y) nothrow @safe @nogc { 4003 if (ctx.gpuAffine.isIdentity) { 4004 if (dx !is null) *dx = x; 4005 if (dy !is null) *dy = y; 4006 } else { 4007 // inverse GPU transformation 4008 NVGMatrix igpu = ctx.gpuAffine.inverted; 4009 igpu.point(dx, dy, x, y); 4010 } 4011 } 4012 4013 4014 // ////////////////////////////////////////////////////////////////////////// // 4015 // rasterization (tesselation) code 4016 4017 int nvg__ptEquals (float x1, float y1, float x2, float y2, float tol) pure nothrow @safe @nogc { 4018 //pragma(inline, true); 4019 immutable float dx = x2-x1; 4020 immutable float dy = y2-y1; 4021 return dx*dx+dy*dy < tol*tol; 4022 } 4023 4024 float nvg__distPtSeg (float x, float y, float px, float py, float qx, float qy) pure nothrow @safe @nogc { 4025 immutable float pqx = qx-px; 4026 immutable float pqy = qy-py; 4027 float dx = x-px; 4028 float dy = y-py; 4029 immutable float d = pqx*pqx+pqy*pqy; 4030 float t = pqx*dx+pqy*dy; 4031 if (d > 0) t /= d; 4032 if (t < 0) t = 0; else if (t > 1) t = 1; 4033 dx = px+t*pqx-x; 4034 dy = py+t*pqy-y; 4035 return dx*dx+dy*dy; 4036 } 4037 4038 void nvg__appendCommands(bool useCommand=true) (NVGContext ctx, Command acmd, const(float)[] vals...) nothrow @trusted @nogc { 4039 int nvals = cast(int)vals.length; 4040 static if (useCommand) { 4041 enum addon = 1; 4042 } else { 4043 enum addon = 0; 4044 if (nvals == 0) return; // nothing to do 4045 } 4046 4047 NVGstate* state = nvg__getState(ctx); 4048 4049 if (ctx.ncommands+nvals+addon > ctx.ccommands) { 4050 //int ccommands = ctx.ncommands+nvals+ctx.ccommands/2; 4051 int ccommands = ((ctx.ncommands+(nvals+addon))|0xfff)+1; 4052 float* commands = cast(float*)realloc(ctx.commands, float.sizeof*ccommands); 4053 if (commands is null) assert(0, "NanoVega: out of memory"); 4054 ctx.commands = commands; 4055 ctx.ccommands = ccommands; 4056 assert(ctx.ncommands+(nvals+addon) <= ctx.ccommands); 4057 } 4058 4059 static if (!useCommand) acmd = cast(Command)vals.ptr[0]; 4060 4061 if (acmd != Command.Close && acmd != Command.Winding) { 4062 //assert(nvals+addon >= 3); 4063 ctx.commandx = vals.ptr[nvals-2]; 4064 ctx.commandy = vals.ptr[nvals-1]; 4065 } 4066 4067 // copy commands 4068 float* vp = ctx.commands+ctx.ncommands; 4069 static if (useCommand) { 4070 vp[0] = cast(float)acmd; 4071 if (nvals > 0) memcpy(vp+1, vals.ptr, nvals*float.sizeof); 4072 } else { 4073 memcpy(vp, vals.ptr, nvals*float.sizeof); 4074 } 4075 ctx.ncommands += nvals+addon; 4076 4077 // transform commands 4078 int i = nvals+addon; 4079 while (i > 0) { 4080 int nlen = 1; 4081 final switch (cast(Command)(*vp)) { 4082 case Command.MoveTo: 4083 case Command.LineTo: 4084 assert(i >= 3); 4085 state.xform.point(vp+1, vp+2, vp[1], vp[2]); 4086 nlen = 3; 4087 break; 4088 case Command.BezierTo: 4089 assert(i >= 7); 4090 state.xform.point(vp+1, vp+2, vp[1], vp[2]); 4091 state.xform.point(vp+3, vp+4, vp[3], vp[4]); 4092 state.xform.point(vp+5, vp+6, vp[5], vp[6]); 4093 nlen = 7; 4094 break; 4095 case Command.Close: 4096 nlen = 1; 4097 break; 4098 case Command.Winding: 4099 nlen = 2; 4100 break; 4101 } 4102 assert(nlen > 0 && nlen <= i); 4103 i -= nlen; 4104 vp += nlen; 4105 } 4106 } 4107 4108 void nvg__clearPathCache (NVGContext ctx) nothrow @trusted @nogc { 4109 // no need to clear paths, as data is not copied there 4110 //foreach (ref p; ctx.cache.paths[0..ctx.cache.npaths]) p.clear(); 4111 ctx.cache.npoints = 0; 4112 ctx.cache.npaths = 0; 4113 ctx.cache.fillReady = ctx.cache.strokeReady = false; 4114 ctx.cache.clipmode = NVGClipMode.None; 4115 } 4116 4117 NVGpath* nvg__lastPath (NVGContext ctx) nothrow @trusted @nogc { 4118 return (ctx.cache.npaths > 0 ? &ctx.cache.paths[ctx.cache.npaths-1] : null); 4119 } 4120 4121 void nvg__addPath (NVGContext ctx) nothrow @trusted @nogc { 4122 import core.stdc.stdlib : realloc; 4123 import core.stdc.string : memset; 4124 4125 if (ctx.cache.npaths+1 > ctx.cache.cpaths) { 4126 int cpaths = ctx.cache.npaths+1+ctx.cache.cpaths/2; 4127 NVGpath* paths = cast(NVGpath*)realloc(ctx.cache.paths, NVGpath.sizeof*cpaths); 4128 if (paths is null) assert(0, "NanoVega: out of memory"); 4129 ctx.cache.paths = paths; 4130 ctx.cache.cpaths = cpaths; 4131 } 4132 4133 NVGpath* path = &ctx.cache.paths[ctx.cache.npaths++]; 4134 memset(path, 0, NVGpath.sizeof); 4135 path.first = ctx.cache.npoints; 4136 path.mWinding = NVGWinding.CCW; 4137 } 4138 4139 NVGpoint* nvg__lastPoint (NVGContext ctx) nothrow @trusted @nogc { 4140 return (ctx.cache.npoints > 0 ? &ctx.cache.points[ctx.cache.npoints-1] : null); 4141 } 4142 4143 void nvg__addPoint (NVGContext ctx, float x, float y, int flags) nothrow @trusted @nogc { 4144 NVGpath* path = nvg__lastPath(ctx); 4145 if (path is null) return; 4146 4147 if (path.count > 0 && ctx.cache.npoints > 0) { 4148 NVGpoint* pt = nvg__lastPoint(ctx); 4149 if (nvg__ptEquals(pt.x, pt.y, x, y, ctx.distTol)) { 4150 pt.flags |= flags; 4151 return; 4152 } 4153 } 4154 4155 if (ctx.cache.npoints+1 > ctx.cache.cpoints) { 4156 int cpoints = ctx.cache.npoints+1+ctx.cache.cpoints/2; 4157 NVGpoint* points = cast(NVGpoint*)realloc(ctx.cache.points, NVGpoint.sizeof*cpoints); 4158 if (points is null) return; 4159 ctx.cache.points = points; 4160 ctx.cache.cpoints = cpoints; 4161 } 4162 4163 NVGpoint* pt = &ctx.cache.points[ctx.cache.npoints]; 4164 memset(pt, 0, (*pt).sizeof); 4165 pt.x = x; 4166 pt.y = y; 4167 pt.flags = cast(ubyte)flags; 4168 4169 ++ctx.cache.npoints; 4170 ++path.count; 4171 } 4172 4173 void nvg__closePath (NVGContext ctx) nothrow @trusted @nogc { 4174 NVGpath* path = nvg__lastPath(ctx); 4175 if (path is null) return; 4176 path.closed = true; 4177 } 4178 4179 void nvg__pathWinding (NVGContext ctx, NVGWinding winding) nothrow @trusted @nogc { 4180 NVGpath* path = nvg__lastPath(ctx); 4181 if (path is null) return; 4182 path.mWinding = winding; 4183 } 4184 4185 float nvg__getAverageScale() (in auto ref NVGMatrix t) /*pure*/ nothrow @trusted @nogc { 4186 immutable float sx = nvg__sqrtf(t.mat.ptr[0]*t.mat.ptr[0]+t.mat.ptr[2]*t.mat.ptr[2]); 4187 immutable float sy = nvg__sqrtf(t.mat.ptr[1]*t.mat.ptr[1]+t.mat.ptr[3]*t.mat.ptr[3]); 4188 return (sx+sy)*0.5f; 4189 } 4190 4191 NVGVertex* nvg__allocTempVerts (NVGContext ctx, int nverts) nothrow @trusted @nogc { 4192 if (nverts > ctx.cache.cverts) { 4193 int cverts = (nverts+0xff)&~0xff; // Round up to prevent allocations when things change just slightly. 4194 NVGVertex* verts = cast(NVGVertex*)realloc(ctx.cache.verts, NVGVertex.sizeof*cverts); 4195 if (verts is null) return null; 4196 ctx.cache.verts = verts; 4197 ctx.cache.cverts = cverts; 4198 } 4199 4200 return ctx.cache.verts; 4201 } 4202 4203 float nvg__triarea2 (float ax, float ay, float bx, float by, float cx, float cy) pure nothrow @safe @nogc { 4204 immutable float abx = bx-ax; 4205 immutable float aby = by-ay; 4206 immutable float acx = cx-ax; 4207 immutable float acy = cy-ay; 4208 return acx*aby-abx*acy; 4209 } 4210 4211 float nvg__polyArea (NVGpoint* pts, int npts) nothrow @trusted @nogc { 4212 float area = 0; 4213 foreach (int i; 2..npts) { 4214 NVGpoint* a = &pts[0]; 4215 NVGpoint* b = &pts[i-1]; 4216 NVGpoint* c = &pts[i]; 4217 area += nvg__triarea2(a.x, a.y, b.x, b.y, c.x, c.y); 4218 } 4219 return area*0.5f; 4220 } 4221 4222 void nvg__polyReverse (NVGpoint* pts, int npts) nothrow @trusted @nogc { 4223 NVGpoint tmp = void; 4224 int i = 0, j = npts-1; 4225 while (i < j) { 4226 tmp = pts[i]; 4227 pts[i] = pts[j]; 4228 pts[j] = tmp; 4229 ++i; 4230 --j; 4231 } 4232 } 4233 4234 void nvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc { 4235 vtx.x = x; 4236 vtx.y = y; 4237 vtx.u = u; 4238 vtx.v = v; 4239 } 4240 4241 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 { 4242 if (level > 10) return; 4243 4244 // check for collinear points, and use AFD tesselator on such curves (it is WAY faster for this case) 4245 /* 4246 if (level == 0 && ctx.tesselatortype == NVGTesselation.Combined) { 4247 static bool collinear (in float v0x, in float v0y, in float v1x, in float v1y, in float v2x, in float v2y) nothrow @trusted @nogc { 4248 immutable float cz = (v1x-v0x)*(v2y-v0y)-(v2x-v0x)*(v1y-v0y); 4249 return (nvg__absf(cz*cz) <= 0.01f); // arbitrary number, seems to work ok with NanoSVG output 4250 } 4251 if (collinear(x1, y1, x2, y2, x3, y3) && collinear(x2, y2, x3, y3, x3, y4)) { 4252 //{ import core.stdc.stdio; printf("AFD fallback!\n"); } 4253 ctx.nvg__tesselateBezierAFD(x1, y1, x2, y2, x3, y3, x4, y4, type); 4254 return; 4255 } 4256 } 4257 */ 4258 4259 immutable float x12 = (x1+x2)*0.5f; 4260 immutable float y12 = (y1+y2)*0.5f; 4261 immutable float x23 = (x2+x3)*0.5f; 4262 immutable float y23 = (y2+y3)*0.5f; 4263 immutable float x34 = (x3+x4)*0.5f; 4264 immutable float y34 = (y3+y4)*0.5f; 4265 immutable float x123 = (x12+x23)*0.5f; 4266 immutable float y123 = (y12+y23)*0.5f; 4267 4268 immutable float dx = x4-x1; 4269 immutable float dy = y4-y1; 4270 immutable float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx)); 4271 immutable float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx)); 4272 4273 if ((d2+d3)*(d2+d3) < ctx.tessTol*(dx*dx+dy*dy)) { 4274 nvg__addPoint(ctx, x4, y4, type); 4275 return; 4276 } 4277 4278 immutable float x234 = (x23+x34)*0.5f; 4279 immutable float y234 = (y23+y34)*0.5f; 4280 immutable float x1234 = (x123+x234)*0.5f; 4281 immutable float y1234 = (y123+y234)*0.5f; 4282 4283 // "taxicab" / "manhattan" check for flat curves 4284 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) { 4285 nvg__addPoint(ctx, x1234, y1234, type); 4286 return; 4287 } 4288 4289 nvg__tesselateBezier(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0); 4290 nvg__tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type); 4291 } 4292 4293 // based on the ideas and code of Maxim Shemanarev. Rest in Peace, bro! 4294 // see http://www.antigrain.com/research/adaptive_bezier/index.html 4295 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 { 4296 enum CollinearEPS = 0.00000001f; // 0.00001f; 4297 enum AngleTolEPS = 0.01f; 4298 4299 static float distSquared (in float x1, in float y1, in float x2, in float y2) pure nothrow @safe @nogc { 4300 pragma(inline, true); 4301 immutable float dx = x2-x1; 4302 immutable float dy = y2-y1; 4303 return dx*dx+dy*dy; 4304 } 4305 4306 if (level == 0) { 4307 nvg__addPoint(ctx, x1, y1, 0); 4308 nvg__tesselateBezierMcSeem(ctx, x1, y1, x2, y2, x3, y3, x4, y4, 1, type); 4309 nvg__addPoint(ctx, x4, y4, type); 4310 return; 4311 } 4312 4313 if (level >= 32) return; // recurse limit; practically, it should be never reached, but... 4314 4315 // calculate all the mid-points of the line segments 4316 immutable float x12 = (x1+x2)*0.5f; 4317 immutable float y12 = (y1+y2)*0.5f; 4318 immutable float x23 = (x2+x3)*0.5f; 4319 immutable float y23 = (y2+y3)*0.5f; 4320 immutable float x34 = (x3+x4)*0.5f; 4321 immutable float y34 = (y3+y4)*0.5f; 4322 immutable float x123 = (x12+x23)*0.5f; 4323 immutable float y123 = (y12+y23)*0.5f; 4324 immutable float x234 = (x23+x34)*0.5f; 4325 immutable float y234 = (y23+y34)*0.5f; 4326 immutable float x1234 = (x123+x234)*0.5f; 4327 immutable float y1234 = (y123+y234)*0.5f; 4328 4329 // try to approximate the full cubic curve by a single straight line 4330 immutable float dx = x4-x1; 4331 immutable float dy = y4-y1; 4332 4333 float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx)); 4334 float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx)); 4335 //immutable float da1, da2, k; 4336 4337 final switch ((cast(int)(d2 > CollinearEPS)<<1)+cast(int)(d3 > CollinearEPS)) { 4338 case 0: 4339 // all collinear or p1 == p4 4340 float k = dx*dx+dy*dy; 4341 if (k == 0) { 4342 d2 = distSquared(x1, y1, x2, y2); 4343 d3 = distSquared(x4, y4, x3, y3); 4344 } else { 4345 k = 1.0f/k; 4346 float da1 = x2-x1; 4347 float da2 = y2-y1; 4348 d2 = k*(da1*dx+da2*dy); 4349 da1 = x3-x1; 4350 da2 = y3-y1; 4351 d3 = k*(da1*dx+da2*dy); 4352 if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) { 4353 // Simple collinear case, 1---2---3---4 4354 // We can leave just two endpoints 4355 return; 4356 } 4357 if (d2 <= 0) d2 = distSquared(x2, y2, x1, y1); 4358 else if (d2 >= 1) d2 = distSquared(x2, y2, x4, y4); 4359 else d2 = distSquared(x2, y2, x1+d2*dx, y1+d2*dy); 4360 4361 if (d3 <= 0) d3 = distSquared(x3, y3, x1, y1); 4362 else if (d3 >= 1) d3 = distSquared(x3, y3, x4, y4); 4363 else d3 = distSquared(x3, y3, x1+d3*dx, y1+d3*dy); 4364 } 4365 if (d2 > d3) { 4366 if (d2 < ctx.tessTol) { 4367 nvg__addPoint(ctx, x2, y2, type); 4368 return; 4369 } 4370 } if (d3 < ctx.tessTol) { 4371 nvg__addPoint(ctx, x3, y3, type); 4372 return; 4373 } 4374 break; 4375 case 1: 4376 // p1,p2,p4 are collinear, p3 is significant 4377 if (d3*d3 <= ctx.tessTol*(dx*dx+dy*dy)) { 4378 if (ctx.angleTol < AngleTolEPS) { 4379 nvg__addPoint(ctx, x23, y23, type); 4380 return; 4381 } else { 4382 // angle condition 4383 float da1 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-nvg__atan2f(y3-y2, x3-x2)); 4384 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4385 if (da1 < ctx.angleTol) { 4386 nvg__addPoint(ctx, x2, y2, type); 4387 nvg__addPoint(ctx, x3, y3, type); 4388 return; 4389 } 4390 if (ctx.cuspLimit != 0.0) { 4391 if (da1 > ctx.cuspLimit) { 4392 nvg__addPoint(ctx, x3, y3, type); 4393 return; 4394 } 4395 } 4396 } 4397 } 4398 break; 4399 case 2: 4400 // p1,p3,p4 are collinear, p2 is significant 4401 if (d2*d2 <= ctx.tessTol*(dx*dx+dy*dy)) { 4402 if (ctx.angleTol < AngleTolEPS) { 4403 nvg__addPoint(ctx, x23, y23, type); 4404 return; 4405 } else { 4406 // angle condition 4407 float da1 = nvg__absf(nvg__atan2f(y3-y2, x3-x2)-nvg__atan2f(y2-y1, x2-x1)); 4408 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4409 if (da1 < ctx.angleTol) { 4410 nvg__addPoint(ctx, x2, y2, type); 4411 nvg__addPoint(ctx, x3, y3, type); 4412 return; 4413 } 4414 if (ctx.cuspLimit != 0.0) { 4415 if (da1 > ctx.cuspLimit) { 4416 nvg__addPoint(ctx, x2, y2, type); 4417 return; 4418 } 4419 } 4420 } 4421 } 4422 break; 4423 case 3: 4424 // regular case 4425 if ((d2+d3)*(d2+d3) <= ctx.tessTol*(dx*dx+dy*dy)) { 4426 // if the curvature doesn't exceed the distance tolerance value, we tend to finish subdivisions 4427 if (ctx.angleTol < AngleTolEPS) { 4428 nvg__addPoint(ctx, x23, y23, type); 4429 return; 4430 } else { 4431 // angle and cusp condition 4432 immutable float k = nvg__atan2f(y3-y2, x3-x2); 4433 float da1 = nvg__absf(k-nvg__atan2f(y2-y1, x2-x1)); 4434 float da2 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-k); 4435 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4436 if (da2 >= NVG_PI) da2 = 2*NVG_PI-da2; 4437 if (da1+da2 < ctx.angleTol) { 4438 // finally we can stop the recursion 4439 nvg__addPoint(ctx, x23, y23, type); 4440 return; 4441 } 4442 if (ctx.cuspLimit != 0.0) { 4443 if (da1 > ctx.cuspLimit) { 4444 nvg__addPoint(ctx, x2, y2, type); 4445 return; 4446 } 4447 if (da2 > ctx.cuspLimit) { 4448 nvg__addPoint(ctx, x3, y3, type); 4449 return; 4450 } 4451 } 4452 } 4453 } 4454 break; 4455 } 4456 4457 // continue subdivision 4458 nvg__tesselateBezierMcSeem(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0); 4459 nvg__tesselateBezierMcSeem(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type); 4460 } 4461 4462 4463 // Adaptive forward differencing for bezier tesselation. 4464 // See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt. 4465 // "Adaptive forward differencing for rendering curves and surfaces." 4466 // ACM SIGGRAPH Computer Graphics. Vol. 21. No. 4. ACM, 1987. 4467 // original code by Taylor Holliday <taylor@audulus.com> 4468 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 { 4469 enum AFD_ONE = (1<<10); 4470 4471 // power basis 4472 immutable float ax = -x1+3*x2-3*x3+x4; 4473 immutable float ay = -y1+3*y2-3*y3+y4; 4474 immutable float bx = 3*x1-6*x2+3*x3; 4475 immutable float by = 3*y1-6*y2+3*y3; 4476 immutable float cx = -3*x1+3*x2; 4477 immutable float cy = -3*y1+3*y2; 4478 4479 // Transform to forward difference basis (stepsize 1) 4480 float px = x1; 4481 float py = y1; 4482 float dx = ax+bx+cx; 4483 float dy = ay+by+cy; 4484 float ddx = 6*ax+2*bx; 4485 float ddy = 6*ay+2*by; 4486 float dddx = 6*ax; 4487 float dddy = 6*ay; 4488 4489 //printf("dx: %f, dy: %f\n", dx, dy); 4490 //printf("ddx: %f, ddy: %f\n", ddx, ddy); 4491 //printf("dddx: %f, dddy: %f\n", dddx, dddy); 4492 4493 int t = 0; 4494 int dt = AFD_ONE; 4495 4496 immutable float tol = ctx.tessTol*4; 4497 4498 while (t < AFD_ONE) { 4499 // Flatness measure. 4500 float d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4501 4502 // printf("d: %f, th: %f\n", d, th); 4503 4504 // Go to higher resolution if we're moving a lot or overshooting the end. 4505 while ((d > tol && dt > 1) || (t+dt > AFD_ONE)) { 4506 // printf("up\n"); 4507 4508 // Apply L to the curve. Increase curve resolution. 4509 dx = 0.5f*dx-(1.0f/8.0f)*ddx+(1.0f/16.0f)*dddx; 4510 dy = 0.5f*dy-(1.0f/8.0f)*ddy+(1.0f/16.0f)*dddy; 4511 ddx = (1.0f/4.0f)*ddx-(1.0f/8.0f)*dddx; 4512 ddy = (1.0f/4.0f)*ddy-(1.0f/8.0f)*dddy; 4513 dddx = (1.0f/8.0f)*dddx; 4514 dddy = (1.0f/8.0f)*dddy; 4515 4516 // Half the stepsize. 4517 dt >>= 1; 4518 4519 // Recompute d 4520 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4521 } 4522 4523 // Go to lower resolution if we're really flat 4524 // and we aren't going to overshoot the end. 4525 // XXX: tol/32 is just a guess for when we are too flat. 4526 while ((d > 0 && d < tol/32.0f && dt < AFD_ONE) && (t+2*dt <= AFD_ONE)) { 4527 // printf("down\n"); 4528 4529 // Apply L^(-1) to the curve. Decrease curve resolution. 4530 dx = 2*dx+ddx; 4531 dy = 2*dy+ddy; 4532 ddx = 4*ddx+4*dddx; 4533 ddy = 4*ddy+4*dddy; 4534 dddx = 8*dddx; 4535 dddy = 8*dddy; 4536 4537 // Double the stepsize. 4538 dt <<= 1; 4539 4540 // Recompute d 4541 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4542 } 4543 4544 // Forward differencing. 4545 px += dx; 4546 py += dy; 4547 dx += ddx; 4548 dy += ddy; 4549 ddx += dddx; 4550 ddy += dddy; 4551 4552 // Output a point. 4553 nvg__addPoint(ctx, px, py, (t > 0 ? type : 0)); 4554 4555 // Advance along the curve. 4556 t += dt; 4557 4558 // Ensure we don't overshoot. 4559 assert(t <= AFD_ONE); 4560 } 4561 } 4562 4563 4564 void nvg__dashLastPath (NVGContext ctx) nothrow @trusted @nogc { 4565 import core.stdc.stdlib : realloc; 4566 import core.stdc.string : memcpy; 4567 4568 NVGpathCache* cache = ctx.cache; 4569 if (cache.npaths == 0) return; 4570 4571 NVGpath* path = nvg__lastPath(ctx); 4572 if (path is null) return; 4573 4574 NVGstate* state = nvg__getState(ctx); 4575 if (!state.dasherActive) return; 4576 4577 static NVGpoint* pts = null; 4578 static uint ptsCount = 0; 4579 static uint ptsSize = 0; 4580 4581 if (path.count < 2) return; // just in case 4582 4583 // copy path points (reserve one point for closed pathes) 4584 if (ptsSize < path.count+1) { 4585 ptsSize = cast(uint)(path.count+1); 4586 pts = cast(NVGpoint*)realloc(pts, ptsSize*NVGpoint.sizeof); 4587 if (pts is null) assert(0, "NanoVega: out of memory"); 4588 } 4589 ptsCount = cast(uint)path.count; 4590 memcpy(pts, &cache.points[path.first], ptsCount*NVGpoint.sizeof); 4591 // add closing point for closed pathes 4592 if (path.closed && !nvg__ptEquals(pts[0].x, pts[0].y, pts[ptsCount-1].x, pts[ptsCount-1].y, ctx.distTol)) { 4593 pts[ptsCount++] = pts[0]; 4594 } 4595 4596 // remove last path (with its points) 4597 --cache.npaths; 4598 cache.npoints -= path.count; 4599 4600 // add stroked pathes 4601 const(float)* dashes = state.dashes.ptr; 4602 immutable uint dashCount = state.dashCount; 4603 float currDashStart = 0; 4604 uint currDashIdx = 0; 4605 immutable bool firstIsGap = state.firstDashIsGap; 4606 4607 // calculate lengthes 4608 { 4609 NVGpoint* v1 = &pts[0]; 4610 NVGpoint* v2 = &pts[1]; 4611 foreach (immutable _; 0..ptsCount) { 4612 float dx = v2.x-v1.x; 4613 float dy = v2.y-v1.y; 4614 v1.len = nvg__normalize(&dx, &dy); 4615 v1 = v2++; 4616 } 4617 } 4618 4619 void calcDashStart (float ds) { 4620 if (ds < 0) { 4621 ds = ds%state.totalDashLen; 4622 while (ds < 0) ds += state.totalDashLen; 4623 } 4624 currDashIdx = 0; 4625 currDashStart = 0; 4626 while (ds > 0) { 4627 if (ds > dashes[currDashIdx]) { 4628 ds -= dashes[currDashIdx]; 4629 ++currDashIdx; 4630 currDashStart = 0; 4631 if (currDashIdx >= dashCount) currDashIdx = 0; 4632 } else { 4633 currDashStart = ds; 4634 ds = 0; 4635 } 4636 } 4637 } 4638 4639 calcDashStart(state.dashStart); 4640 4641 uint srcPointIdx = 1; 4642 const(NVGpoint)* v1 = &pts[0]; 4643 const(NVGpoint)* v2 = &pts[1]; 4644 float currRest = v1.len; 4645 nvg__addPath(ctx); 4646 nvg__addPoint(ctx, v1.x, v1.y, PointFlag.Corner); 4647 4648 void fixLastPoint () { 4649 auto lpt = nvg__lastPath(ctx); 4650 if (lpt !is null && lpt.count > 0) { 4651 // fix last point 4652 if (auto lps = nvg__lastPoint(ctx)) lps.flags = PointFlag.Corner; 4653 // fix first point 4654 NVGpathCache* cache = ctx.cache; 4655 cache.points[lpt.first].flags = PointFlag.Corner; 4656 } 4657 } 4658 4659 for (;;) { 4660 immutable float dlen = dashes[currDashIdx]; 4661 if (dlen == 0) { 4662 ++currDashIdx; 4663 if (currDashIdx >= dashCount) currDashIdx = 0; 4664 continue; 4665 } 4666 immutable float dashRest = dlen-currDashStart; 4667 if ((currDashIdx&1) != firstIsGap) { 4668 // this is "moveto" command, so create new path 4669 fixLastPoint(); 4670 nvg__addPath(ctx); 4671 } 4672 if (currRest > dashRest) { 4673 currRest -= dashRest; 4674 ++currDashIdx; 4675 if (currDashIdx >= dashCount) currDashIdx = 0; 4676 currDashStart = 0; 4677 nvg__addPoint(ctx, 4678 v2.x-(v2.x-v1.x)*currRest/v1.len, 4679 v2.y-(v2.y-v1.y)*currRest/v1.len, 4680 PointFlag.Corner 4681 ); 4682 } else { 4683 currDashStart += currRest; 4684 nvg__addPoint(ctx, v2.x, v2.y, v1.flags); //k8:fix flags here? 4685 ++srcPointIdx; 4686 v1 = v2; 4687 currRest = v1.len; 4688 if (srcPointIdx >= ptsCount) break; 4689 v2 = &pts[srcPointIdx]; 4690 } 4691 } 4692 fixLastPoint(); 4693 } 4694 4695 4696 version(nanovg_bench_flatten) import iv.timer : Timer; 4697 4698 void nvg__flattenPaths(bool asStroke) (NVGContext ctx) nothrow @trusted @nogc { 4699 version(nanovg_bench_flatten) { 4700 Timer timer; 4701 char[128] tmbuf; 4702 int bzcount; 4703 } 4704 NVGpathCache* cache = ctx.cache; 4705 NVGstate* state = nvg__getState(ctx); 4706 4707 // check if we already did flattening 4708 static if (asStroke) { 4709 if (state.dashCount >= 2) { 4710 if (cache.npaths > 0 && state.lastFlattenDashCount == state.dashCount) return; // already flattened 4711 state.dasherActive = true; 4712 state.lastFlattenDashCount = state.dashCount; 4713 } else { 4714 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened 4715 state.dasherActive = false; 4716 state.lastFlattenDashCount = 0; 4717 } 4718 } else { 4719 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened 4720 state.lastFlattenDashCount = 0; // so next stroke flattening will redo it 4721 state.dasherActive = false; 4722 } 4723 4724 // clear path cache 4725 cache.npaths = 0; 4726 cache.npoints = 0; 4727 4728 // flatten 4729 version(nanovg_bench_flatten) timer.restart(); 4730 int i = 0; 4731 while (i < ctx.ncommands) { 4732 final switch (cast(Command)ctx.commands[i]) { 4733 case Command.MoveTo: 4734 //assert(i+3 <= ctx.ncommands); 4735 static if (asStroke) { 4736 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx); 4737 } 4738 nvg__addPath(ctx); 4739 const p = &ctx.commands[i+1]; 4740 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner); 4741 i += 3; 4742 break; 4743 case Command.LineTo: 4744 //assert(i+3 <= ctx.ncommands); 4745 const p = &ctx.commands[i+1]; 4746 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner); 4747 i += 3; 4748 break; 4749 case Command.BezierTo: 4750 //assert(i+7 <= ctx.ncommands); 4751 const last = nvg__lastPoint(ctx); 4752 if (last !is null) { 4753 const cp1 = &ctx.commands[i+1]; 4754 const cp2 = &ctx.commands[i+3]; 4755 const p = &ctx.commands[i+5]; 4756 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) { 4757 nvg__tesselateBezier(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner); 4758 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) { 4759 nvg__tesselateBezierMcSeem(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner); 4760 } else { 4761 nvg__tesselateBezierAFD(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], PointFlag.Corner); 4762 } 4763 version(nanovg_bench_flatten) ++bzcount; 4764 } 4765 i += 7; 4766 break; 4767 case Command.Close: 4768 //assert(i+1 <= ctx.ncommands); 4769 nvg__closePath(ctx); 4770 i += 1; 4771 break; 4772 case Command.Winding: 4773 //assert(i+2 <= ctx.ncommands); 4774 nvg__pathWinding(ctx, cast(NVGWinding)ctx.commands[i+1]); 4775 i += 2; 4776 break; 4777 } 4778 } 4779 static if (asStroke) { 4780 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx); 4781 } 4782 version(nanovg_bench_flatten) {{ 4783 timer.stop(); 4784 auto xb = timer.toBuffer(tmbuf[]); 4785 import core.stdc.stdio : printf; 4786 printf("flattening time: [%.*s] (%d beziers)\n", cast(uint)xb.length, xb.ptr, bzcount); 4787 }} 4788 4789 cache.bounds.ptr[0] = cache.bounds.ptr[1] = float.max; 4790 cache.bounds.ptr[2] = cache.bounds.ptr[3] = -float.max; 4791 4792 // calculate the direction and length of line segments 4793 version(nanovg_bench_flatten) timer.restart(); 4794 foreach (int j; 0..cache.npaths) { 4795 NVGpath* path = &cache.paths[j]; 4796 NVGpoint* pts = &cache.points[path.first]; 4797 4798 // if the first and last points are the same, remove the last, mark as closed path 4799 NVGpoint* p0 = &pts[path.count-1]; 4800 NVGpoint* p1 = &pts[0]; 4801 if (nvg__ptEquals(p0.x, p0.y, p1.x, p1.y, ctx.distTol)) { 4802 --path.count; 4803 p0 = &pts[path.count-1]; 4804 path.closed = true; 4805 } 4806 4807 // enforce winding 4808 if (path.count > 2) { 4809 immutable float area = nvg__polyArea(pts, path.count); 4810 if (path.mWinding == NVGWinding.CCW && area < 0.0f) nvg__polyReverse(pts, path.count); 4811 if (path.mWinding == NVGWinding.CW && area > 0.0f) nvg__polyReverse(pts, path.count); 4812 } 4813 4814 foreach (immutable _; 0..path.count) { 4815 // calculate segment direction and length 4816 p0.dx = p1.x-p0.x; 4817 p0.dy = p1.y-p0.y; 4818 p0.len = nvg__normalize(&p0.dx, &p0.dy); 4819 // update bounds 4820 cache.bounds.ptr[0] = nvg__min(cache.bounds.ptr[0], p0.x); 4821 cache.bounds.ptr[1] = nvg__min(cache.bounds.ptr[1], p0.y); 4822 cache.bounds.ptr[2] = nvg__max(cache.bounds.ptr[2], p0.x); 4823 cache.bounds.ptr[3] = nvg__max(cache.bounds.ptr[3], p0.y); 4824 // advance 4825 p0 = p1++; 4826 } 4827 } 4828 version(nanovg_bench_flatten) {{ 4829 timer.stop(); 4830 auto xb = timer.toBuffer(tmbuf[]); 4831 import core.stdc.stdio : printf; 4832 printf("segment calculation time: [%.*s]\n", cast(uint)xb.length, xb.ptr); 4833 }} 4834 } 4835 4836 int nvg__curveDivs (float r, float arc, float tol) nothrow @trusted @nogc { 4837 immutable float da = nvg__acosf(r/(r+tol))*2.0f; 4838 return nvg__max(2, cast(int)nvg__ceilf(arc/da)); 4839 } 4840 4841 void nvg__chooseBevel (int bevel, NVGpoint* p0, NVGpoint* p1, float w, float* x0, float* y0, float* x1, float* y1) nothrow @trusted @nogc { 4842 if (bevel) { 4843 *x0 = p1.x+p0.dy*w; 4844 *y0 = p1.y-p0.dx*w; 4845 *x1 = p1.x+p1.dy*w; 4846 *y1 = p1.y-p1.dx*w; 4847 } else { 4848 *x0 = p1.x+p1.dmx*w; 4849 *y0 = p1.y+p1.dmy*w; 4850 *x1 = p1.x+p1.dmx*w; 4851 *y1 = p1.y+p1.dmy*w; 4852 } 4853 } 4854 4855 NVGVertex* nvg__roundJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, int ncap, float fringe) nothrow @trusted @nogc { 4856 float dlx0 = p0.dy; 4857 float dly0 = -p0.dx; 4858 float dlx1 = p1.dy; 4859 float dly1 = -p1.dx; 4860 //NVG_NOTUSED(fringe); 4861 4862 if (p1.flags&PointFlag.Left) { 4863 float lx0 = void, ly0 = void, lx1 = void, ly1 = void; 4864 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1); 4865 immutable float a0 = nvg__atan2f(-dly0, -dlx0); 4866 float a1 = nvg__atan2f(-dly1, -dlx1); 4867 if (a1 > a0) a1 -= NVG_PI*2; 4868 4869 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4870 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4871 4872 int n = nvg__clamp(cast(int)nvg__ceilf(((a0-a1)/NVG_PI)*ncap), 2, ncap); 4873 for (int i = 0; i < n; ++i) { 4874 float u = i/cast(float)(n-1); 4875 float a = a0+u*(a1-a0); 4876 float rx = p1.x+nvg__cosf(a)*rw; 4877 float ry = p1.y+nvg__sinf(a)*rw; 4878 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4879 nvg__vset(dst, rx, ry, ru, 1); ++dst; 4880 } 4881 4882 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4883 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4884 4885 } else { 4886 float rx0 = void, ry0 = void, rx1 = void, ry1 = void; 4887 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1); 4888 immutable float a0 = nvg__atan2f(dly0, dlx0); 4889 float a1 = nvg__atan2f(dly1, dlx1); 4890 if (a1 < a0) a1 += NVG_PI*2; 4891 4892 nvg__vset(dst, p1.x+dlx0*rw, p1.y+dly0*rw, lu, 1); ++dst; 4893 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4894 4895 int n = nvg__clamp(cast(int)nvg__ceilf(((a1-a0)/NVG_PI)*ncap), 2, ncap); 4896 for (int i = 0; i < n; i++) { 4897 float u = i/cast(float)(n-1); 4898 float a = a0+u*(a1-a0); 4899 float lx = p1.x+nvg__cosf(a)*lw; 4900 float ly = p1.y+nvg__sinf(a)*lw; 4901 nvg__vset(dst, lx, ly, lu, 1); ++dst; 4902 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4903 } 4904 4905 nvg__vset(dst, p1.x+dlx1*rw, p1.y+dly1*rw, lu, 1); ++dst; 4906 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 4907 4908 } 4909 return dst; 4910 } 4911 4912 NVGVertex* nvg__bevelJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, float fringe) nothrow @trusted @nogc { 4913 float rx0, ry0, rx1, ry1; 4914 float lx0, ly0, lx1, ly1; 4915 float dlx0 = p0.dy; 4916 float dly0 = -p0.dx; 4917 float dlx1 = p1.dy; 4918 float dly1 = -p1.dx; 4919 //NVG_NOTUSED(fringe); 4920 4921 if (p1.flags&PointFlag.Left) { 4922 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1); 4923 4924 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4925 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4926 4927 if (p1.flags&PointFlag.Bevel) { 4928 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4929 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4930 4931 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4932 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4933 } else { 4934 rx0 = p1.x-p1.dmx*rw; 4935 ry0 = p1.y-p1.dmy*rw; 4936 4937 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4938 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4939 4940 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4941 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4942 4943 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4944 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4945 } 4946 4947 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4948 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4949 4950 } else { 4951 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1); 4952 4953 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 4954 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4955 4956 if (p1.flags&PointFlag.Bevel) { 4957 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 4958 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4959 4960 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 4961 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 4962 } else { 4963 lx0 = p1.x+p1.dmx*lw; 4964 ly0 = p1.y+p1.dmy*lw; 4965 4966 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 4967 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4968 4969 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4970 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4971 4972 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 4973 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4974 } 4975 4976 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 4977 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 4978 } 4979 4980 return dst; 4981 } 4982 4983 NVGVertex* nvg__buttCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc { 4984 immutable float px = p.x-dx*d; 4985 immutable float py = p.y-dy*d; 4986 immutable float dlx = dy; 4987 immutable float dly = -dx; 4988 nvg__vset(dst, px+dlx*w-dx*aa, py+dly*w-dy*aa, 0, 0); ++dst; 4989 nvg__vset(dst, px-dlx*w-dx*aa, py-dly*w-dy*aa, 1, 0); ++dst; 4990 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 4991 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 4992 return dst; 4993 } 4994 4995 NVGVertex* nvg__buttCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc { 4996 immutable float px = p.x+dx*d; 4997 immutable float py = p.y+dy*d; 4998 immutable float dlx = dy; 4999 immutable float dly = -dx; 5000 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5001 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5002 nvg__vset(dst, px+dlx*w+dx*aa, py+dly*w+dy*aa, 0, 0); ++dst; 5003 nvg__vset(dst, px-dlx*w+dx*aa, py-dly*w+dy*aa, 1, 0); ++dst; 5004 return dst; 5005 } 5006 5007 NVGVertex* nvg__roundCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc { 5008 immutable float px = p.x; 5009 immutable float py = p.y; 5010 immutable float dlx = dy; 5011 immutable float dly = -dx; 5012 //NVG_NOTUSED(aa); 5013 immutable float ncpf = cast(float)(ncap-1); 5014 foreach (int i; 0..ncap) { 5015 float a = i/*/cast(float)(ncap-1)*//ncpf*NVG_PI; 5016 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w; 5017 nvg__vset(dst, px-dlx*ax-dx*ay, py-dly*ax-dy*ay, 0, 1); ++dst; 5018 nvg__vset(dst, px, py, 0.5f, 1); ++dst; 5019 } 5020 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5021 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5022 return dst; 5023 } 5024 5025 NVGVertex* nvg__roundCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc { 5026 immutable float px = p.x; 5027 immutable float py = p.y; 5028 immutable float dlx = dy; 5029 immutable float dly = -dx; 5030 //NVG_NOTUSED(aa); 5031 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5032 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5033 immutable float ncpf = cast(float)(ncap-1); 5034 foreach (int i; 0..ncap) { 5035 float a = i/*cast(float)(ncap-1)*//ncpf*NVG_PI; 5036 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w; 5037 nvg__vset(dst, px, py, 0.5f, 1); ++dst; 5038 nvg__vset(dst, px-dlx*ax+dx*ay, py-dly*ax+dy*ay, 0, 1); ++dst; 5039 } 5040 return dst; 5041 } 5042 5043 void nvg__calculateJoins (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5044 NVGpathCache* cache = ctx.cache; 5045 float iw = 0.0f; 5046 5047 if (w > 0.0f) iw = 1.0f/w; 5048 5049 // Calculate which joins needs extra vertices to append, and gather vertex count. 5050 foreach (int i; 0..cache.npaths) { 5051 NVGpath* path = &cache.paths[i]; 5052 NVGpoint* pts = &cache.points[path.first]; 5053 NVGpoint* p0 = &pts[path.count-1]; 5054 NVGpoint* p1 = &pts[0]; 5055 int nleft = 0; 5056 5057 path.nbevel = 0; 5058 5059 foreach (int j; 0..path.count) { 5060 //float dlx0, dly0, dlx1, dly1, dmr2, cross, limit; 5061 immutable float dlx0 = p0.dy; 5062 immutable float dly0 = -p0.dx; 5063 immutable float dlx1 = p1.dy; 5064 immutable float dly1 = -p1.dx; 5065 // Calculate extrusions 5066 p1.dmx = (dlx0+dlx1)*0.5f; 5067 p1.dmy = (dly0+dly1)*0.5f; 5068 immutable float dmr2 = p1.dmx*p1.dmx+p1.dmy*p1.dmy; 5069 if (dmr2 > 0.000001f) { 5070 float scale = 1.0f/dmr2; 5071 if (scale > 600.0f) scale = 600.0f; 5072 p1.dmx *= scale; 5073 p1.dmy *= scale; 5074 } 5075 5076 // Clear flags, but keep the corner. 5077 p1.flags = (p1.flags&PointFlag.Corner) ? PointFlag.Corner : 0; 5078 5079 // Keep track of left turns. 5080 immutable float cross = p1.dx*p0.dy-p0.dx*p1.dy; 5081 if (cross > 0.0f) { 5082 nleft++; 5083 p1.flags |= PointFlag.Left; 5084 } 5085 5086 // Calculate if we should use bevel or miter for inner join. 5087 immutable float limit = nvg__max(1.01f, nvg__min(p0.len, p1.len)*iw); 5088 if ((dmr2*limit*limit) < 1.0f) p1.flags |= PointFlag.InnerBevelPR; 5089 5090 // Check to see if the corner needs to be beveled. 5091 if (p1.flags&PointFlag.Corner) { 5092 if ((dmr2*miterLimit*miterLimit) < 1.0f || lineJoin == NVGLineCap.Bevel || lineJoin == NVGLineCap.Round) { 5093 p1.flags |= PointFlag.Bevel; 5094 } 5095 } 5096 5097 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) path.nbevel++; 5098 5099 p0 = p1++; 5100 } 5101 5102 path.convex = (nleft == path.count); 5103 } 5104 } 5105 5106 void nvg__expandStroke (NVGContext ctx, float w, int lineCap, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5107 NVGpathCache* cache = ctx.cache; 5108 immutable float aa = ctx.fringeWidth; 5109 int ncap = nvg__curveDivs(w, NVG_PI, ctx.tessTol); // Calculate divisions per half circle. 5110 5111 nvg__calculateJoins(ctx, w, lineJoin, miterLimit); 5112 5113 // Calculate max vertex usage. 5114 int cverts = 0; 5115 foreach (int i; 0..cache.npaths) { 5116 NVGpath* path = &cache.paths[i]; 5117 immutable bool loop = path.closed; 5118 if (lineJoin == NVGLineCap.Round) { 5119 cverts += (path.count+path.nbevel*(ncap+2)+1)*2; // plus one for loop 5120 } else { 5121 cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop 5122 } 5123 if (!loop) { 5124 // space for caps 5125 if (lineCap == NVGLineCap.Round) { 5126 cverts += (ncap*2+2)*2; 5127 } else { 5128 cverts += (3+3)*2; 5129 } 5130 } 5131 } 5132 5133 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts); 5134 if (verts is null) return; 5135 5136 foreach (int i; 0..cache.npaths) { 5137 NVGpath* path = &cache.paths[i]; 5138 NVGpoint* pts = &cache.points[path.first]; 5139 NVGpoint* p0; 5140 NVGpoint* p1; 5141 int s, e; 5142 5143 path.fill = null; 5144 path.nfill = 0; 5145 5146 // Calculate fringe or stroke 5147 immutable bool loop = path.closed; 5148 NVGVertex* dst = verts; 5149 path.stroke = dst; 5150 5151 if (loop) { 5152 // Looping 5153 p0 = &pts[path.count-1]; 5154 p1 = &pts[0]; 5155 s = 0; 5156 e = path.count; 5157 } else { 5158 // Add cap 5159 p0 = &pts[0]; 5160 p1 = &pts[1]; 5161 s = 1; 5162 e = path.count-1; 5163 } 5164 5165 if (!loop) { 5166 // Add cap 5167 float dx = p1.x-p0.x; 5168 float dy = p1.y-p0.y; 5169 nvg__normalize(&dx, &dy); 5170 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa); 5171 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa); 5172 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa); 5173 } 5174 5175 foreach (int j; s..e) { 5176 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) { 5177 if (lineJoin == NVGLineCap.Round) { 5178 dst = nvg__roundJoin(dst, p0, p1, w, w, 0, 1, ncap, aa); 5179 } else { 5180 dst = nvg__bevelJoin(dst, p0, p1, w, w, 0, 1, aa); 5181 } 5182 } else { 5183 nvg__vset(dst, p1.x+(p1.dmx*w), p1.y+(p1.dmy*w), 0, 1); ++dst; 5184 nvg__vset(dst, p1.x-(p1.dmx*w), p1.y-(p1.dmy*w), 1, 1); ++dst; 5185 } 5186 p0 = p1++; 5187 } 5188 5189 if (loop) { 5190 // Loop it 5191 nvg__vset(dst, verts[0].x, verts[0].y, 0, 1); ++dst; 5192 nvg__vset(dst, verts[1].x, verts[1].y, 1, 1); ++dst; 5193 } else { 5194 // Add cap 5195 float dx = p1.x-p0.x; 5196 float dy = p1.y-p0.y; 5197 nvg__normalize(&dx, &dy); 5198 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa); 5199 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa); 5200 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa); 5201 } 5202 5203 path.nstroke = cast(int)(dst-verts); 5204 5205 verts = dst; 5206 } 5207 } 5208 5209 void nvg__expandFill (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5210 NVGpathCache* cache = ctx.cache; 5211 immutable float aa = ctx.fringeWidth; 5212 bool fringe = (w > 0.0f); 5213 5214 nvg__calculateJoins(ctx, w, lineJoin, miterLimit); 5215 5216 // Calculate max vertex usage. 5217 int cverts = 0; 5218 foreach (int i; 0..cache.npaths) { 5219 NVGpath* path = &cache.paths[i]; 5220 cverts += path.count+path.nbevel+1; 5221 if (fringe) cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop 5222 } 5223 5224 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts); 5225 if (verts is null) return; 5226 5227 bool convex = (cache.npaths == 1 && cache.paths[0].convex); 5228 5229 foreach (int i; 0..cache.npaths) { 5230 NVGpath* path = &cache.paths[i]; 5231 NVGpoint* pts = &cache.points[path.first]; 5232 5233 // Calculate shape vertices. 5234 immutable float woff = 0.5f*aa; 5235 NVGVertex* dst = verts; 5236 path.fill = dst; 5237 5238 if (fringe) { 5239 // Looping 5240 NVGpoint* p0 = &pts[path.count-1]; 5241 NVGpoint* p1 = &pts[0]; 5242 foreach (int j; 0..path.count) { 5243 if (p1.flags&PointFlag.Bevel) { 5244 immutable float dlx0 = p0.dy; 5245 immutable float dly0 = -p0.dx; 5246 immutable float dlx1 = p1.dy; 5247 immutable float dly1 = -p1.dx; 5248 if (p1.flags&PointFlag.Left) { 5249 immutable float lx = p1.x+p1.dmx*woff; 5250 immutable float ly = p1.y+p1.dmy*woff; 5251 nvg__vset(dst, lx, ly, 0.5f, 1); ++dst; 5252 } else { 5253 immutable float lx0 = p1.x+dlx0*woff; 5254 immutable float ly0 = p1.y+dly0*woff; 5255 immutable float lx1 = p1.x+dlx1*woff; 5256 immutable float ly1 = p1.y+dly1*woff; 5257 nvg__vset(dst, lx0, ly0, 0.5f, 1); ++dst; 5258 nvg__vset(dst, lx1, ly1, 0.5f, 1); ++dst; 5259 } 5260 } else { 5261 nvg__vset(dst, p1.x+(p1.dmx*woff), p1.y+(p1.dmy*woff), 0.5f, 1); ++dst; 5262 } 5263 p0 = p1++; 5264 } 5265 } else { 5266 foreach (int j; 0..path.count) { 5267 nvg__vset(dst, pts[j].x, pts[j].y, 0.5f, 1); 5268 ++dst; 5269 } 5270 } 5271 5272 path.nfill = cast(int)(dst-verts); 5273 verts = dst; 5274 5275 // Calculate fringe 5276 if (fringe) { 5277 float lw = w+woff; 5278 immutable float rw = w-woff; 5279 float lu = 0; 5280 immutable float ru = 1; 5281 dst = verts; 5282 path.stroke = dst; 5283 5284 // Create only half a fringe for convex shapes so that 5285 // the shape can be rendered without stenciling. 5286 if (convex) { 5287 lw = woff; // This should generate the same vertex as fill inset above. 5288 lu = 0.5f; // Set outline fade at middle. 5289 } 5290 5291 // Looping 5292 NVGpoint* p0 = &pts[path.count-1]; 5293 NVGpoint* p1 = &pts[0]; 5294 5295 foreach (int j; 0..path.count) { 5296 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) { 5297 dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx.fringeWidth); 5298 } else { 5299 nvg__vset(dst, p1.x+(p1.dmx*lw), p1.y+(p1.dmy*lw), lu, 1); ++dst; 5300 nvg__vset(dst, p1.x-(p1.dmx*rw), p1.y-(p1.dmy*rw), ru, 1); ++dst; 5301 } 5302 p0 = p1++; 5303 } 5304 5305 // Loop it 5306 nvg__vset(dst, verts[0].x, verts[0].y, lu, 1); ++dst; 5307 nvg__vset(dst, verts[1].x, verts[1].y, ru, 1); ++dst; 5308 5309 path.nstroke = cast(int)(dst-verts); 5310 verts = dst; 5311 } else { 5312 path.stroke = null; 5313 path.nstroke = 0; 5314 } 5315 } 5316 } 5317 5318 5319 // ////////////////////////////////////////////////////////////////////////// // 5320 // Paths 5321 5322 /// Clears the current path and sub-paths. 5323 /// Group: paths 5324 @scriptable 5325 public void beginPath (NVGContext ctx) nothrow @trusted @nogc { 5326 ctx.ncommands = 0; 5327 ctx.pathPickRegistered &= NVGPickKind.All; // reset "registered" flags 5328 nvg__clearPathCache(ctx); 5329 } 5330 5331 public alias newPath = beginPath; /// Ditto. 5332 5333 /// Starts new sub-path with specified point as first point. 5334 /// Group: paths 5335 @scriptable 5336 public void moveTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 5337 nvg__appendCommands(ctx, Command.MoveTo, x, y); 5338 } 5339 5340 /// Starts new sub-path with specified point as first point. 5341 /// Arguments: [x, y]* 5342 /// Group: paths 5343 public void moveTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5344 enum ArgC = 2; 5345 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [moveTo] call"); 5346 if (args.length < ArgC) return; 5347 nvg__appendCommands(ctx, Command.MoveTo, args[$-2..$]); 5348 } 5349 5350 /// Adds line segment from the last point in the path to the specified point. 5351 /// Group: paths 5352 @scriptable 5353 public void lineTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 5354 nvg__appendCommands(ctx, Command.LineTo, x, y); 5355 } 5356 5357 /// Adds line segment from the last point in the path to the specified point. 5358 /// Arguments: [x, y]* 5359 /// Group: paths 5360 public void lineTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5361 enum ArgC = 2; 5362 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [lineTo] call"); 5363 if (args.length < ArgC) return; 5364 foreach (immutable idx; 0..args.length/ArgC) { 5365 nvg__appendCommands(ctx, Command.LineTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]); 5366 } 5367 } 5368 5369 /// Adds cubic bezier segment from last point in the path via two control points to the specified point. 5370 /// Group: paths 5371 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 { 5372 nvg__appendCommands(ctx, Command.BezierTo, c1x, c1y, c2x, c2y, x, y); 5373 } 5374 5375 /// Adds cubic bezier segment from last point in the path via two control points to the specified point. 5376 /// Arguments: [c1x, c1y, c2x, c2y, x, y]* 5377 /// Group: paths 5378 public void bezierTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5379 enum ArgC = 6; 5380 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [bezierTo] call"); 5381 if (args.length < ArgC) return; 5382 foreach (immutable idx; 0..args.length/ArgC) { 5383 nvg__appendCommands(ctx, Command.BezierTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]); 5384 } 5385 } 5386 5387 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point. 5388 /// Group: paths 5389 public void quadTo (NVGContext ctx, in float cx, in float cy, in float x, in float y) nothrow @trusted @nogc { 5390 immutable float x0 = ctx.commandx; 5391 immutable float y0 = ctx.commandy; 5392 nvg__appendCommands(ctx, 5393 Command.BezierTo, 5394 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 5395 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 5396 x, y, 5397 ); 5398 } 5399 5400 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point. 5401 /// Arguments: [cx, cy, x, y]* 5402 /// Group: paths 5403 public void quadTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5404 enum ArgC = 4; 5405 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [quadTo] call"); 5406 if (args.length < ArgC) return; 5407 const(float)* aptr = args.ptr; 5408 foreach (immutable idx; 0..args.length/ArgC) { 5409 immutable float x0 = ctx.commandx; 5410 immutable float y0 = ctx.commandy; 5411 immutable float cx = *aptr++; 5412 immutable float cy = *aptr++; 5413 immutable float x = *aptr++; 5414 immutable float y = *aptr++; 5415 nvg__appendCommands(ctx, 5416 Command.BezierTo, 5417 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 5418 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 5419 x, y, 5420 ); 5421 } 5422 } 5423 5424 /// Adds an arc segment at the corner defined by the last path point, and two specified points. 5425 /// Group: paths 5426 public void arcTo (NVGContext ctx, in float x1, in float y1, in float x2, in float y2, in float radius) nothrow @trusted @nogc { 5427 if (ctx.ncommands == 0) return; 5428 5429 immutable float x0 = ctx.commandx; 5430 immutable float y0 = ctx.commandy; 5431 5432 // handle degenerate cases 5433 if (nvg__ptEquals(x0, y0, x1, y1, ctx.distTol) || 5434 nvg__ptEquals(x1, y1, x2, y2, ctx.distTol) || 5435 nvg__distPtSeg(x1, y1, x0, y0, x2, y2) < ctx.distTol*ctx.distTol || 5436 radius < ctx.distTol) 5437 { 5438 ctx.lineTo(x1, y1); 5439 return; 5440 } 5441 5442 // calculate tangential circle to lines (x0, y0)-(x1, y1) and (x1, y1)-(x2, y2) 5443 float dx0 = x0-x1; 5444 float dy0 = y0-y1; 5445 float dx1 = x2-x1; 5446 float dy1 = y2-y1; 5447 nvg__normalize(&dx0, &dy0); 5448 nvg__normalize(&dx1, &dy1); 5449 immutable float a = nvg__acosf(dx0*dx1+dy0*dy1); 5450 immutable float d = radius/nvg__tanf(a/2.0f); 5451 5452 //printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d); 5453 5454 if (d > 10000.0f) { 5455 ctx.lineTo(x1, y1); 5456 return; 5457 } 5458 5459 float cx = void, cy = void, a0 = void, a1 = void; 5460 NVGWinding dir; 5461 if (nvg__cross(dx0, dy0, dx1, dy1) > 0.0f) { 5462 cx = x1+dx0*d+dy0*radius; 5463 cy = y1+dy0*d+-dx0*radius; 5464 a0 = nvg__atan2f(dx0, -dy0); 5465 a1 = nvg__atan2f(-dx1, dy1); 5466 dir = NVGWinding.CW; 5467 //printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); 5468 } else { 5469 cx = x1+dx0*d+-dy0*radius; 5470 cy = y1+dy0*d+dx0*radius; 5471 a0 = nvg__atan2f(-dx0, dy0); 5472 a1 = nvg__atan2f(dx1, -dy1); 5473 dir = NVGWinding.CCW; 5474 //printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); 5475 } 5476 5477 ctx.arc(dir, cx, cy, radius, a0, a1); // first is line 5478 } 5479 5480 5481 /// Adds an arc segment at the corner defined by the last path point, and two specified points. 5482 /// Arguments: [x1, y1, x2, y2, radius]* 5483 /// Group: paths 5484 public void arcTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5485 enum ArgC = 5; 5486 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arcTo] call"); 5487 if (args.length < ArgC) return; 5488 if (ctx.ncommands == 0) return; 5489 const(float)* aptr = args.ptr; 5490 foreach (immutable idx; 0..args.length/ArgC) { 5491 immutable float x0 = ctx.commandx; 5492 immutable float y0 = ctx.commandy; 5493 immutable float x1 = *aptr++; 5494 immutable float y1 = *aptr++; 5495 immutable float x2 = *aptr++; 5496 immutable float y2 = *aptr++; 5497 immutable float radius = *aptr++; 5498 ctx.arcTo(x1, y1, x2, y2, radius); 5499 } 5500 } 5501 5502 /// Closes current sub-path with a line segment. 5503 /// Group: paths 5504 @scriptable 5505 public void closePath (NVGContext ctx) nothrow @trusted @nogc { 5506 nvg__appendCommands(ctx, Command.Close); 5507 } 5508 5509 /// Sets the current sub-path winding, see NVGWinding and NVGSolidity. 5510 /// Group: paths 5511 public void pathWinding (NVGContext ctx, NVGWinding dir) nothrow @trusted @nogc { 5512 nvg__appendCommands(ctx, Command.Winding, cast(float)dir); 5513 } 5514 5515 /// Ditto. 5516 public void pathWinding (NVGContext ctx, NVGSolidity dir) nothrow @trusted @nogc { 5517 nvg__appendCommands(ctx, Command.Winding, cast(float)dir); 5518 } 5519 5520 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r, 5521 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW). 5522 * Angles are specified in radians. 5523 * 5524 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo 5525 * 5526 * Group: paths 5527 */ 5528 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 { 5529 static assert(mode == "original" || mode == "move" || mode == "line"); 5530 5531 float[3+5*7+100] vals = void; 5532 //int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo); 5533 static if (mode == "original") { 5534 immutable int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo); 5535 } else static if (mode == "move") { 5536 enum move = Command.MoveTo; 5537 } else static if (mode == "line") { 5538 enum move = Command.LineTo; 5539 } else { 5540 static assert(0, "wtf?!"); 5541 } 5542 5543 // Clamp angles 5544 float da = a1-a0; 5545 if (dir == NVGWinding.CW) { 5546 if (nvg__absf(da) >= NVG_PI*2) { 5547 da = NVG_PI*2; 5548 } else { 5549 while (da < 0.0f) da += NVG_PI*2; 5550 } 5551 } else { 5552 if (nvg__absf(da) >= NVG_PI*2) { 5553 da = -NVG_PI*2; 5554 } else { 5555 while (da > 0.0f) da -= NVG_PI*2; 5556 } 5557 } 5558 5559 // Split arc into max 90 degree segments. 5560 immutable int ndivs = nvg__max(1, nvg__min(cast(int)(nvg__absf(da)/(NVG_PI*0.5f)+0.5f), 5)); 5561 immutable float hda = (da/cast(float)ndivs)/2.0f; 5562 float kappa = nvg__absf(4.0f/3.0f*(1.0f-nvg__cosf(hda))/nvg__sinf(hda)); 5563 5564 if (dir == NVGWinding.CCW) kappa = -kappa; 5565 5566 int nvals = 0; 5567 float px = 0, py = 0, ptanx = 0, ptany = 0; 5568 foreach (int i; 0..ndivs+1) { 5569 immutable float a = a0+da*(i/cast(float)ndivs); 5570 immutable float dx = nvg__cosf(a); 5571 immutable float dy = nvg__sinf(a); 5572 immutable float x = cx+dx*r; 5573 immutable float y = cy+dy*r; 5574 immutable float tanx = -dy*r*kappa; 5575 immutable float tany = dx*r*kappa; 5576 5577 if (i == 0) { 5578 if (vals.length-nvals < 3) { 5579 // flush 5580 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5581 nvals = 0; 5582 } 5583 vals.ptr[nvals++] = cast(float)move; 5584 vals.ptr[nvals++] = x; 5585 vals.ptr[nvals++] = y; 5586 } else { 5587 if (vals.length-nvals < 7) { 5588 // flush 5589 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5590 nvals = 0; 5591 } 5592 vals.ptr[nvals++] = Command.BezierTo; 5593 vals.ptr[nvals++] = px+ptanx; 5594 vals.ptr[nvals++] = py+ptany; 5595 vals.ptr[nvals++] = x-tanx; 5596 vals.ptr[nvals++] = y-tany; 5597 vals.ptr[nvals++] = x; 5598 vals.ptr[nvals++] = y; 5599 } 5600 px = x; 5601 py = y; 5602 ptanx = tanx; 5603 ptany = tany; 5604 } 5605 5606 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5607 } 5608 5609 5610 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r, 5611 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW). 5612 * Angles are specified in radians. 5613 * 5614 * Arguments: [cx, cy, r, a0, a1]* 5615 * 5616 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo 5617 * 5618 * Group: paths 5619 */ 5620 public void arc(string mode="original") (NVGContext ctx, NVGWinding dir, in float[] args) nothrow @trusted @nogc { 5621 static assert(mode == "original" || mode == "move" || mode == "line"); 5622 enum ArgC = 5; 5623 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arc] call"); 5624 if (args.length < ArgC) return; 5625 const(float)* aptr = args.ptr; 5626 foreach (immutable idx; 0..args.length/ArgC) { 5627 immutable cx = *aptr++; 5628 immutable cy = *aptr++; 5629 immutable r = *aptr++; 5630 immutable a0 = *aptr++; 5631 immutable a1 = *aptr++; 5632 ctx.arc!mode(dir, cx, cy, r, a0, a1); 5633 } 5634 } 5635 5636 /// Creates new rectangle shaped sub-path. 5637 /// Group: paths 5638 @scriptable 5639 public void rect (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc { 5640 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5641 Command.MoveTo, x, y, 5642 Command.LineTo, x, y+h, 5643 Command.LineTo, x+w, y+h, 5644 Command.LineTo, x+w, y, 5645 Command.Close, 5646 ); 5647 } 5648 5649 /// Creates new rectangle shaped sub-path. 5650 /// Arguments: [x, y, w, h]* 5651 /// Group: paths 5652 public void rect (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5653 enum ArgC = 4; 5654 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [rect] call"); 5655 if (args.length < ArgC) return; 5656 const(float)* aptr = args.ptr; 5657 foreach (immutable idx; 0..args.length/ArgC) { 5658 immutable x = *aptr++; 5659 immutable y = *aptr++; 5660 immutable w = *aptr++; 5661 immutable h = *aptr++; 5662 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5663 Command.MoveTo, x, y, 5664 Command.LineTo, x, y+h, 5665 Command.LineTo, x+w, y+h, 5666 Command.LineTo, x+w, y, 5667 Command.Close, 5668 ); 5669 } 5670 } 5671 5672 /// Creates new rounded rectangle shaped sub-path. 5673 /// Group: paths 5674 @scriptable 5675 public void roundedRect (NVGContext ctx, in float x, in float y, in float w, in float h, in float radius) nothrow @trusted @nogc { 5676 ctx.roundedRectVarying(x, y, w, h, radius, radius, radius, radius); 5677 } 5678 5679 /// Creates new rounded rectangle shaped sub-path. 5680 /// Arguments: [x, y, w, h, radius]* 5681 /// Group: paths 5682 public void roundedRect (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5683 enum ArgC = 5; 5684 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRect] call"); 5685 if (args.length < ArgC) return; 5686 const(float)* aptr = args.ptr; 5687 foreach (immutable idx; 0..args.length/ArgC) { 5688 immutable x = *aptr++; 5689 immutable y = *aptr++; 5690 immutable w = *aptr++; 5691 immutable h = *aptr++; 5692 immutable r = *aptr++; 5693 ctx.roundedRectVarying(x, y, w, h, r, r, r, r); 5694 } 5695 } 5696 5697 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it. 5698 /// Group: paths 5699 @scriptable 5700 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 { 5701 if (rw < 0.1f || rh < 0.1f) { 5702 rect(ctx, x, y, w, h); 5703 } else { 5704 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5705 Command.MoveTo, x+rw, y, 5706 Command.LineTo, x+w-rw, y, 5707 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh, 5708 Command.LineTo, x+w, y+h-rh, 5709 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h, 5710 Command.LineTo, x+rw, y+h, 5711 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh, 5712 Command.LineTo, x, y+rh, 5713 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y, 5714 Command.Close, 5715 ); 5716 } 5717 } 5718 5719 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it. 5720 /// Arguments: [x, y, w, h, rw, rh]* 5721 /// Group: paths 5722 public void roundedRectEllipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5723 enum ArgC = 6; 5724 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectEllipse] call"); 5725 if (args.length < ArgC) return; 5726 const(float)* aptr = args.ptr; 5727 foreach (immutable idx; 0..args.length/ArgC) { 5728 immutable x = *aptr++; 5729 immutable y = *aptr++; 5730 immutable w = *aptr++; 5731 immutable h = *aptr++; 5732 immutable rw = *aptr++; 5733 immutable rh = *aptr++; 5734 if (rw < 0.1f || rh < 0.1f) { 5735 rect(ctx, x, y, w, h); 5736 } else { 5737 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5738 Command.MoveTo, x+rw, y, 5739 Command.LineTo, x+w-rw, y, 5740 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh, 5741 Command.LineTo, x+w, y+h-rh, 5742 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h, 5743 Command.LineTo, x+rw, y+h, 5744 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh, 5745 Command.LineTo, x, y+rh, 5746 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y, 5747 Command.Close, 5748 ); 5749 } 5750 } 5751 } 5752 5753 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner. 5754 /// Group: paths 5755 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 { 5756 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { 5757 ctx.rect(x, y, w, h); 5758 } else { 5759 immutable float halfw = nvg__absf(w)*0.5f; 5760 immutable float halfh = nvg__absf(h)*0.5f; 5761 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h); 5762 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h); 5763 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h); 5764 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h); 5765 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5766 Command.MoveTo, x, y+ryTL, 5767 Command.LineTo, x, y+h-ryBL, 5768 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h, 5769 Command.LineTo, x+w-rxBR, y+h, 5770 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR, 5771 Command.LineTo, x+w, y+ryTR, 5772 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y, 5773 Command.LineTo, x+rxTL, y, 5774 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL, 5775 Command.Close, 5776 ); 5777 } 5778 } 5779 5780 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner. 5781 /// Arguments: [x, y, w, h, radTopLeft, radTopRight, radBottomRight, radBottomLeft]* 5782 /// Group: paths 5783 public void roundedRectVarying (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5784 enum ArgC = 8; 5785 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectVarying] call"); 5786 if (args.length < ArgC) return; 5787 const(float)* aptr = args.ptr; 5788 foreach (immutable idx; 0..args.length/ArgC) { 5789 immutable x = *aptr++; 5790 immutable y = *aptr++; 5791 immutable w = *aptr++; 5792 immutable h = *aptr++; 5793 immutable radTopLeft = *aptr++; 5794 immutable radTopRight = *aptr++; 5795 immutable radBottomRight = *aptr++; 5796 immutable radBottomLeft = *aptr++; 5797 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { 5798 ctx.rect(x, y, w, h); 5799 } else { 5800 immutable float halfw = nvg__absf(w)*0.5f; 5801 immutable float halfh = nvg__absf(h)*0.5f; 5802 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h); 5803 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h); 5804 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h); 5805 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h); 5806 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5807 Command.MoveTo, x, y+ryTL, 5808 Command.LineTo, x, y+h-ryBL, 5809 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h, 5810 Command.LineTo, x+w-rxBR, y+h, 5811 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR, 5812 Command.LineTo, x+w, y+ryTR, 5813 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y, 5814 Command.LineTo, x+rxTL, y, 5815 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL, 5816 Command.Close, 5817 ); 5818 } 5819 } 5820 } 5821 5822 /// Creates new ellipse shaped sub-path. 5823 /// Group: paths 5824 public void ellipse (NVGContext ctx, in float cx, in float cy, in float rx, in float ry) nothrow @trusted @nogc { 5825 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5826 Command.MoveTo, cx-rx, cy, 5827 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry, 5828 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy, 5829 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry, 5830 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy, 5831 Command.Close, 5832 ); 5833 } 5834 5835 /// Creates new ellipse shaped sub-path. 5836 /// Arguments: [cx, cy, rx, ry]* 5837 /// Group: paths 5838 public void ellipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5839 enum ArgC = 4; 5840 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [ellipse] call"); 5841 if (args.length < ArgC) return; 5842 const(float)* aptr = args.ptr; 5843 foreach (immutable idx; 0..args.length/ArgC) { 5844 immutable cx = *aptr++; 5845 immutable cy = *aptr++; 5846 immutable rx = *aptr++; 5847 immutable ry = *aptr++; 5848 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5849 Command.MoveTo, cx-rx, cy, 5850 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry, 5851 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy, 5852 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry, 5853 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy, 5854 Command.Close, 5855 ); 5856 } 5857 } 5858 5859 /// Creates new circle shaped sub-path. 5860 /// Group: paths 5861 public void circle (NVGContext ctx, in float cx, in float cy, in float r) nothrow @trusted @nogc { 5862 ctx.ellipse(cx, cy, r, r); 5863 } 5864 5865 /// Creates new circle shaped sub-path. 5866 /// Arguments: [cx, cy, r]* 5867 /// Group: paths 5868 public void circle (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5869 enum ArgC = 3; 5870 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [circle] call"); 5871 if (args.length < ArgC) return; 5872 const(float)* aptr = args.ptr; 5873 foreach (immutable idx; 0..args.length/ArgC) { 5874 immutable cx = *aptr++; 5875 immutable cy = *aptr++; 5876 immutable r = *aptr++; 5877 ctx.ellipse(cx, cy, r, r); 5878 } 5879 } 5880 5881 // Debug function to dump cached path data. 5882 debug public void debugDumpPathCache (NVGContext ctx) nothrow @trusted @nogc { 5883 import core.stdc.stdio : printf; 5884 const(NVGpath)* path; 5885 printf("Dumping %d cached paths\n", ctx.cache.npaths); 5886 for (int i = 0; i < ctx.cache.npaths; ++i) { 5887 path = &ctx.cache.paths[i]; 5888 printf("-Path %d\n", i); 5889 if (path.nfill) { 5890 printf("-fill: %d\n", path.nfill); 5891 for (int j = 0; j < path.nfill; ++j) printf("%f\t%f\n", path.fill[j].x, path.fill[j].y); 5892 } 5893 if (path.nstroke) { 5894 printf("-stroke: %d\n", path.nstroke); 5895 for (int j = 0; j < path.nstroke; ++j) printf("%f\t%f\n", path.stroke[j].x, path.stroke[j].y); 5896 } 5897 } 5898 } 5899 5900 // Flatten path, prepare it for fill operation. 5901 void nvg__prepareFill (NVGContext ctx) nothrow @trusted @nogc { 5902 NVGpathCache* cache = ctx.cache; 5903 NVGstate* state = nvg__getState(ctx); 5904 5905 nvg__flattenPaths!false(ctx); 5906 5907 if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) { 5908 nvg__expandFill(ctx, ctx.fringeWidth, NVGLineCap.Miter, 2.4f); 5909 } else { 5910 nvg__expandFill(ctx, 0.0f, NVGLineCap.Miter, 2.4f); 5911 } 5912 5913 cache.evenOddMode = state.evenOddMode; 5914 cache.fringeWidth = ctx.fringeWidth; 5915 cache.fillReady = true; 5916 cache.strokeReady = false; 5917 cache.clipmode = NVGClipMode.None; 5918 } 5919 5920 // Flatten path, prepare it for stroke operation. 5921 void nvg__prepareStroke (NVGContext ctx) nothrow @trusted @nogc { 5922 NVGstate* state = nvg__getState(ctx); 5923 NVGpathCache* cache = ctx.cache; 5924 5925 nvg__flattenPaths!true(ctx); 5926 5927 immutable float scale = nvg__getAverageScale(state.xform); 5928 float strokeWidth = nvg__clamp(state.strokeWidth*scale, 0.0f, 200.0f); 5929 5930 if (strokeWidth < ctx.fringeWidth) { 5931 // If the stroke width is less than pixel size, use alpha to emulate coverage. 5932 // Since coverage is area, scale by alpha*alpha. 5933 immutable float alpha = nvg__clamp(strokeWidth/ctx.fringeWidth, 0.0f, 1.0f); 5934 cache.strokeAlphaMul = alpha*alpha; 5935 strokeWidth = ctx.fringeWidth; 5936 } else { 5937 cache.strokeAlphaMul = 1.0f; 5938 } 5939 cache.strokeWidth = strokeWidth; 5940 5941 if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) { 5942 nvg__expandStroke(ctx, strokeWidth*0.5f+ctx.fringeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit); 5943 } else { 5944 nvg__expandStroke(ctx, strokeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit); 5945 } 5946 5947 cache.fringeWidth = ctx.fringeWidth; 5948 cache.fillReady = false; 5949 cache.strokeReady = true; 5950 cache.clipmode = NVGClipMode.None; 5951 } 5952 5953 /// Fills the current path with current fill style. 5954 /// Group: paths 5955 @scriptable 5956 public void fill (NVGContext ctx) nothrow @trusted @nogc { 5957 NVGstate* state = nvg__getState(ctx); 5958 5959 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) { 5960 ctx.pathPickRegistered |= NVGPickKind.Fill<<16; 5961 ctx.currFillHitId = ctx.pathPickId; 5962 } 5963 5964 nvg__prepareFill(ctx); 5965 5966 // apply global alpha 5967 NVGPaint fillPaint = state.fill; 5968 fillPaint.innerColor.a *= state.alpha; 5969 fillPaint.middleColor.a *= state.alpha; 5970 fillPaint.outerColor.a *= state.alpha; 5971 5972 ctx.appendCurrentPathToCache(ctx.recset, state.fill); 5973 5974 if (ctx.recblockdraw) return; 5975 5976 ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &fillPaint, &state.scissor, ctx.fringeWidth, ctx.cache.bounds.ptr, ctx.cache.paths, ctx.cache.npaths, state.evenOddMode); 5977 5978 // count triangles 5979 foreach (int i; 0..ctx.cache.npaths) { 5980 NVGpath* path = &ctx.cache.paths[i]; 5981 ctx.fillTriCount += path.nfill-2; 5982 ctx.fillTriCount += path.nstroke-2; 5983 ctx.drawCallCount += 2; 5984 } 5985 } 5986 5987 /// Fills the current path with current stroke style. 5988 /// Group: paths 5989 @scriptable 5990 public void stroke (NVGContext ctx) nothrow @trusted @nogc { 5991 NVGstate* state = nvg__getState(ctx); 5992 5993 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) { 5994 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16; 5995 ctx.currStrokeHitId = ctx.pathPickId; 5996 } 5997 5998 nvg__prepareStroke(ctx); 5999 6000 NVGpathCache* cache = ctx.cache; 6001 6002 NVGPaint strokePaint = state.stroke; 6003 strokePaint.innerColor.a *= cache.strokeAlphaMul; 6004 strokePaint.middleColor.a *= cache.strokeAlphaMul; 6005 strokePaint.outerColor.a *= cache.strokeAlphaMul; 6006 6007 // apply global alpha 6008 strokePaint.innerColor.a *= state.alpha; 6009 strokePaint.middleColor.a *= state.alpha; 6010 strokePaint.outerColor.a *= state.alpha; 6011 6012 ctx.appendCurrentPathToCache(ctx.recset, state.stroke); 6013 6014 if (ctx.recblockdraw) return; 6015 6016 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths); 6017 6018 // count triangles 6019 foreach (int i; 0..ctx.cache.npaths) { 6020 NVGpath* path = &ctx.cache.paths[i]; 6021 ctx.strokeTriCount += path.nstroke-2; 6022 ++ctx.drawCallCount; 6023 } 6024 } 6025 6026 /// Sets current path as clipping region. 6027 /// Group: clipping 6028 public void clip (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc { 6029 NVGstate* state = nvg__getState(ctx); 6030 6031 if (aclipmode == NVGClipMode.None) return; 6032 if (ctx.recblockdraw) return; //??? 6033 6034 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr); 6035 6036 /* 6037 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) { 6038 ctx.pathPickRegistered |= NVGPickKind.Fill<<16; 6039 ctx.currFillHitId = ctx.pathPickId; 6040 } 6041 */ 6042 6043 nvg__prepareFill(ctx); 6044 6045 // apply global alpha 6046 NVGPaint fillPaint = state.fill; 6047 fillPaint.innerColor.a *= state.alpha; 6048 fillPaint.middleColor.a *= state.alpha; 6049 fillPaint.outerColor.a *= state.alpha; 6050 6051 //ctx.appendCurrentPathToCache(ctx.recset, state.fill); 6052 6053 ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, aclipmode, &fillPaint, &state.scissor, ctx.fringeWidth, ctx.cache.bounds.ptr, ctx.cache.paths, ctx.cache.npaths, state.evenOddMode); 6054 6055 // count triangles 6056 foreach (int i; 0..ctx.cache.npaths) { 6057 NVGpath* path = &ctx.cache.paths[i]; 6058 ctx.fillTriCount += path.nfill-2; 6059 ctx.fillTriCount += path.nstroke-2; 6060 ctx.drawCallCount += 2; 6061 } 6062 } 6063 6064 /// Sets current path as clipping region. 6065 /// Group: clipping 6066 public alias clipFill = clip; 6067 6068 /// Sets current path' stroke as clipping region. 6069 /// Group: clipping 6070 public void clipStroke (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc { 6071 NVGstate* state = nvg__getState(ctx); 6072 6073 if (aclipmode == NVGClipMode.None) return; 6074 if (ctx.recblockdraw) return; //??? 6075 6076 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr); 6077 6078 /* 6079 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) { 6080 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16; 6081 ctx.currStrokeHitId = ctx.pathPickId; 6082 } 6083 */ 6084 6085 nvg__prepareStroke(ctx); 6086 6087 NVGpathCache* cache = ctx.cache; 6088 6089 NVGPaint strokePaint = state.stroke; 6090 strokePaint.innerColor.a *= cache.strokeAlphaMul; 6091 strokePaint.middleColor.a *= cache.strokeAlphaMul; 6092 strokePaint.outerColor.a *= cache.strokeAlphaMul; 6093 6094 // apply global alpha 6095 strokePaint.innerColor.a *= state.alpha; 6096 strokePaint.middleColor.a *= state.alpha; 6097 strokePaint.outerColor.a *= state.alpha; 6098 6099 //ctx.appendCurrentPathToCache(ctx.recset, state.stroke); 6100 6101 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, aclipmode, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths); 6102 6103 // count triangles 6104 foreach (int i; 0..ctx.cache.npaths) { 6105 NVGpath* path = &ctx.cache.paths[i]; 6106 ctx.strokeTriCount += path.nstroke-2; 6107 ++ctx.drawCallCount; 6108 } 6109 } 6110 6111 6112 // ////////////////////////////////////////////////////////////////////////// // 6113 // Picking API 6114 6115 // most of the code is by Michael Wynne <mike@mikesspace.net> 6116 // https://github.com/memononen/nanovg/pull/230 6117 // https://github.com/MikeWW/nanovg 6118 6119 /// Pick type query. Used in [hitTest] and [hitTestAll]. 6120 /// Group: picking_api 6121 public enum NVGPickKind : ubyte { 6122 Fill = 0x01, /// 6123 Stroke = 0x02, /// 6124 All = 0x03, /// 6125 } 6126 6127 /// Marks the fill of the current path as pickable with the specified id. 6128 /// Note that you can create and mark path without rasterizing it. 6129 /// Group: picking_api 6130 public void currFillHitId (NVGContext ctx, int id) nothrow @trusted @nogc { 6131 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6132 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/false); 6133 nvg__pickSceneInsert(ps, pp); 6134 } 6135 6136 public alias currFillPickId = currFillHitId; /// Ditto. 6137 6138 /// Marks the stroke of the current path as pickable with the specified id. 6139 /// Note that you can create and mark path without rasterizing it. 6140 /// Group: picking_api 6141 public void currStrokeHitId (NVGContext ctx, int id) nothrow @trusted @nogc { 6142 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6143 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/true); 6144 nvg__pickSceneInsert(ps, pp); 6145 } 6146 6147 public alias currStrokePickId = currStrokeHitId; /// Ditto. 6148 6149 // Marks the saved path set (fill) as pickable with the specified id. 6150 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!) 6151 // Group: picking_api 6152 /+ 6153 public void pathSetFillHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc { 6154 if (svp is null) return; 6155 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context"); 6156 foreach (ref cp; svp.caches[0..svp.ncaches]) { 6157 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6158 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/false); 6159 nvg__pickSceneInsert(ps, pp); 6160 } 6161 } 6162 +/ 6163 6164 // Marks the saved path set (stroke) as pickable with the specified id. 6165 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!) 6166 // Group: picking_api 6167 /+ 6168 public void pathSetStrokeHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc { 6169 if (svp is null) return; 6170 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context"); 6171 foreach (ref cp; svp.caches[0..svp.ncaches]) { 6172 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6173 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/true); 6174 nvg__pickSceneInsert(ps, pp); 6175 } 6176 } 6177 +/ 6178 6179 private template IsGoodHitTestDG(DG) { 6180 enum IsGoodHitTestDG = 6181 __traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); }) || 6182 __traits(compiles, (){ DG dg; dg(cast(int)42, cast(int)666); }); 6183 } 6184 6185 private template IsGoodHitTestInternalDG(DG) { 6186 enum IsGoodHitTestInternalDG = 6187 __traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); }) || 6188 __traits(compiles, (){ DG dg; NVGpickPath* pp; dg(pp); }); 6189 } 6190 6191 /// Call delegate [dg] for each path under the specified position (in no particular order). 6192 /// Returns the id of the path for which delegate [dg] returned true or [NVGNoPick]. 6193 /// dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending). 6194 /// Group: picking_api 6195 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) { 6196 if (ctx.pickScene is null || ctx.pickScene.npaths == 0 || (kind&NVGPickKind.All) == 0) return -1; 6197 6198 NVGpickScene* ps = ctx.pickScene; 6199 int levelwidth = 1<<(ps.nlevels-1); 6200 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth); 6201 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth); 6202 int npicked = 0; 6203 6204 // if we are interested only in most-toplevel path, there is no reason to check paths with worser order. 6205 // but we cannot just get out on the first path found, 'cause we are using quad tree to speed up bounds 6206 // checking, so path walking order is not guaranteed. 6207 static if (bestOrder) { 6208 int lastBestOrder = int.min; 6209 } 6210 6211 //{ import core.stdc.stdio; printf("npaths=%d\n", ps.npaths); } 6212 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) { 6213 for (NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; pp !is null; pp = pp.next) { 6214 //{ 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); } 6215 static if (bestOrder) { 6216 // reject earlier paths 6217 if (pp.order <= lastBestOrder) continue; // not interesting 6218 } 6219 immutable uint kpx = kind&pp.flags&3; 6220 if (kpx == 0) continue; // not interesting 6221 if (!nvg__pickPathTestBounds(ctx, ps, pp, x, y)) continue; // not interesting 6222 //{ import core.stdc.stdio; printf("in bounds!\n"); } 6223 int hit = 0; 6224 if (kpx&NVGPickKind.Stroke) hit = nvg__pickPathStroke(ps, pp, x, y); 6225 if (!hit && (kpx&NVGPickKind.Fill)) hit = nvg__pickPath(ps, pp, x, y); 6226 if (!hit) continue; 6227 //{ import core.stdc.stdio; printf(" HIT!\n"); } 6228 static if (bestOrder) lastBestOrder = pp.order; 6229 static if (IsGoodHitTestDG!DG) { 6230 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) { 6231 if (dg(pp.id, cast(int)pp.order)) return pp.id; 6232 } else { 6233 dg(pp.id, cast(int)pp.order); 6234 } 6235 } else { 6236 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) { 6237 if (dg(pp)) return pp.id; 6238 } else { 6239 dg(pp); 6240 } 6241 } 6242 } 6243 cellx >>= 1; 6244 celly >>= 1; 6245 levelwidth >>= 1; 6246 } 6247 6248 return -1; 6249 } 6250 6251 /// Fills ids with a list of the top most hit ids (from bottom to top) under the specified position. 6252 /// Returns the slice of [ids]. 6253 /// Group: picking_api 6254 public int[] hitTestAll (NVGContext ctx, in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc { 6255 if (ctx.pickScene is null || ids.length == 0) return ids[0..0]; 6256 6257 int npicked = 0; 6258 NVGpickScene* ps = ctx.pickScene; 6259 6260 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 6261 if (npicked == ps.cpicked) { 6262 int cpicked = ps.cpicked+ps.cpicked; 6263 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked); 6264 if (picked is null) return true; // abort 6265 ps.cpicked = cpicked; 6266 ps.picked = picked; 6267 } 6268 ps.picked[npicked] = pp; 6269 ++npicked; 6270 return false; // go on 6271 }); 6272 6273 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths); 6274 6275 assert(npicked >= 0); 6276 if (npicked > ids.length) npicked = cast(int)ids.length; 6277 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id; 6278 6279 return ids[0..npicked]; 6280 } 6281 6282 /// Returns the id of the pickable shape containing x,y or [NVGNoPick] if no shape was found. 6283 /// Group: picking_api 6284 public int hitTest (NVGContext ctx, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc { 6285 if (ctx.pickScene is null) return -1; 6286 6287 int bestOrder = int.min; 6288 int bestID = -1; 6289 6290 ctx.hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) { 6291 if (pp.order > bestOrder) { 6292 bestOrder = pp.order; 6293 bestID = pp.id; 6294 } 6295 }); 6296 6297 return bestID; 6298 } 6299 6300 /// Returns `true` if the path with the given id contains x,y. 6301 /// Group: picking_api 6302 public bool hitTestForId (NVGContext ctx, in int id, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc { 6303 if (ctx.pickScene is null || id == NVGNoPick) return false; 6304 6305 bool res = false; 6306 6307 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) { 6308 if (pp.id == id) { 6309 res = true; 6310 return true; // stop 6311 } 6312 return false; // continue 6313 }); 6314 6315 return res; 6316 } 6317 6318 /// Returns `true` if the given point is within the fill of the currently defined path. 6319 /// This operation can be done before rasterizing the current path. 6320 /// Group: picking_api 6321 public bool hitTestCurrFill (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 6322 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6323 int oldnpoints = ps.npoints; 6324 int oldnsegments = ps.nsegments; 6325 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/false); 6326 if (pp is null) return false; // oops 6327 scope(exit) { 6328 nvg__freePickPath(ps, pp); 6329 ps.npoints = oldnpoints; 6330 ps.nsegments = oldnsegments; 6331 } 6332 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPath(ps, pp, x, y) : false); 6333 } 6334 6335 public alias isPointInPath = hitTestCurrFill; /// Ditto. 6336 6337 /// Returns `true` if the given point is within the stroke of the currently defined path. 6338 /// This operation can be done before rasterizing the current path. 6339 /// Group: picking_api 6340 public bool hitTestCurrStroke (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 6341 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6342 int oldnpoints = ps.npoints; 6343 int oldnsegments = ps.nsegments; 6344 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/true); 6345 if (pp is null) return false; // oops 6346 scope(exit) { 6347 nvg__freePickPath(ps, pp); 6348 ps.npoints = oldnpoints; 6349 ps.nsegments = oldnsegments; 6350 } 6351 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPathStroke(ps, pp, x, y) : false); 6352 } 6353 6354 6355 nothrow @trusted @nogc { 6356 extern(C) { 6357 private alias _compare_fp_t = int function (const void*, const void*) nothrow @nogc; 6358 private extern(C) void qsort (scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc; 6359 6360 extern(C) int nvg__comparePaths (const void* a, const void* b) { 6361 return (*cast(const(NVGpickPath)**)b).order-(*cast(const(NVGpickPath)**)a).order; 6362 } 6363 } 6364 6365 enum NVGPickEPS = 0.0001f; 6366 6367 // Segment flags 6368 enum NVGSegmentFlags { 6369 Corner = 1, 6370 Bevel = 2, 6371 InnerBevel = 4, 6372 Cap = 8, 6373 Endcap = 16, 6374 } 6375 6376 // Path flags 6377 enum NVGPathFlags : ushort { 6378 Fill = NVGPickKind.Fill, 6379 Stroke = NVGPickKind.Stroke, 6380 Scissor = 0x80, 6381 } 6382 6383 struct NVGsegment { 6384 int firstPoint; // Index into NVGpickScene.points 6385 short type; // NVG_LINETO or NVG_BEZIERTO 6386 short flags; // Flags relate to the corner between the prev segment and this one. 6387 float[4] bounds; 6388 float[2] startDir; // Direction at t == 0 6389 float[2] endDir; // Direction at t == 1 6390 float[2] miterDir; // Direction of miter of corner between the prev segment and this one. 6391 } 6392 6393 struct NVGpickSubPath { 6394 short winding; // TODO: Merge to flag field 6395 bool closed; // TODO: Merge to flag field 6396 6397 int firstSegment; // Index into NVGpickScene.segments 6398 int nsegments; 6399 6400 float[4] bounds; 6401 6402 NVGpickSubPath* next; 6403 } 6404 6405 struct NVGpickPath { 6406 int id; 6407 short flags; 6408 short order; 6409 float strokeWidth; 6410 float miterLimit; 6411 short lineCap; 6412 short lineJoin; 6413 bool evenOddMode; 6414 6415 float[4] bounds; 6416 int scissor; // Indexes into ps->points and defines scissor rect as XVec, YVec and Center 6417 6418 NVGpickSubPath* subPaths; 6419 NVGpickPath* next; 6420 NVGpickPath* cellnext; 6421 } 6422 6423 struct NVGpickScene { 6424 int npaths; 6425 6426 NVGpickPath* paths; // Linked list of paths 6427 NVGpickPath* lastPath; // The last path in the paths linked list (the first path added) 6428 NVGpickPath* freePaths; // Linked list of free paths 6429 6430 NVGpickSubPath* freeSubPaths; // Linked list of free sub paths 6431 6432 int width; 6433 int height; 6434 6435 // Points for all path sub paths. 6436 float* points; 6437 int npoints; 6438 int cpoints; 6439 6440 // Segments for all path sub paths 6441 NVGsegment* segments; 6442 int nsegments; 6443 int csegments; 6444 6445 // Implicit quadtree 6446 float xdim; // Width / (1 << nlevels) 6447 float ydim; // Height / (1 << nlevels) 6448 int ncells; // Total number of cells in all levels 6449 int nlevels; 6450 NVGpickPath*** levels; // Index: [Level][LevelY * LevelW + LevelX] Value: Linked list of paths 6451 6452 // Temp storage for picking 6453 int cpicked; 6454 NVGpickPath** picked; 6455 } 6456 6457 6458 // bounds utilities 6459 void nvg__initBounds (ref float[4] bounds) { 6460 bounds.ptr[0] = bounds.ptr[1] = float.max; 6461 bounds.ptr[2] = bounds.ptr[3] = -float.max; 6462 } 6463 6464 void nvg__expandBounds (ref float[4] bounds, const(float)* points, int npoints) { 6465 npoints *= 2; 6466 for (int i = 0; i < npoints; i += 2) { 6467 bounds.ptr[0] = nvg__min(bounds.ptr[0], points[i]); 6468 bounds.ptr[1] = nvg__min(bounds.ptr[1], points[i+1]); 6469 bounds.ptr[2] = nvg__max(bounds.ptr[2], points[i]); 6470 bounds.ptr[3] = nvg__max(bounds.ptr[3], points[i+1]); 6471 } 6472 } 6473 6474 void nvg__unionBounds (ref float[4] bounds, in ref float[4] boundsB) { 6475 bounds.ptr[0] = nvg__min(bounds.ptr[0], boundsB.ptr[0]); 6476 bounds.ptr[1] = nvg__min(bounds.ptr[1], boundsB.ptr[1]); 6477 bounds.ptr[2] = nvg__max(bounds.ptr[2], boundsB.ptr[2]); 6478 bounds.ptr[3] = nvg__max(bounds.ptr[3], boundsB.ptr[3]); 6479 } 6480 6481 void nvg__intersectBounds (ref float[4] bounds, in ref float[4] boundsB) { 6482 bounds.ptr[0] = nvg__max(boundsB.ptr[0], bounds.ptr[0]); 6483 bounds.ptr[1] = nvg__max(boundsB.ptr[1], bounds.ptr[1]); 6484 bounds.ptr[2] = nvg__min(boundsB.ptr[2], bounds.ptr[2]); 6485 bounds.ptr[3] = nvg__min(boundsB.ptr[3], bounds.ptr[3]); 6486 6487 bounds.ptr[2] = nvg__max(bounds.ptr[0], bounds.ptr[2]); 6488 bounds.ptr[3] = nvg__max(bounds.ptr[1], bounds.ptr[3]); 6489 } 6490 6491 bool nvg__pointInBounds (in float x, in float y, in ref float[4] bounds) { 6492 pragma(inline, true); 6493 return (x >= bounds.ptr[0] && x <= bounds.ptr[2] && y >= bounds.ptr[1] && y <= bounds.ptr[3]); 6494 } 6495 6496 // building paths & sub paths 6497 int nvg__pickSceneAddPoints (NVGpickScene* ps, const(float)* xy, int n) { 6498 import core.stdc.string : memcpy; 6499 if (ps.npoints+n > ps.cpoints) { 6500 import core.stdc.stdlib : realloc; 6501 int cpoints = ps.npoints+n+(ps.cpoints<<1); 6502 float* points = cast(float*)realloc(ps.points, float.sizeof*2*cpoints); 6503 if (points is null) assert(0, "NanoVega: out of memory"); 6504 ps.points = points; 6505 ps.cpoints = cpoints; 6506 } 6507 int i = ps.npoints; 6508 if (xy !is null) memcpy(&ps.points[i*2], xy, float.sizeof*2*n); 6509 ps.npoints += n; 6510 return i; 6511 } 6512 6513 void nvg__pickSubPathAddSegment (NVGpickScene* ps, NVGpickSubPath* psp, int firstPoint, int type, short flags) { 6514 NVGsegment* seg = null; 6515 if (ps.nsegments == ps.csegments) { 6516 int csegments = 1+ps.csegments+(ps.csegments<<1); 6517 NVGsegment* segments = cast(NVGsegment*)realloc(ps.segments, NVGsegment.sizeof*csegments); 6518 if (segments is null) assert(0, "NanoVega: out of memory"); 6519 ps.segments = segments; 6520 ps.csegments = csegments; 6521 } 6522 6523 if (psp.firstSegment == -1) psp.firstSegment = ps.nsegments; 6524 6525 seg = &ps.segments[ps.nsegments]; 6526 ++ps.nsegments; 6527 seg.firstPoint = firstPoint; 6528 seg.type = cast(short)type; 6529 seg.flags = flags; 6530 ++psp.nsegments; 6531 6532 nvg__segmentDir(ps, psp, seg, 0, seg.startDir); 6533 nvg__segmentDir(ps, psp, seg, 1, seg.endDir); 6534 } 6535 6536 void nvg__segmentDir (NVGpickScene* ps, NVGpickSubPath* psp, NVGsegment* seg, float t, ref float[2] d) { 6537 const(float)* points = &ps.points[seg.firstPoint*2]; 6538 immutable float x0 = points[0*2+0], x1 = points[1*2+0]; 6539 immutable float y0 = points[0*2+1], y1 = points[1*2+1]; 6540 switch (seg.type) { 6541 case Command.LineTo: 6542 d.ptr[0] = x1-x0; 6543 d.ptr[1] = y1-y0; 6544 nvg__normalize(&d.ptr[0], &d.ptr[1]); 6545 break; 6546 case Command.BezierTo: 6547 immutable float x2 = points[2*2+0]; 6548 immutable float y2 = points[2*2+1]; 6549 immutable float x3 = points[3*2+0]; 6550 immutable float y3 = points[3*2+1]; 6551 6552 immutable float omt = 1.0f-t; 6553 immutable float omt2 = omt*omt; 6554 immutable float t2 = t*t; 6555 6556 d.ptr[0] = 6557 3.0f*omt2*(x1-x0)+ 6558 6.0f*omt*t*(x2-x1)+ 6559 3.0f*t2*(x3-x2); 6560 6561 d.ptr[1] = 6562 3.0f*omt2*(y1-y0)+ 6563 6.0f*omt*t*(y2-y1)+ 6564 3.0f*t2*(y3-y2); 6565 6566 nvg__normalize(&d.ptr[0], &d.ptr[1]); 6567 break; 6568 default: 6569 break; 6570 } 6571 } 6572 6573 void nvg__pickSubPathAddFillSupports (NVGpickScene* ps, NVGpickSubPath* psp) { 6574 if (psp.firstSegment == -1) return; 6575 NVGsegment* segments = &ps.segments[psp.firstSegment]; 6576 for (int s = 0; s < psp.nsegments; ++s) { 6577 NVGsegment* seg = &segments[s]; 6578 const(float)* points = &ps.points[seg.firstPoint*2]; 6579 if (seg.type == Command.LineTo) { 6580 nvg__initBounds(seg.bounds); 6581 nvg__expandBounds(seg.bounds, points, 2); 6582 } else { 6583 nvg__bezierBounds(points, seg.bounds); 6584 } 6585 } 6586 } 6587 6588 void nvg__pickSubPathAddStrokeSupports (NVGpickScene* ps, NVGpickSubPath* psp, float strokeWidth, int lineCap, int lineJoin, float miterLimit) { 6589 if (psp.firstSegment == -1) return; 6590 immutable bool closed = psp.closed; 6591 const(float)* points = ps.points; 6592 NVGsegment* seg = null; 6593 NVGsegment* segments = &ps.segments[psp.firstSegment]; 6594 int nsegments = psp.nsegments; 6595 NVGsegment* prevseg = (closed ? &segments[psp.nsegments-1] : null); 6596 6597 int ns = 0; // nsupports 6598 float[32] supportingPoints = void; 6599 int firstPoint, lastPoint; 6600 6601 if (!closed) { 6602 segments[0].flags |= NVGSegmentFlags.Cap; 6603 segments[nsegments-1].flags |= NVGSegmentFlags.Endcap; 6604 } 6605 6606 for (int s = 0; s < nsegments; ++s) { 6607 seg = &segments[s]; 6608 nvg__initBounds(seg.bounds); 6609 6610 firstPoint = seg.firstPoint*2; 6611 lastPoint = firstPoint+(seg.type == Command.LineTo ? 2 : 6); 6612 6613 ns = 0; 6614 6615 // First two supporting points are either side of the start point 6616 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[1]*strokeWidth; 6617 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.startDir.ptr[0]*strokeWidth; 6618 6619 supportingPoints.ptr[ns++] = points[firstPoint]+seg.startDir.ptr[1]*strokeWidth; 6620 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[0]*strokeWidth; 6621 6622 // Second two supporting points are either side of the end point 6623 supportingPoints.ptr[ns++] = points[lastPoint]-seg.endDir.ptr[1]*strokeWidth; 6624 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[0]*strokeWidth; 6625 6626 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[1]*strokeWidth; 6627 supportingPoints.ptr[ns++] = points[lastPoint+1]-seg.endDir.ptr[0]*strokeWidth; 6628 6629 if ((seg.flags&NVGSegmentFlags.Corner) && prevseg !is null) { 6630 seg.miterDir.ptr[0] = 0.5f*(-prevseg.endDir.ptr[1]-seg.startDir.ptr[1]); 6631 seg.miterDir.ptr[1] = 0.5f*(prevseg.endDir.ptr[0]+seg.startDir.ptr[0]); 6632 6633 immutable float M2 = seg.miterDir.ptr[0]*seg.miterDir.ptr[0]+seg.miterDir.ptr[1]*seg.miterDir.ptr[1]; 6634 6635 if (M2 > 0.000001f) { 6636 float scale = 1.0f/M2; 6637 if (scale > 600.0f) scale = 600.0f; 6638 seg.miterDir.ptr[0] *= scale; 6639 seg.miterDir.ptr[1] *= scale; 6640 } 6641 6642 //NVG_PICK_DEBUG_VECTOR_SCALE(&points[firstPoint], seg.miterDir, 10); 6643 6644 // Add an additional support at the corner on the other line 6645 supportingPoints.ptr[ns++] = points[firstPoint]-prevseg.endDir.ptr[1]*strokeWidth; 6646 supportingPoints.ptr[ns++] = points[firstPoint+1]+prevseg.endDir.ptr[0]*strokeWidth; 6647 6648 if (lineJoin == NVGLineCap.Miter || lineJoin == NVGLineCap.Bevel) { 6649 // Set a corner as beveled if the join type is bevel or mitered and 6650 // miterLimit is hit. 6651 if (lineJoin == NVGLineCap.Bevel || (M2*miterLimit*miterLimit) < 1.0f) { 6652 seg.flags |= NVGSegmentFlags.Bevel; 6653 } else { 6654 // Corner is mitered - add miter point as a support 6655 supportingPoints.ptr[ns++] = points[firstPoint]+seg.miterDir.ptr[0]*strokeWidth; 6656 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.miterDir.ptr[1]*strokeWidth; 6657 } 6658 } else if (lineJoin == NVGLineCap.Round) { 6659 // ... and at the midpoint of the corner arc 6660 float[2] vertexN = [ -seg.startDir.ptr[0]+prevseg.endDir.ptr[0], -seg.startDir.ptr[1]+prevseg.endDir.ptr[1] ]; 6661 nvg__normalize(&vertexN[0], &vertexN[1]); 6662 6663 supportingPoints.ptr[ns++] = points[firstPoint]+vertexN[0]*strokeWidth; 6664 supportingPoints.ptr[ns++] = points[firstPoint+1]+vertexN[1]*strokeWidth; 6665 } 6666 } 6667 6668 if (seg.flags&NVGSegmentFlags.Cap) { 6669 switch (lineCap) { 6670 case NVGLineCap.Butt: 6671 // supports for butt already added 6672 break; 6673 case NVGLineCap.Square: 6674 // square cap supports are just the original two supports moved out along the direction 6675 supportingPoints.ptr[ns++] = supportingPoints.ptr[0]-seg.startDir.ptr[0]*strokeWidth; 6676 supportingPoints.ptr[ns++] = supportingPoints.ptr[1]-seg.startDir.ptr[1]*strokeWidth; 6677 supportingPoints.ptr[ns++] = supportingPoints.ptr[2]-seg.startDir.ptr[0]*strokeWidth; 6678 supportingPoints.ptr[ns++] = supportingPoints.ptr[3]-seg.startDir.ptr[1]*strokeWidth; 6679 break; 6680 case NVGLineCap.Round: 6681 // add one additional support for the round cap along the dir 6682 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[0]*strokeWidth; 6683 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[1]*strokeWidth; 6684 break; 6685 default: 6686 break; 6687 } 6688 } 6689 6690 if (seg.flags&NVGSegmentFlags.Endcap) { 6691 // end supporting points, either side of line 6692 int end = 4; 6693 switch(lineCap) { 6694 case NVGLineCap.Butt: 6695 // supports for butt already added 6696 break; 6697 case NVGLineCap.Square: 6698 // square cap supports are just the original two supports moved out along the direction 6699 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+0]+seg.endDir.ptr[0]*strokeWidth; 6700 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+1]+seg.endDir.ptr[1]*strokeWidth; 6701 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+2]+seg.endDir.ptr[0]*strokeWidth; 6702 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+3]+seg.endDir.ptr[1]*strokeWidth; 6703 break; 6704 case NVGLineCap.Round: 6705 // add one additional support for the round cap along the dir 6706 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[0]*strokeWidth; 6707 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[1]*strokeWidth; 6708 break; 6709 default: 6710 break; 6711 } 6712 } 6713 6714 nvg__expandBounds(seg.bounds, supportingPoints.ptr, ns/2); 6715 6716 prevseg = seg; 6717 } 6718 } 6719 6720 NVGpickPath* nvg__pickPathCreate (NVGContext context, const(float)[] acommands, int id, bool forStroke) { 6721 NVGpickScene* ps = nvg__pickSceneGet(context); 6722 if (ps is null) return null; 6723 6724 int i = 0; 6725 6726 int ncommands = cast(int)acommands.length; 6727 const(float)* commands = acommands.ptr; 6728 6729 NVGpickPath* pp = null; 6730 NVGpickSubPath* psp = null; 6731 float[2] start = void; 6732 int firstPoint; 6733 6734 //bool hasHoles = false; 6735 NVGpickSubPath* prev = null; 6736 6737 float[8] points = void; 6738 float[2] inflections = void; 6739 int ninflections = 0; 6740 6741 NVGstate* state = nvg__getState(context); 6742 float[4] totalBounds = void; 6743 NVGsegment* segments = null; 6744 const(NVGsegment)* seg = null; 6745 NVGpickSubPath *curpsp; 6746 6747 pp = nvg__allocPickPath(ps); 6748 if (pp is null) return null; 6749 6750 pp.id = id; 6751 6752 bool hasPoints = false; 6753 6754 void closeIt () { 6755 if (psp is null || !hasPoints) return; 6756 if (ps.points[(ps.npoints-1)*2] != start.ptr[0] || ps.points[(ps.npoints-1)*2+1] != start.ptr[1]) { 6757 firstPoint = nvg__pickSceneAddPoints(ps, start.ptr, 1); 6758 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, Command.LineTo, NVGSegmentFlags.Corner); 6759 } 6760 psp.closed = true; 6761 } 6762 6763 while (i < ncommands) { 6764 int cmd = cast(int)commands[i++]; 6765 switch (cmd) { 6766 case Command.MoveTo: // one coordinate pair 6767 const(float)* tfxy = commands+i; 6768 i += 2; 6769 6770 // new starting point 6771 start.ptr[0..2] = tfxy[0..2]; 6772 6773 // start a new path for each sub path to handle sub paths that intersect other sub paths 6774 prev = psp; 6775 psp = nvg__allocPickSubPath(ps); 6776 if (psp is null) { psp = prev; break; } 6777 psp.firstSegment = -1; 6778 psp.winding = NVGSolidity.Solid; 6779 psp.next = prev; 6780 6781 nvg__pickSceneAddPoints(ps, tfxy, 1); 6782 hasPoints = true; 6783 break; 6784 case Command.LineTo: // one coordinate pair 6785 const(float)* tfxy = commands+i; 6786 i += 2; 6787 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 1); 6788 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner); 6789 hasPoints = true; 6790 break; 6791 case Command.BezierTo: // three coordinate pairs 6792 const(float)* tfxy = commands+i; 6793 i += 3*2; 6794 6795 // Split the curve at it's dx==0 or dy==0 inflection points. 6796 // Thus: 6797 // A horizontal line only ever interects the curves once. 6798 // and 6799 // Finding the closest point on any curve converges more reliably. 6800 6801 // NOTE: We could just split on dy==0 here. 6802 6803 memcpy(&points.ptr[0], &ps.points[(ps.npoints-1)*2], float.sizeof*2); 6804 memcpy(&points.ptr[2], tfxy, float.sizeof*2*3); 6805 6806 ninflections = 0; 6807 nvg__bezierInflections(points.ptr, 1, &ninflections, inflections.ptr); 6808 nvg__bezierInflections(points.ptr, 0, &ninflections, inflections.ptr); 6809 6810 if (ninflections) { 6811 float previnfl = 0; 6812 float[8] pointsA = void, pointsB = void; 6813 6814 nvg__smallsort(inflections.ptr, ninflections); 6815 6816 for (int infl = 0; infl < ninflections; ++infl) { 6817 if (nvg__absf(inflections.ptr[infl]-previnfl) < NVGPickEPS) continue; 6818 6819 immutable float t = (inflections.ptr[infl]-previnfl)*(1.0f/(1.0f-previnfl)); 6820 6821 previnfl = inflections.ptr[infl]; 6822 6823 nvg__splitBezier(points.ptr, t, pointsA.ptr, pointsB.ptr); 6824 6825 firstPoint = nvg__pickSceneAddPoints(ps, &pointsA.ptr[2], 3); 6826 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, (infl == 0) ? NVGSegmentFlags.Corner : 0); 6827 6828 memcpy(points.ptr, pointsB.ptr, float.sizeof*8); 6829 } 6830 6831 firstPoint = nvg__pickSceneAddPoints(ps, &pointsB.ptr[2], 3); 6832 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, 0); 6833 } else { 6834 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 3); 6835 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner); 6836 } 6837 hasPoints = true; 6838 break; 6839 case Command.Close: 6840 closeIt(); 6841 break; 6842 case Command.Winding: 6843 psp.winding = cast(short)cast(int)commands[i]; 6844 //if (psp.winding == NVGSolidity.Hole) hasHoles = true; 6845 i += 1; 6846 break; 6847 default: 6848 break; 6849 } 6850 } 6851 6852 // force-close filled paths 6853 if (psp !is null && !forStroke && hasPoints && !psp.closed) closeIt(); 6854 6855 pp.flags = (forStroke ? NVGPathFlags.Stroke : NVGPathFlags.Fill); 6856 pp.subPaths = psp; 6857 pp.strokeWidth = state.strokeWidth*0.5f; 6858 pp.miterLimit = state.miterLimit; 6859 pp.lineCap = cast(short)state.lineCap; 6860 pp.lineJoin = cast(short)state.lineJoin; 6861 pp.evenOddMode = nvg__getState(context).evenOddMode; 6862 6863 nvg__initBounds(totalBounds); 6864 6865 for (curpsp = psp; curpsp; curpsp = curpsp.next) { 6866 if (forStroke) { 6867 nvg__pickSubPathAddStrokeSupports(ps, curpsp, pp.strokeWidth, pp.lineCap, pp.lineJoin, pp.miterLimit); 6868 } else { 6869 nvg__pickSubPathAddFillSupports(ps, curpsp); 6870 } 6871 6872 if (curpsp.firstSegment == -1) continue; 6873 segments = &ps.segments[curpsp.firstSegment]; 6874 nvg__initBounds(curpsp.bounds); 6875 for (int s = 0; s < curpsp.nsegments; ++s) { 6876 seg = &segments[s]; 6877 //NVG_PICK_DEBUG_BOUNDS(seg.bounds); 6878 nvg__unionBounds(curpsp.bounds, seg.bounds); 6879 } 6880 6881 nvg__unionBounds(totalBounds, curpsp.bounds); 6882 } 6883 6884 // Store the scissor rect if present. 6885 if (state.scissor.extent.ptr[0] != -1.0f) { 6886 // Use points storage to store the scissor data 6887 pp.scissor = nvg__pickSceneAddPoints(ps, null, 4); 6888 float* scissor = &ps.points[pp.scissor*2]; 6889 6890 //memcpy(scissor, state.scissor.xform.ptr, 6*float.sizeof); 6891 scissor[0..6] = state.scissor.xform.mat[]; 6892 memcpy(scissor+6, state.scissor.extent.ptr, 2*float.sizeof); 6893 6894 pp.flags |= NVGPathFlags.Scissor; 6895 } 6896 6897 memcpy(pp.bounds.ptr, totalBounds.ptr, float.sizeof*4); 6898 6899 return pp; 6900 } 6901 6902 6903 // Struct management 6904 NVGpickPath* nvg__allocPickPath (NVGpickScene* ps) { 6905 NVGpickPath* pp = ps.freePaths; 6906 if (pp !is null) { 6907 ps.freePaths = pp.next; 6908 } else { 6909 pp = cast(NVGpickPath*)malloc(NVGpickPath.sizeof); 6910 } 6911 memset(pp, 0, NVGpickPath.sizeof); 6912 return pp; 6913 } 6914 6915 // Put a pick path and any sub paths (back) to the free lists. 6916 void nvg__freePickPath (NVGpickScene* ps, NVGpickPath* pp) { 6917 // Add all sub paths to the sub path free list. 6918 // Finds the end of the path sub paths, links that to the current 6919 // sub path free list head and replaces the head ptr with the 6920 // head path sub path entry. 6921 NVGpickSubPath* psp = null; 6922 for (psp = pp.subPaths; psp !is null && psp.next !is null; psp = psp.next) {} 6923 6924 if (psp) { 6925 psp.next = ps.freeSubPaths; 6926 ps.freeSubPaths = pp.subPaths; 6927 } 6928 pp.subPaths = null; 6929 6930 // Add the path to the path freelist 6931 pp.next = ps.freePaths; 6932 ps.freePaths = pp; 6933 if (pp.next is null) ps.lastPath = pp; 6934 } 6935 6936 NVGpickSubPath* nvg__allocPickSubPath (NVGpickScene* ps) { 6937 NVGpickSubPath* psp = ps.freeSubPaths; 6938 if (psp !is null) { 6939 ps.freeSubPaths = psp.next; 6940 } else { 6941 psp = cast(NVGpickSubPath*)malloc(NVGpickSubPath.sizeof); 6942 if (psp is null) return null; 6943 } 6944 memset(psp, 0, NVGpickSubPath.sizeof); 6945 return psp; 6946 } 6947 6948 void nvg__returnPickSubPath (NVGpickScene* ps, NVGpickSubPath* psp) { 6949 psp.next = ps.freeSubPaths; 6950 ps.freeSubPaths = psp; 6951 } 6952 6953 NVGpickScene* nvg__allocPickScene () { 6954 NVGpickScene* ps = cast(NVGpickScene*)malloc(NVGpickScene.sizeof); 6955 if (ps is null) return null; 6956 memset(ps, 0, NVGpickScene.sizeof); 6957 ps.nlevels = 5; 6958 return ps; 6959 } 6960 6961 void nvg__deletePickScene (NVGpickScene* ps) { 6962 NVGpickPath* pp; 6963 NVGpickSubPath* psp; 6964 6965 // Add all paths (and thus sub paths) to the free list(s). 6966 while (ps.paths !is null) { 6967 pp = ps.paths.next; 6968 nvg__freePickPath(ps, ps.paths); 6969 ps.paths = pp; 6970 } 6971 6972 // Delete all paths 6973 while (ps.freePaths !is null) { 6974 pp = ps.freePaths; 6975 ps.freePaths = pp.next; 6976 while (pp.subPaths !is null) { 6977 psp = pp.subPaths; 6978 pp.subPaths = psp.next; 6979 free(psp); 6980 } 6981 free(pp); 6982 } 6983 6984 // Delete all sub paths 6985 while (ps.freeSubPaths !is null) { 6986 psp = ps.freeSubPaths.next; 6987 free(ps.freeSubPaths); 6988 ps.freeSubPaths = psp; 6989 } 6990 6991 ps.npoints = 0; 6992 ps.nsegments = 0; 6993 6994 if (ps.levels !is null) { 6995 free(ps.levels[0]); 6996 free(ps.levels); 6997 } 6998 6999 if (ps.picked !is null) free(ps.picked); 7000 if (ps.points !is null) free(ps.points); 7001 if (ps.segments !is null) free(ps.segments); 7002 7003 free(ps); 7004 } 7005 7006 NVGpickScene* nvg__pickSceneGet (NVGContext ctx) { 7007 if (ctx.pickScene is null) ctx.pickScene = nvg__allocPickScene(); 7008 return ctx.pickScene; 7009 } 7010 7011 7012 // Applies Casteljau's algorithm to a cubic bezier for a given parameter t 7013 // points is 4 points (8 floats) 7014 // lvl1 is 3 points (6 floats) 7015 // lvl2 is 2 points (4 floats) 7016 // lvl3 is 1 point (2 floats) 7017 void nvg__casteljau (const(float)* points, float t, float* lvl1, float* lvl2, float* lvl3) { 7018 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0; 7019 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1; 7020 7021 // Level 1 7022 lvl1[x0] = (points[x1]-points[x0])*t+points[x0]; 7023 lvl1[y0] = (points[y1]-points[y0])*t+points[y0]; 7024 7025 lvl1[x1] = (points[x2]-points[x1])*t+points[x1]; 7026 lvl1[y1] = (points[y2]-points[y1])*t+points[y1]; 7027 7028 lvl1[x2] = (points[x3]-points[x2])*t+points[x2]; 7029 lvl1[y2] = (points[y3]-points[y2])*t+points[y2]; 7030 7031 // Level 2 7032 lvl2[x0] = (lvl1[x1]-lvl1[x0])*t+lvl1[x0]; 7033 lvl2[y0] = (lvl1[y1]-lvl1[y0])*t+lvl1[y0]; 7034 7035 lvl2[x1] = (lvl1[x2]-lvl1[x1])*t+lvl1[x1]; 7036 lvl2[y1] = (lvl1[y2]-lvl1[y1])*t+lvl1[y1]; 7037 7038 // Level 3 7039 lvl3[x0] = (lvl2[x1]-lvl2[x0])*t+lvl2[x0]; 7040 lvl3[y0] = (lvl2[y1]-lvl2[y0])*t+lvl2[y0]; 7041 } 7042 7043 // Calculates a point on a bezier at point t. 7044 void nvg__bezierEval (const(float)* points, float t, ref float[2] tpoint) { 7045 immutable float omt = 1-t; 7046 immutable float omt3 = omt*omt*omt; 7047 immutable float omt2 = omt*omt; 7048 immutable float t3 = t*t*t; 7049 immutable float t2 = t*t; 7050 7051 tpoint.ptr[0] = 7052 points[0]*omt3+ 7053 points[2]*3.0f*omt2*t+ 7054 points[4]*3.0f*omt*t2+ 7055 points[6]*t3; 7056 7057 tpoint.ptr[1] = 7058 points[1]*omt3+ 7059 points[3]*3.0f*omt2*t+ 7060 points[5]*3.0f*omt*t2+ 7061 points[7]*t3; 7062 } 7063 7064 // Splits a cubic bezier curve into two parts at point t. 7065 void nvg__splitBezier (const(float)* points, float t, float* pointsA, float* pointsB) { 7066 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0; 7067 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1; 7068 7069 float[6] lvl1 = void; 7070 float[4] lvl2 = void; 7071 float[2] lvl3 = void; 7072 7073 nvg__casteljau(points, t, lvl1.ptr, lvl2.ptr, lvl3.ptr); 7074 7075 // First half 7076 pointsA[x0] = points[x0]; 7077 pointsA[y0] = points[y0]; 7078 7079 pointsA[x1] = lvl1.ptr[x0]; 7080 pointsA[y1] = lvl1.ptr[y0]; 7081 7082 pointsA[x2] = lvl2.ptr[x0]; 7083 pointsA[y2] = lvl2.ptr[y0]; 7084 7085 pointsA[x3] = lvl3.ptr[x0]; 7086 pointsA[y3] = lvl3.ptr[y0]; 7087 7088 // Second half 7089 pointsB[x0] = lvl3.ptr[x0]; 7090 pointsB[y0] = lvl3.ptr[y0]; 7091 7092 pointsB[x1] = lvl2.ptr[x1]; 7093 pointsB[y1] = lvl2.ptr[y1]; 7094 7095 pointsB[x2] = lvl1.ptr[x2]; 7096 pointsB[y2] = lvl1.ptr[y2]; 7097 7098 pointsB[x3] = points[x3]; 7099 pointsB[y3] = points[y3]; 7100 } 7101 7102 // Calculates the inflection points in coordinate coord (X = 0, Y = 1) of a cubic bezier. 7103 // Appends any found inflection points to the array inflections and increments *ninflections. 7104 // So finds the parameters where dx/dt or dy/dt is 0 7105 void nvg__bezierInflections (const(float)* points, int coord, int* ninflections, float* inflections) { 7106 immutable float v0 = points[0*2+coord], v1 = points[1*2+coord], v2 = points[2*2+coord], v3 = points[3*2+coord]; 7107 float[2] t = void; 7108 int nvalid = *ninflections; 7109 7110 immutable float a = 3.0f*( -v0+3.0f*v1-3.0f*v2+v3 ); 7111 immutable float b = 6.0f*( v0-2.0f*v1+v2 ); 7112 immutable float c = 3.0f*( v1-v0 ); 7113 7114 float d = b*b-4.0f*a*c; 7115 if (nvg__absf(d-0.0f) < NVGPickEPS) { 7116 // Zero or one root 7117 t.ptr[0] = -b/2.0f*a; 7118 if (t.ptr[0] > NVGPickEPS && t.ptr[0] < (1.0f-NVGPickEPS)) { 7119 inflections[nvalid] = t.ptr[0]; 7120 ++nvalid; 7121 } 7122 } else if (d > NVGPickEPS) { 7123 // zero, one or two roots 7124 d = nvg__sqrtf(d); 7125 7126 t.ptr[0] = (-b+d)/(2.0f*a); 7127 t.ptr[1] = (-b-d)/(2.0f*a); 7128 7129 for (int i = 0; i < 2; ++i) { 7130 if (t.ptr[i] > NVGPickEPS && t.ptr[i] < (1.0f-NVGPickEPS)) { 7131 inflections[nvalid] = t.ptr[i]; 7132 ++nvalid; 7133 } 7134 } 7135 } else { 7136 // zero roots 7137 } 7138 7139 *ninflections = nvalid; 7140 } 7141 7142 // Sort a small number of floats in ascending order (0 < n < 6) 7143 void nvg__smallsort (float* values, int n) { 7144 bool bSwapped = true; 7145 for (int j = 0; j < n-1 && bSwapped; ++j) { 7146 bSwapped = false; 7147 for (int i = 0; i < n-1; ++i) { 7148 if (values[i] > values[i+1]) { 7149 auto tmp = values[i]; 7150 values[i] = values[i+1]; 7151 values[i+1] = tmp; 7152 } 7153 } 7154 } 7155 } 7156 7157 // Calculates the bounding rect of a given cubic bezier curve. 7158 void nvg__bezierBounds (const(float)* points, ref float[4] bounds) { 7159 float[4] inflections = void; 7160 int ninflections = 0; 7161 float[2] tpoint = void; 7162 7163 nvg__initBounds(bounds); 7164 7165 // Include start and end points in bounds 7166 nvg__expandBounds(bounds, &points[0], 1); 7167 nvg__expandBounds(bounds, &points[6], 1); 7168 7169 // Calculate dx==0 and dy==0 inflection points and add them to the bounds 7170 7171 nvg__bezierInflections(points, 0, &ninflections, inflections.ptr); 7172 nvg__bezierInflections(points, 1, &ninflections, inflections.ptr); 7173 7174 foreach (immutable int i; 0..ninflections) { 7175 nvg__bezierEval(points, inflections[i], tpoint); 7176 nvg__expandBounds(bounds, tpoint.ptr, 1); 7177 } 7178 } 7179 7180 // Checks to see if a line originating from x,y along the +ve x axis 7181 // intersects the given line (points[0],points[1]) -> (points[2], points[3]). 7182 // Returns `true` on intersection. 7183 // Horizontal lines are never hit. 7184 bool nvg__intersectLine (const(float)* points, float x, float y) { 7185 immutable float x1 = points[0]; 7186 immutable float y1 = points[1]; 7187 immutable float x2 = points[2]; 7188 immutable float y2 = points[3]; 7189 immutable float d = y2-y1; 7190 if (d > NVGPickEPS || d < -NVGPickEPS) { 7191 immutable float s = (x2-x1)/d; 7192 immutable float lineX = x1+(y-y1)*s; 7193 return (lineX > x); 7194 } else { 7195 return false; 7196 } 7197 } 7198 7199 // Checks to see if a line originating from x,y along the +ve x axis intersects the given bezier. 7200 // It is assumed that the line originates from within the bounding box of 7201 // the bezier and that the curve has no dy=0 inflection points. 7202 // Returns the number of intersections found (which is either 1 or 0). 7203 int nvg__intersectBezier (const(float)* points, float x, float y) { 7204 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0]; 7205 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1]; 7206 7207 if (y0 == y1 && y1 == y2 && y2 == y3) return 0; 7208 7209 // Initial t guess 7210 float t = void; 7211 if (y3 != y0) t = (y-y0)/(y3-y0); 7212 else if (x3 != x0) t = (x-x0)/(x3-x0); 7213 else t = 0.5f; 7214 7215 // A few Newton iterations 7216 for (int iter = 0; iter < 6; ++iter) { 7217 immutable float omt = 1-t; 7218 immutable float omt2 = omt*omt; 7219 immutable float t2 = t*t; 7220 immutable float omt3 = omt2*omt; 7221 immutable float t3 = t2*t; 7222 7223 immutable float ty = y0*omt3 + 7224 y1*3.0f*omt2*t + 7225 y2*3.0f*omt*t2 + 7226 y3*t3; 7227 7228 // Newton iteration 7229 immutable float dty = 3.0f*omt2*(y1-y0) + 7230 6.0f*omt*t*(y2-y1) + 7231 3.0f*t2*(y3-y2); 7232 7233 // dty will never == 0 since: 7234 // Either omt, omt2 are zero OR t2 is zero 7235 // y0 != y1 != y2 != y3 (checked above) 7236 t = t-(ty-y)/dty; 7237 } 7238 7239 { 7240 immutable float omt = 1-t; 7241 immutable float omt2 = omt*omt; 7242 immutable float t2 = t*t; 7243 immutable float omt3 = omt2*omt; 7244 immutable float t3 = t2*t; 7245 7246 immutable float tx = 7247 x0*omt3+ 7248 x1*3.0f*omt2*t+ 7249 x2*3.0f*omt*t2+ 7250 x3*t3; 7251 7252 return (tx > x ? 1 : 0); 7253 } 7254 } 7255 7256 // Finds the closest point on a line to a given point 7257 void nvg__closestLine (const(float)* points, float x, float y, float* closest, float* ot) { 7258 immutable float x1 = points[0]; 7259 immutable float y1 = points[1]; 7260 immutable float x2 = points[2]; 7261 immutable float y2 = points[3]; 7262 immutable float pqx = x2-x1; 7263 immutable float pqz = y2-y1; 7264 immutable float dx = x-x1; 7265 immutable float dz = y-y1; 7266 immutable float d = pqx*pqx+pqz*pqz; 7267 float t = pqx*dx+pqz*dz; 7268 if (d > 0) t /= d; 7269 if (t < 0) t = 0; else if (t > 1) t = 1; 7270 closest[0] = x1+t*pqx; 7271 closest[1] = y1+t*pqz; 7272 *ot = t; 7273 } 7274 7275 // Finds the closest point on a curve for a given point (x,y). 7276 // Assumes that the curve has no dx==0 or dy==0 inflection points. 7277 void nvg__closestBezier (const(float)* points, float x, float y, float* closest, float *ot) { 7278 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0]; 7279 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1]; 7280 7281 // This assumes that the curve has no dy=0 inflection points. 7282 7283 // Initial t guess 7284 float t = 0.5f; 7285 7286 // A few Newton iterations 7287 for (int iter = 0; iter < 6; ++iter) { 7288 immutable float omt = 1-t; 7289 immutable float omt2 = omt*omt; 7290 immutable float t2 = t*t; 7291 immutable float omt3 = omt2*omt; 7292 immutable float t3 = t2*t; 7293 7294 immutable float ty = 7295 y0*omt3+ 7296 y1*3.0f*omt2*t+ 7297 y2*3.0f*omt*t2+ 7298 y3*t3; 7299 7300 immutable float tx = 7301 x0*omt3+ 7302 x1*3.0f*omt2*t+ 7303 x2*3.0f*omt*t2+ 7304 x3*t3; 7305 7306 // Newton iteration 7307 immutable float dty = 7308 3.0f*omt2*(y1-y0)+ 7309 6.0f*omt*t*(y2-y1)+ 7310 3.0f*t2*(y3-y2); 7311 7312 immutable float ddty = 7313 6.0f*omt*(y2-2.0f*y1+y0)+ 7314 6.0f*t*(y3-2.0f*y2+y1); 7315 7316 immutable float dtx = 7317 3.0f*omt2*(x1-x0)+ 7318 6.0f*omt*t*(x2-x1)+ 7319 3.0f*t2*(x3-x2); 7320 7321 immutable float ddtx = 7322 6.0f*omt*(x2-2.0f*x1+x0)+ 7323 6.0f*t*(x3-2.0f*x2+x1); 7324 7325 immutable float errorx = tx-x; 7326 immutable float errory = ty-y; 7327 7328 immutable float n = errorx*dtx+errory*dty; 7329 if (n == 0) break; 7330 7331 immutable float d = dtx*dtx+dty*dty+errorx*ddtx+errory*ddty; 7332 if (d != 0) t = t-n/d; else break; 7333 } 7334 7335 t = nvg__max(0, nvg__min(1.0, t)); 7336 *ot = t; 7337 { 7338 immutable float omt = 1-t; 7339 immutable float omt2 = omt*omt; 7340 immutable float t2 = t*t; 7341 immutable float omt3 = omt2*omt; 7342 immutable float t3 = t2*t; 7343 7344 immutable float ty = 7345 y0*omt3+ 7346 y1*3.0f*omt2*t+ 7347 y2*3.0f*omt*t2+ 7348 y3*t3; 7349 7350 immutable float tx = 7351 x0*omt3+ 7352 x1*3.0f*omt2*t+ 7353 x2*3.0f*omt*t2+ 7354 x3*t3; 7355 7356 closest[0] = tx; 7357 closest[1] = ty; 7358 } 7359 } 7360 7361 // Returns: 7362 // 1 If (x,y) is contained by the stroke of the path 7363 // 0 If (x,y) is not contained by the path. 7364 int nvg__pickSubPathStroke (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, float strokeWidth, int lineCap, int lineJoin) { 7365 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0; 7366 if (psp.firstSegment == -1) return 0; 7367 7368 float[2] closest = void; 7369 float[2] d = void; 7370 float t = void; 7371 7372 // trace a line from x,y out along the positive x axis and count the number of intersections 7373 int nsegments = psp.nsegments; 7374 const(NVGsegment)* seg = ps.segments+psp.firstSegment; 7375 const(NVGsegment)* prevseg = (psp.closed ? &ps.segments[psp.firstSegment+nsegments-1] : null); 7376 immutable float strokeWidthSqd = strokeWidth*strokeWidth; 7377 7378 for (int s = 0; s < nsegments; ++s, prevseg = seg, ++seg) { 7379 if (nvg__pointInBounds(x, y, seg.bounds)) { 7380 // Line potentially hits stroke. 7381 switch (seg.type) { 7382 case Command.LineTo: 7383 nvg__closestLine(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t); 7384 break; 7385 case Command.BezierTo: 7386 nvg__closestBezier(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t); 7387 break; 7388 default: 7389 continue; 7390 } 7391 7392 d.ptr[0] = x-closest.ptr[0]; 7393 d.ptr[1] = y-closest.ptr[1]; 7394 7395 if ((t >= NVGPickEPS && t <= 1.0f-NVGPickEPS) || 7396 (seg.flags&(NVGSegmentFlags.Corner|NVGSegmentFlags.Cap|NVGSegmentFlags.Endcap)) == 0 || 7397 (lineJoin == NVGLineCap.Round)) 7398 { 7399 // Closest point is in the middle of the line/curve, at a rounded join/cap 7400 // or at a smooth join 7401 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7402 if (distSqd < strokeWidthSqd) return 1; 7403 } else if ((t > 1.0f-NVGPickEPS && (seg.flags&NVGSegmentFlags.Endcap)) || 7404 (t < NVGPickEPS && (seg.flags&NVGSegmentFlags.Cap))) { 7405 switch (lineCap) { 7406 case NVGLineCap.Butt: 7407 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7408 immutable float dirD = (t < NVGPickEPS ? 7409 -(d.ptr[0]*seg.startDir.ptr[0]+d.ptr[1]*seg.startDir.ptr[1]) : 7410 d.ptr[0]*seg.endDir.ptr[0]+d.ptr[1]*seg.endDir.ptr[1]); 7411 if (dirD < -NVGPickEPS && distSqd < strokeWidthSqd) return 1; 7412 break; 7413 case NVGLineCap.Square: 7414 if (nvg__absf(d.ptr[0]) < strokeWidth && nvg__absf(d.ptr[1]) < strokeWidth) return 1; 7415 break; 7416 case NVGLineCap.Round: 7417 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7418 if (distSqd < strokeWidthSqd) return 1; 7419 break; 7420 default: 7421 break; 7422 } 7423 } else if (seg.flags&NVGSegmentFlags.Corner) { 7424 // Closest point is at a corner 7425 const(NVGsegment)* seg0, seg1; 7426 7427 if (t < NVGPickEPS) { 7428 seg0 = prevseg; 7429 seg1 = seg; 7430 } else { 7431 seg0 = seg; 7432 seg1 = (s == nsegments-1 ? &ps.segments[psp.firstSegment] : seg+1); 7433 } 7434 7435 if (!(seg1.flags&NVGSegmentFlags.Bevel)) { 7436 immutable float prevNDist = -seg0.endDir.ptr[1]*d.ptr[0]+seg0.endDir.ptr[0]*d.ptr[1]; 7437 immutable float curNDist = seg1.startDir.ptr[1]*d.ptr[0]-seg1.startDir.ptr[0]*d.ptr[1]; 7438 if (nvg__absf(prevNDist) < strokeWidth && nvg__absf(curNDist) < strokeWidth) return 1; 7439 } else { 7440 d.ptr[0] -= -seg1.startDir.ptr[1]*strokeWidth; 7441 d.ptr[1] -= +seg1.startDir.ptr[0]*strokeWidth; 7442 if (seg1.miterDir.ptr[0]*d.ptr[0]+seg1.miterDir.ptr[1]*d.ptr[1] < 0) return 1; 7443 } 7444 } 7445 } 7446 } 7447 7448 return 0; 7449 } 7450 7451 // Returns: 7452 // 1 If (x,y) is contained by the path and the path is solid. 7453 // -1 If (x,y) is contained by the path and the path is a hole. 7454 // 0 If (x,y) is not contained by the path. 7455 int nvg__pickSubPath (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, bool evenOddMode) { 7456 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0; 7457 if (psp.firstSegment == -1) return 0; 7458 7459 const(NVGsegment)* seg = &ps.segments[psp.firstSegment]; 7460 int nsegments = psp.nsegments; 7461 int nintersections = 0; 7462 7463 // trace a line from x,y out along the positive x axis and count the number of intersections 7464 for (int s = 0; s < nsegments; ++s, ++seg) { 7465 if ((seg.bounds.ptr[1]-NVGPickEPS) < y && 7466 (seg.bounds.ptr[3]-NVGPickEPS) > y && 7467 seg.bounds.ptr[2] > x) 7468 { 7469 // Line hits the box. 7470 switch (seg.type) { 7471 case Command.LineTo: 7472 if (seg.bounds.ptr[0] > x) { 7473 // line originates outside the box 7474 ++nintersections; 7475 } else { 7476 // line originates inside the box 7477 nintersections += nvg__intersectLine(&ps.points[seg.firstPoint*2], x, y); 7478 } 7479 break; 7480 case Command.BezierTo: 7481 if (seg.bounds.ptr[0] > x) { 7482 // line originates outside the box 7483 ++nintersections; 7484 } else { 7485 // line originates inside the box 7486 nintersections += nvg__intersectBezier(&ps.points[seg.firstPoint*2], x, y); 7487 } 7488 break; 7489 default: 7490 break; 7491 } 7492 } 7493 } 7494 7495 if (evenOddMode) { 7496 return nintersections; 7497 } else { 7498 return (nintersections&1 ? (psp.winding == NVGSolidity.Solid ? 1 : -1) : 0); 7499 } 7500 } 7501 7502 bool nvg__pickPath (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) { 7503 int pickCount = 0; 7504 const(NVGpickSubPath)* psp = pp.subPaths; 7505 while (psp !is null) { 7506 pickCount += nvg__pickSubPath(ps, psp, x, y, pp.evenOddMode); 7507 psp = psp.next; 7508 } 7509 return ((pp.evenOddMode ? pickCount&1 : pickCount) != 0); 7510 } 7511 7512 bool nvg__pickPathStroke (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) { 7513 const(NVGpickSubPath)* psp = pp.subPaths; 7514 while (psp !is null) { 7515 if (nvg__pickSubPathStroke(ps, psp, x, y, pp.strokeWidth, pp.lineCap, pp.lineJoin)) return true; 7516 psp = psp.next; 7517 } 7518 return false; 7519 } 7520 7521 bool nvg__pickPathTestBounds (NVGContext ctx, const NVGpickScene* ps, const NVGpickPath* pp, float x, float y) { 7522 if (nvg__pointInBounds(x, y, pp.bounds)) { 7523 //{ import core.stdc.stdio; printf(" (0): in bounds!\n"); } 7524 if (pp.flags&NVGPathFlags.Scissor) { 7525 const(float)* scissor = &ps.points[pp.scissor*2]; 7526 // untransform scissor translation 7527 float stx = void, sty = void; 7528 ctx.gpuUntransformPoint(&stx, &sty, scissor[4], scissor[5]); 7529 immutable float rx = x-stx; 7530 immutable float ry = y-sty; 7531 //{ 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]); } 7532 if (nvg__absf((scissor[0]*rx)+(scissor[1]*ry)) > scissor[6] || 7533 nvg__absf((scissor[2]*rx)+(scissor[3]*ry)) > scissor[7]) 7534 { 7535 //{ import core.stdc.stdio; printf(" (1): scissor reject!\n"); } 7536 return false; 7537 } 7538 } 7539 return true; 7540 } 7541 return false; 7542 } 7543 7544 int nvg__countBitsUsed (uint v) pure { 7545 pragma(inline, true); 7546 import core.bitop : bsr; 7547 return (v != 0 ? bsr(v)+1 : 0); 7548 } 7549 7550 void nvg__pickSceneInsert (NVGpickScene* ps, NVGpickPath* pp) { 7551 if (ps is null || pp is null) return; 7552 7553 int[4] cellbounds; 7554 int base = ps.nlevels-1; 7555 int level; 7556 int levelwidth; 7557 int levelshift; 7558 int levelx; 7559 int levely; 7560 NVGpickPath** cell = null; 7561 7562 // Bit tricks for inserting into an implicit quadtree. 7563 7564 // Calc bounds of path in cells at the lowest level 7565 cellbounds.ptr[0] = cast(int)(pp.bounds.ptr[0]/ps.xdim); 7566 cellbounds.ptr[1] = cast(int)(pp.bounds.ptr[1]/ps.ydim); 7567 cellbounds.ptr[2] = cast(int)(pp.bounds.ptr[2]/ps.xdim); 7568 cellbounds.ptr[3] = cast(int)(pp.bounds.ptr[3]/ps.ydim); 7569 7570 // Find which bits differ between the min/max x/y coords 7571 cellbounds.ptr[0] ^= cellbounds.ptr[2]; 7572 cellbounds.ptr[1] ^= cellbounds.ptr[3]; 7573 7574 // Use the number of bits used (countBitsUsed(x) == sizeof(int) * 8 - clz(x); 7575 // to calculate the level to insert at (the level at which the bounds fit in a single cell) 7576 level = nvg__min(base-nvg__countBitsUsed(cellbounds.ptr[0]), base-nvg__countBitsUsed(cellbounds.ptr[1])); 7577 if (level < 0) level = 0; 7578 //{ 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]); } 7579 //level = 0; 7580 7581 // Find the correct cell in the chosen level, clamping to the edges. 7582 levelwidth = 1<<level; 7583 levelshift = (ps.nlevels-level)-1; 7584 levelx = nvg__clamp(cellbounds.ptr[2]>>levelshift, 0, levelwidth-1); 7585 levely = nvg__clamp(cellbounds.ptr[3]>>levelshift, 0, levelwidth-1); 7586 7587 // Insert the path into the linked list at that cell. 7588 cell = &ps.levels[level][levely*levelwidth+levelx]; 7589 7590 pp.cellnext = *cell; 7591 *cell = pp; 7592 7593 if (ps.paths is null) ps.lastPath = pp; 7594 pp.next = ps.paths; 7595 ps.paths = pp; 7596 7597 // Store the order (depth) of the path for picking ops. 7598 pp.order = cast(short)ps.npaths; 7599 ++ps.npaths; 7600 } 7601 7602 void nvg__pickBeginFrame (NVGContext ctx, int width, int height) { 7603 NVGpickScene* ps = nvg__pickSceneGet(ctx); 7604 7605 //NVG_PICK_DEBUG_NEWFRAME(); 7606 7607 // Return all paths & sub paths from last frame to the free list 7608 while (ps.paths !is null) { 7609 NVGpickPath* pp = ps.paths.next; 7610 nvg__freePickPath(ps, ps.paths); 7611 ps.paths = pp; 7612 } 7613 7614 ps.paths = null; 7615 ps.npaths = 0; 7616 7617 // Store the screen metrics for the quadtree 7618 ps.width = width; 7619 ps.height = height; 7620 7621 immutable float lowestSubDiv = cast(float)(1<<(ps.nlevels-1)); 7622 ps.xdim = cast(float)width/lowestSubDiv; 7623 ps.ydim = cast(float)height/lowestSubDiv; 7624 7625 // Allocate the quadtree if required. 7626 if (ps.levels is null) { 7627 int ncells = 1; 7628 7629 ps.levels = cast(NVGpickPath***)malloc((NVGpickPath**).sizeof*ps.nlevels); 7630 for (int l = 0; l < ps.nlevels; ++l) { 7631 int leveldim = 1<<l; 7632 ncells += leveldim*leveldim; 7633 } 7634 7635 ps.levels[0] = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ncells); 7636 7637 int cell = 1; 7638 for (int l = 1; l < ps.nlevels; ++l) { 7639 ps.levels[l] = &ps.levels[0][cell]; 7640 int leveldim = 1<<l; 7641 cell += leveldim*leveldim; 7642 } 7643 7644 ps.ncells = ncells; 7645 } 7646 memset(ps.levels[0], 0, ps.ncells*(NVGpickPath*).sizeof); 7647 7648 // Allocate temporary storage for nvgHitTestAll results if required. 7649 if (ps.picked is null) { 7650 ps.cpicked = 16; 7651 ps.picked = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ps.cpicked); 7652 } 7653 7654 ps.npoints = 0; 7655 ps.nsegments = 0; 7656 } 7657 } // nothrow @trusted @nogc 7658 7659 7660 /// Return outline of the current path. Returned outline is not flattened. 7661 /// Group: paths 7662 public NVGPathOutline getCurrPathOutline (NVGContext ctx) nothrow @trusted @nogc { 7663 if (ctx is null || !ctx.contextAlive || ctx.ncommands == 0) return NVGPathOutline.init; 7664 7665 auto res = NVGPathOutline.createNew(); 7666 7667 const(float)[] acommands = ctx.commands[0..ctx.ncommands]; 7668 int ncommands = cast(int)acommands.length; 7669 const(float)* commands = acommands.ptr; 7670 7671 float cx = 0, cy = 0; 7672 float[2] start = void; 7673 float[4] totalBounds = [float.max, float.max, -float.max, -float.max]; 7674 float[8] bcp = void; // bezier curve points; used to calculate bounds 7675 7676 void addToBounds (in float x, in float y) nothrow @trusted @nogc { 7677 totalBounds.ptr[0] = nvg__min(totalBounds.ptr[0], x); 7678 totalBounds.ptr[1] = nvg__min(totalBounds.ptr[1], y); 7679 totalBounds.ptr[2] = nvg__max(totalBounds.ptr[2], x); 7680 totalBounds.ptr[3] = nvg__max(totalBounds.ptr[3], y); 7681 } 7682 7683 bool hasPoints = false; 7684 7685 void closeIt () nothrow @trusted @nogc { 7686 if (!hasPoints) return; 7687 if (cx != start.ptr[0] || cy != start.ptr[1]) { 7688 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo); 7689 res.ds.putArgs(start[]); 7690 cx = start.ptr[0]; 7691 cy = start.ptr[1]; 7692 addToBounds(cx, cy); 7693 } 7694 } 7695 7696 int i = 0; 7697 while (i < ncommands) { 7698 int cmd = cast(int)commands[i++]; 7699 switch (cmd) { 7700 case Command.MoveTo: // one coordinate pair 7701 const(float)* tfxy = commands+i; 7702 i += 2; 7703 // add command 7704 res.ds.putCommand(NVGPathOutline.Command.Kind.MoveTo); 7705 res.ds.putArgs(tfxy[0..2]); 7706 // new starting point 7707 start.ptr[0..2] = tfxy[0..2]; 7708 cx = tfxy[0]; 7709 cy = tfxy[0]; 7710 addToBounds(cx, cy); 7711 hasPoints = true; 7712 break; 7713 case Command.LineTo: // one coordinate pair 7714 const(float)* tfxy = commands+i; 7715 i += 2; 7716 // add command 7717 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo); 7718 res.ds.putArgs(tfxy[0..2]); 7719 cx = tfxy[0]; 7720 cy = tfxy[0]; 7721 addToBounds(cx, cy); 7722 hasPoints = true; 7723 break; 7724 case Command.BezierTo: // three coordinate pairs 7725 const(float)* tfxy = commands+i; 7726 i += 3*2; 7727 // add command 7728 res.ds.putCommand(NVGPathOutline.Command.Kind.BezierTo); 7729 res.ds.putArgs(tfxy[0..6]); 7730 // bounds 7731 bcp.ptr[0] = cx; 7732 bcp.ptr[1] = cy; 7733 bcp.ptr[2..8] = tfxy[0..6]; 7734 nvg__bezierBounds(bcp.ptr, totalBounds); 7735 cx = tfxy[4]; 7736 cy = tfxy[5]; 7737 hasPoints = true; 7738 break; 7739 case Command.Close: 7740 closeIt(); 7741 hasPoints = false; 7742 break; 7743 case Command.Winding: 7744 //psp.winding = cast(short)cast(int)commands[i]; 7745 i += 1; 7746 break; 7747 default: 7748 break; 7749 } 7750 } 7751 7752 res.ds.bounds[] = totalBounds[]; 7753 return res; 7754 } 7755 7756 7757 // ////////////////////////////////////////////////////////////////////////// // 7758 // Text 7759 7760 /** Creates font by loading it from the disk from specified file name. 7761 * Returns handle to the font or FONS_INVALID (aka -1) on error. 7762 * Use "fontname:noaa" as [name] to turn off antialiasing (if font driver supports that). 7763 * 7764 * On POSIX systems it is possible to use fontconfig font names too. 7765 * `:noaa` in font path is still allowed, but it must be the last option. 7766 * 7767 * Group: text_api 7768 */ 7769 public int createFont (NVGContext ctx, const(char)[] name, const(char)[] path) nothrow @trusted { 7770 return ctx.fs.addFont(name, path, ctx.params.fontAA); 7771 } 7772 7773 /** Creates font by loading it from the specified memory chunk. 7774 * Returns handle to the font or FONS_INVALID (aka -1) on error. 7775 * Won't free data on error. 7776 * 7777 * Group: text_api 7778 */ 7779 public int createFontMem (NVGContext ctx, const(char)[] name, ubyte* data, int ndata, bool freeData) nothrow @trusted @nogc { 7780 return ctx.fs.addFontMem(name, data, ndata, freeData, ctx.params.fontAA); 7781 } 7782 7783 /// Add fonts from another context. 7784 /// This is more effective than reloading fonts, 'cause font data will be shared. 7785 /// Group: text_api 7786 public void addFontsFrom (NVGContext ctx, NVGContext source) nothrow @trusted @nogc { 7787 if (ctx is null || source is null) return; 7788 ctx.fs.addFontsFrom(source.fs); 7789 } 7790 7791 /// Finds a loaded font of specified name, and returns handle to it, or FONS_INVALID (aka -1) if the font is not found. 7792 /// Group: text_api 7793 public int findFont (NVGContext ctx, const(char)[] name) nothrow @trusted @nogc { 7794 pragma(inline, true); 7795 return (name.length == 0 ? FONS_INVALID : ctx.fs.getFontByName(name)); 7796 } 7797 7798 /// Sets the font size of current text style. 7799 /// Group: text_api 7800 public void fontSize (NVGContext ctx, float size) nothrow @trusted @nogc { 7801 pragma(inline, true); 7802 nvg__getState(ctx).fontSize = size; 7803 } 7804 7805 /// Gets the font size of current text style. 7806 /// Group: text_api 7807 public float fontSize (NVGContext ctx) nothrow @trusted @nogc { 7808 pragma(inline, true); 7809 return nvg__getState(ctx).fontSize; 7810 } 7811 7812 /// Sets the blur of current text style. 7813 /// Group: text_api 7814 public void fontBlur (NVGContext ctx, float blur) nothrow @trusted @nogc { 7815 pragma(inline, true); 7816 nvg__getState(ctx).fontBlur = blur; 7817 } 7818 7819 /// Gets the blur of current text style. 7820 /// Group: text_api 7821 public float fontBlur (NVGContext ctx) nothrow @trusted @nogc { 7822 pragma(inline, true); 7823 return nvg__getState(ctx).fontBlur; 7824 } 7825 7826 /// Sets the letter spacing of current text style. 7827 /// Group: text_api 7828 public void textLetterSpacing (NVGContext ctx, float spacing) nothrow @trusted @nogc { 7829 pragma(inline, true); 7830 nvg__getState(ctx).letterSpacing = spacing; 7831 } 7832 7833 /// Gets the letter spacing of current text style. 7834 /// Group: text_api 7835 public float textLetterSpacing (NVGContext ctx) nothrow @trusted @nogc { 7836 pragma(inline, true); 7837 return nvg__getState(ctx).letterSpacing; 7838 } 7839 7840 /// Sets the proportional line height of current text style. The line height is specified as multiple of font size. 7841 /// Group: text_api 7842 public void textLineHeight (NVGContext ctx, float lineHeight) nothrow @trusted @nogc { 7843 pragma(inline, true); 7844 nvg__getState(ctx).lineHeight = lineHeight; 7845 } 7846 7847 /// Gets the proportional line height of current text style. The line height is specified as multiple of font size. 7848 /// Group: text_api 7849 public float textLineHeight (NVGContext ctx) nothrow @trusted @nogc { 7850 pragma(inline, true); 7851 return nvg__getState(ctx).lineHeight; 7852 } 7853 7854 /// Sets the text align of current text style, see [NVGTextAlign] for options. 7855 /// Group: text_api 7856 public void textAlign (NVGContext ctx, NVGTextAlign talign) nothrow @trusted @nogc { 7857 pragma(inline, true); 7858 nvg__getState(ctx).textAlign = talign; 7859 } 7860 7861 /// Ditto. 7862 public void textAlign (NVGContext ctx, NVGTextAlign.H h) nothrow @trusted @nogc { 7863 pragma(inline, true); 7864 nvg__getState(ctx).textAlign.horizontal = h; 7865 } 7866 7867 /// Ditto. 7868 public void textAlign (NVGContext ctx, NVGTextAlign.V v) nothrow @trusted @nogc { 7869 pragma(inline, true); 7870 nvg__getState(ctx).textAlign.vertical = v; 7871 } 7872 7873 /// Ditto. 7874 public void textAlign (NVGContext ctx, NVGTextAlign.H h, NVGTextAlign.V v) nothrow @trusted @nogc { 7875 pragma(inline, true); 7876 nvg__getState(ctx).textAlign.reset(h, v); 7877 } 7878 7879 /// Ditto. 7880 public void textAlign (NVGContext ctx, NVGTextAlign.V v, NVGTextAlign.H h) nothrow @trusted @nogc { 7881 pragma(inline, true); 7882 nvg__getState(ctx).textAlign.reset(h, v); 7883 } 7884 7885 /// Gets the text align of current text style, see [NVGTextAlign] for options. 7886 /// Group: text_api 7887 public NVGTextAlign textAlign (NVGContext ctx) nothrow @trusted @nogc { 7888 pragma(inline, true); 7889 return nvg__getState(ctx).textAlign; 7890 } 7891 7892 /// Sets the font face based on specified id of current text style. 7893 /// Group: text_api 7894 public void fontFaceId (NVGContext ctx, int font) nothrow @trusted @nogc { 7895 pragma(inline, true); 7896 nvg__getState(ctx).fontId = font; 7897 } 7898 7899 /// Gets the font face based on specified id of current text style. 7900 /// Group: text_api 7901 public int fontFaceId (NVGContext ctx) nothrow @trusted @nogc { 7902 pragma(inline, true); 7903 return nvg__getState(ctx).fontId; 7904 } 7905 7906 /** Sets the font face based on specified name of current text style. 7907 * 7908 * The underlying implementation is using O(1) data structure to lookup 7909 * font names, so you probably should use this function instead of [fontFaceId] 7910 * to make your code more robust and less error-prone. 7911 * 7912 * Group: text_api 7913 */ 7914 public void fontFace (NVGContext ctx, const(char)[] font) nothrow @trusted @nogc { 7915 pragma(inline, true); 7916 nvg__getState(ctx).fontId = ctx.fs.getFontByName(font); 7917 } 7918 7919 static if (is(typeof(&fons__nvg__toPath))) { 7920 public enum NanoVegaHasCharToPath = true; /// 7921 } else { 7922 public enum NanoVegaHasCharToPath = false; /// 7923 } 7924 7925 /// Adds glyph outlines to the current path. Vertical 0 is baseline. 7926 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead. 7927 /// Returns `false` if there is no such glyph, or current font is not scalable. 7928 /// Group: text_api 7929 public bool charToPath (NVGContext ctx, dchar dch, float[] bounds=null) nothrow @trusted @nogc { 7930 NVGstate* state = nvg__getState(ctx); 7931 ctx.fs.fontId = state.fontId; 7932 return ctx.fs.toPath(ctx, dch, bounds); 7933 } 7934 7935 static if (is(typeof(&fons__nvg__bounds))) { 7936 public enum NanoVegaHasCharPathBounds = true; /// 7937 } else { 7938 public enum NanoVegaHasCharPathBounds = false; /// 7939 } 7940 7941 /// Returns bounds of the glyph outlines. Vertical 0 is baseline. 7942 /// The glyph is not scaled in any way. 7943 /// Returns `false` if there is no such glyph, or current font is not scalable. 7944 /// Group: text_api 7945 public bool charPathBounds (NVGContext ctx, dchar dch, float[] bounds) nothrow @trusted @nogc { 7946 NVGstate* state = nvg__getState(ctx); 7947 ctx.fs.fontId = state.fontId; 7948 return ctx.fs.getPathBounds(dch, bounds); 7949 } 7950 7951 /** [charOutline] will return [NVGPathOutline]. 7952 7953 some usage samples: 7954 7955 --- 7956 float[4] bounds = void; 7957 7958 nvg.scale(0.5, 0.5); 7959 nvg.translate(500, 800); 7960 nvg.evenOddFill; 7961 7962 nvg.newPath(); 7963 nvg.charToPath('&', bounds[]); 7964 conwriteln(bounds[]); 7965 nvg.fillPaint(nvg.linearGradient(0, 0, 600, 600, NVGColor("#f70"), NVGColor("#ff0"))); 7966 nvg.strokeColor(NVGColor("#0f0")); 7967 nvg.strokeWidth = 3; 7968 nvg.fill(); 7969 nvg.stroke(); 7970 // glyph bounds 7971 nvg.newPath(); 7972 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); 7973 nvg.strokeColor(NVGColor("#00f")); 7974 nvg.stroke(); 7975 7976 nvg.newPath(); 7977 nvg.charToPath('g', bounds[]); 7978 conwriteln(bounds[]); 7979 nvg.fill(); 7980 nvg.strokeColor(NVGColor("#0f0")); 7981 nvg.stroke(); 7982 // glyph bounds 7983 nvg.newPath(); 7984 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); 7985 nvg.strokeColor(NVGColor("#00f")); 7986 nvg.stroke(); 7987 7988 nvg.newPath(); 7989 nvg.moveTo(0, 0); 7990 nvg.lineTo(600, 0); 7991 nvg.strokeColor(NVGColor("#0ff")); 7992 nvg.stroke(); 7993 7994 if (auto ol = nvg.charOutline('Q')) { 7995 scope(exit) ol.kill(); 7996 nvg.newPath(); 7997 conwriteln("==== length: ", ol.length, " ===="); 7998 foreach (const ref cmd; ol.commands) { 7999 //conwriteln(" ", cmd.code, ": ", cmd.args[]); 8000 assert(cmd.valid); 8001 final switch (cmd.code) { 8002 case cmd.Kind.MoveTo: nvg.moveTo(cmd.args[0], cmd.args[1]); break; 8003 case cmd.Kind.LineTo: nvg.lineTo(cmd.args[0], cmd.args[1]); break; 8004 case cmd.Kind.QuadTo: nvg.quadTo(cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]); break; 8005 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; 8006 } 8007 } 8008 nvg.strokeColor(NVGColor("#f00")); 8009 nvg.stroke(); 8010 } 8011 --- 8012 8013 Group: text_api 8014 */ 8015 public struct NVGPathOutline { 8016 private nothrow @trusted @nogc: 8017 struct DataStore { 8018 uint rc; // refcount 8019 ubyte* data; 8020 uint used; 8021 uint size; 8022 uint ccount; // number of commands 8023 float[4] bounds = 0; /// outline bounds 8024 nothrow @trusted @nogc: 8025 void putBytes (const(void)[] b) { 8026 if (b.length == 0) return; 8027 if (b.length >= int.max/8) assert(0, "NanoVega: out of memory"); 8028 if (int.max/8-used < b.length) assert(0, "NanoVega: out of memory"); 8029 if (used+cast(uint)b.length > size) { 8030 import core.stdc.stdlib : realloc; 8031 uint newsz = size; 8032 while (newsz < used+cast(uint)b.length) newsz = (newsz == 0 ? 1024 : newsz < 32768 ? newsz*2 : newsz+8192); 8033 assert(used+cast(uint)b.length <= newsz); 8034 data = cast(ubyte*)realloc(data, newsz); 8035 if (data is null) assert(0, "NanoVega: out of memory"); 8036 size = newsz; 8037 } 8038 import core.stdc.string : memcpy; 8039 memcpy(data+used, b.ptr, b.length); 8040 used += cast(uint)b.length; 8041 } 8042 void putCommand (ubyte cmd) { pragma(inline, true); ++ccount; putBytes((&cmd)[0..1]); } 8043 void putArgs (const(float)[] f...) { pragma(inline, true); putBytes(f[]); } 8044 } 8045 8046 static void incRef (DataStore* ds) { 8047 pragma(inline, true); 8048 if (ds !is null) { 8049 ++ds.rc; 8050 //{ import core.stdc.stdio; printf("ods(%p): incref: newrc=%u\n", ds, ds.rc); } 8051 } 8052 } 8053 8054 static void decRef (DataStore* ds) { 8055 version(aliced) pragma(inline, true); 8056 if (ds !is null) { 8057 //{ import core.stdc.stdio; printf("ods(%p): decref: newrc=%u\n", ds, ds.rc-1); } 8058 if (--ds.rc == 0) { 8059 import core.stdc.stdlib : free; 8060 import core.stdc.string : memset; 8061 if (ds.data !is null) free(ds.data); 8062 memset(ds, 0, DataStore.sizeof); // just in case 8063 free(ds); 8064 //{ import core.stdc.stdio; printf(" ods(%p): killed.\n"); } 8065 } 8066 } 8067 } 8068 8069 private: 8070 static NVGPathOutline createNew () { 8071 import core.stdc.stdlib : malloc; 8072 import core.stdc.string : memset; 8073 auto ds = cast(DataStore*)malloc(DataStore.sizeof); 8074 if (ds is null) assert(0, "NanoVega: out of memory"); 8075 memset(ds, 0, DataStore.sizeof); 8076 ds.rc = 1; 8077 NVGPathOutline res; 8078 res.dsaddr = cast(usize)ds; 8079 return res; 8080 } 8081 8082 private: 8083 usize dsaddr; // fool GC 8084 8085 @property inout(DataStore)* ds () inout pure { pragma(inline, true); return cast(DataStore*)dsaddr; } 8086 8087 public: 8088 /// commands 8089 static struct Command { 8090 /// 8091 enum Kind : ubyte { 8092 MoveTo, /// 8093 LineTo, /// 8094 QuadTo, /// 8095 BezierTo, /// 8096 End, /// no more commands (this command is not `valid`!) 8097 8098 } 8099 Kind code; /// 8100 const(float)[] args; /// 8101 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (code >= Kind.min && code < Kind.End && args.length >= 2); } /// 8102 8103 static uint arglen (Kind code) pure nothrow @safe @nogc { 8104 pragma(inline, true); 8105 return 8106 code == Kind.MoveTo || code == Kind.LineTo ? 2 : 8107 code == Kind.QuadTo ? 4 : 8108 code == Kind.BezierTo ? 6 : 8109 0; 8110 } 8111 8112 /// perform NanoVega command with stored data. 8113 void perform (NVGContext ctx) const nothrow @trusted @nogc { 8114 if (ctx is null) return; 8115 final switch (code) { 8116 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(args.ptr[0..2]); break; 8117 case Kind.LineTo: if (args.length > 1) ctx.lineTo(args.ptr[0..2]); break; 8118 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(args.ptr[0..4]); break; 8119 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(args.ptr[0..6]); break; 8120 case Kind.End: break; 8121 } 8122 } 8123 8124 /// perform NanoVega command with stored data, transforming points with [xform] transformation matrix. 8125 void perform() (NVGContext ctx, in auto ref NVGMatrix xform) const nothrow @trusted @nogc { 8126 if (ctx is null || !valid) return; 8127 float[6] pts = void; 8128 pts[0..args.length] = args[]; 8129 foreach (immutable pidx; 0..args.length/2) xform.point(pts.ptr[pidx*2+0], pts.ptr[pidx*2+1]); 8130 final switch (code) { 8131 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(pts.ptr[0..2]); break; 8132 case Kind.LineTo: if (args.length > 1) ctx.lineTo(pts.ptr[0..2]); break; 8133 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(pts.ptr[0..4]); break; 8134 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(pts.ptr[0..6]); break; 8135 case Kind.End: break; 8136 } 8137 } 8138 } 8139 8140 public: 8141 /// Create new path with quadratic bezier (first command is MoveTo, second command is QuadTo). 8142 static NVGPathOutline createNewQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) { 8143 auto res = createNew(); 8144 res.ds.putCommand(Command.Kind.MoveTo); 8145 res.ds.putArgs(x0, y0); 8146 res.ds.putCommand(Command.Kind.QuadTo); 8147 res.ds.putArgs(cx, cy, x, y); 8148 return res; 8149 } 8150 8151 /// Create new path with cubic bezier (first command is MoveTo, second command is BezierTo). 8152 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) { 8153 auto res = createNew(); 8154 res.ds.putCommand(Command.Kind.MoveTo); 8155 res.ds.putArgs(x1, y1); 8156 res.ds.putCommand(Command.Kind.BezierTo); 8157 res.ds.putArgs(x2, y2, x3, y3, x4, y4); 8158 return res; 8159 } 8160 8161 public: 8162 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); } 8163 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); } 8164 8165 void opAssign() (in auto ref NVGPathOutline a) { 8166 incRef(cast(DataStore*)a.dsaddr); 8167 decRef(cast(DataStore*)dsaddr); 8168 dsaddr = a.dsaddr; 8169 } 8170 8171 /// Clear storage. 8172 void clear () { 8173 pragma(inline, true); 8174 decRef(ds); 8175 dsaddr = 0; 8176 } 8177 8178 /// Is this outline empty? 8179 @property empty () const pure { pragma(inline, true); return (dsaddr == 0 || ds.ccount == 0); } 8180 8181 /// Returns number of commands in outline. 8182 @property int length () const pure { pragma(inline, true); return (dsaddr ? ds.ccount : 0); } 8183 8184 /// Returns "flattened" path. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8185 NVGPathOutline flatten () const { pragma(inline, true); return flattenInternal(null); } 8186 8187 /// Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8188 NVGPathOutline flatten() (in auto ref NVGMatrix mt) const { pragma(inline, true); return flattenInternal(&mt); } 8189 8190 // Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8191 private NVGPathOutline flattenInternal (scope NVGMatrix* tfm) const { 8192 import core.stdc.string : memset; 8193 8194 NVGPathOutline res; 8195 if (dsaddr == 0 || ds.ccount == 0) { res = this; return res; } // nothing to do 8196 8197 // check if we need to flatten the path 8198 if (tfm is null) { 8199 bool dowork = false; 8200 foreach (const ref cs; commands) { 8201 if (cs.code != Command.Kind.MoveTo && cs.code != Command.Kind.LineTo) { 8202 dowork = true; 8203 break; 8204 } 8205 } 8206 if (!dowork) { res = this; return res; } // nothing to do 8207 } 8208 8209 NVGcontextinternal ctx; 8210 memset(&ctx, 0, ctx.sizeof); 8211 ctx.cache = nvg__allocPathCache(); 8212 scope(exit) { 8213 import core.stdc.stdlib : free; 8214 nvg__deletePathCache(ctx.cache); 8215 } 8216 8217 ctx.tessTol = 0.25f; 8218 ctx.angleTol = 0; // 0 -- angle tolerance for McSeem Bezier rasterizer 8219 ctx.cuspLimit = 0; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps) 8220 ctx.distTol = 0.01f; 8221 ctx.tesselatortype = NVGTesselation.DeCasteljau; 8222 8223 nvg__addPath(&ctx); // we need this for `nvg__addPoint()` 8224 8225 // has some curves or transformations, convert path 8226 res = createNew(); 8227 float[8] args = void; 8228 8229 res.ds.bounds = [float.max, float.max, -float.max, -float.max]; 8230 8231 float lastX = float.max, lastY = float.max; 8232 bool lastWasMove = false; 8233 8234 void addPoint (float x, float y, Command.Kind cmd=Command.Kind.LineTo) nothrow @trusted @nogc { 8235 if (tfm !is null) tfm.point(x, y); 8236 bool isMove = (cmd == Command.Kind.MoveTo); 8237 if (isMove) { 8238 // moveto 8239 if (lastWasMove && nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return; 8240 } else { 8241 // lineto 8242 if (nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return; 8243 } 8244 lastWasMove = isMove; 8245 lastX = x; 8246 lastY = y; 8247 res.ds.putCommand(cmd); 8248 res.ds.putArgs(x, y); 8249 res.ds.bounds.ptr[0] = nvg__min(res.ds.bounds.ptr[0], x); 8250 res.ds.bounds.ptr[1] = nvg__min(res.ds.bounds.ptr[1], y); 8251 res.ds.bounds.ptr[2] = nvg__max(res.ds.bounds.ptr[2], x); 8252 res.ds.bounds.ptr[3] = nvg__max(res.ds.bounds.ptr[3], y); 8253 } 8254 8255 // sorry for this pasta 8256 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 { 8257 ctx.cache.npoints = 0; 8258 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) { 8259 nvg__tesselateBezier(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner); 8260 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) { 8261 nvg__tesselateBezierMcSeem(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner); 8262 } else { 8263 nvg__tesselateBezierAFD(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, PointFlag.Corner); 8264 } 8265 // add generated points 8266 foreach (const ref pt; ctx.cache.points[0..ctx.cache.npoints]) addPoint(pt.x, pt.y); 8267 } 8268 8269 void flattenQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) { 8270 flattenBezier( 8271 x0, y0, 8272 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 8273 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 8274 x, y, 8275 0, 8276 ); 8277 } 8278 8279 float cx = 0, cy = 0; 8280 foreach (const ref cs; commands) { 8281 switch (cs.code) { 8282 case Command.Kind.LineTo: 8283 case Command.Kind.MoveTo: 8284 addPoint(cs.args[0], cs.args[1], cs.code); 8285 cx = cs.args[0]; 8286 cy = cs.args[1]; 8287 break; 8288 case Command.Kind.QuadTo: 8289 flattenQuad(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3]); 8290 cx = cs.args[2]; 8291 cy = cs.args[3]; 8292 break; 8293 case Command.Kind.BezierTo: 8294 flattenBezier(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3], cs.args[4], cs.args[5], 0); 8295 cx = cs.args[4]; 8296 cy = cs.args[5]; 8297 break; 8298 default: 8299 break; 8300 } 8301 } 8302 8303 return res; 8304 } 8305 8306 /// Returns forward range with all glyph commands. 8307 auto commands () const nothrow @trusted @nogc { 8308 static struct Range { 8309 private nothrow @trusted @nogc: 8310 usize dsaddr; 8311 uint cpos; // current position in data 8312 uint cleft; // number of commands left 8313 @property const(ubyte)* data () inout pure { pragma(inline, true); return (dsaddr ? (cast(DataStore*)dsaddr).data : null); } 8314 public: 8315 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); } 8316 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); } 8317 void opAssign() (in auto ref Range a) { 8318 incRef(cast(DataStore*)a.dsaddr); 8319 decRef(cast(DataStore*)dsaddr); 8320 dsaddr = a.dsaddr; 8321 cpos = a.cpos; 8322 cleft = a.cleft; 8323 } 8324 float[4] bounds () const pure { float[4] res = 0; pragma(inline, true); if (dsaddr) res[] = (cast(DataStore*)dsaddr).bounds[]; return res; } /// outline bounds 8325 @property bool empty () const pure { pragma(inline, true); return (cleft == 0); } 8326 @property int length () const pure { pragma(inline, true); return cleft; } 8327 @property Range save () const { pragma(inline, true); Range res = this; return res; } 8328 @property Command front () const { 8329 Command res = void; 8330 if (cleft > 0) { 8331 res.code = cast(Command.Kind)data[cpos]; 8332 switch (res.code) { 8333 case Command.Kind.MoveTo: 8334 case Command.Kind.LineTo: 8335 res.args = (cast(const(float*))(data+cpos+1))[0..1*2]; 8336 break; 8337 case Command.Kind.QuadTo: 8338 res.args = (cast(const(float*))(data+cpos+1))[0..2*2]; 8339 break; 8340 case Command.Kind.BezierTo: 8341 res.args = (cast(const(float*))(data+cpos+1))[0..3*2]; 8342 break; 8343 default: 8344 res.code = Command.Kind.End; 8345 res.args = null; 8346 break; 8347 } 8348 } else { 8349 res.code = Command.Kind.End; 8350 res.args = null; 8351 } 8352 return res; 8353 } 8354 void popFront () { 8355 if (cleft <= 1) { cleft = 0; return; } // don't waste time skipping last command 8356 --cleft; 8357 switch (data[cpos]) { 8358 case Command.Kind.MoveTo: 8359 case Command.Kind.LineTo: 8360 cpos += 1+1*2*cast(uint)float.sizeof; 8361 break; 8362 case Command.Kind.QuadTo: 8363 cpos += 1+2*2*cast(uint)float.sizeof; 8364 break; 8365 case Command.Kind.BezierTo: 8366 cpos += 1+3*2*cast(uint)float.sizeof; 8367 break; 8368 default: 8369 cleft = 0; 8370 break; 8371 } 8372 } 8373 } 8374 if (dsaddr) { 8375 incRef(cast(DataStore*)dsaddr); // range anchors it 8376 return Range(dsaddr, 0, ds.ccount); 8377 } else { 8378 return Range.init; 8379 } 8380 } 8381 } 8382 8383 public alias NVGGlyphOutline = NVGPathOutline; /// For backwards compatibility. 8384 8385 /// Destroy glyph outiline and free allocated memory. 8386 /// Group: text_api 8387 public void kill (ref NVGPathOutline ol) nothrow @trusted @nogc { 8388 pragma(inline, true); 8389 ol.clear(); 8390 } 8391 8392 static if (is(typeof(&fons__nvg__toOutline))) { 8393 public enum NanoVegaHasCharOutline = true; /// 8394 } else { 8395 public enum NanoVegaHasCharOutline = false; /// 8396 } 8397 8398 /// Returns glyph outlines as array of commands. Vertical 0 is baseline. 8399 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead. 8400 /// Returns `null` if there is no such glyph, or current font is not scalable. 8401 /// Group: text_api 8402 public NVGPathOutline charOutline (NVGContext ctx, dchar dch) nothrow @trusted @nogc { 8403 import core.stdc.stdlib : malloc; 8404 import core.stdc.string : memcpy; 8405 NVGstate* state = nvg__getState(ctx); 8406 ctx.fs.fontId = state.fontId; 8407 auto oline = NVGPathOutline.createNew(); 8408 if (!ctx.fs.toOutline(dch, oline.ds)) oline.clear(); 8409 return oline; 8410 } 8411 8412 8413 float nvg__quantize (float a, float d) pure nothrow @safe @nogc { 8414 pragma(inline, true); 8415 return (cast(int)(a/d+0.5f))*d; 8416 } 8417 8418 float nvg__getFontScale (NVGstate* state) /*pure*/ nothrow @safe @nogc { 8419 pragma(inline, true); 8420 return nvg__min(nvg__quantize(nvg__getAverageScale(state.xform), 0.01f), 4.0f); 8421 } 8422 8423 void nvg__flushTextTexture (NVGContext ctx) nothrow @trusted @nogc { 8424 int[4] dirty = void; 8425 if (ctx.fs.validateTexture(dirty.ptr)) { 8426 auto fontImage = &ctx.fontImages[ctx.fontImageIdx]; 8427 // Update texture 8428 if (fontImage.valid) { 8429 int iw, ih; 8430 const(ubyte)* data = ctx.fs.getTextureData(&iw, &ih); 8431 int x = dirty[0]; 8432 int y = dirty[1]; 8433 int w = dirty[2]-dirty[0]; 8434 int h = dirty[3]-dirty[1]; 8435 ctx.params.renderUpdateTexture(ctx.params.userPtr, fontImage.id, x, y, w, h, data); 8436 } 8437 } 8438 } 8439 8440 bool nvg__allocTextAtlas (NVGContext ctx) nothrow @trusted @nogc { 8441 int iw, ih; 8442 nvg__flushTextTexture(ctx); 8443 if (ctx.fontImageIdx >= NVG_MAX_FONTIMAGES-1) return false; 8444 // if next fontImage already have a texture 8445 if (ctx.fontImages[ctx.fontImageIdx+1].valid) { 8446 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx+1], iw, ih); 8447 } else { 8448 // calculate the new font image size and create it 8449 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx], iw, ih); 8450 if (iw > ih) ih *= 2; else iw *= 2; 8451 if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE; 8452 ctx.fontImages[ctx.fontImageIdx+1].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, iw, ih, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); 8453 if (ctx.fontImages[ctx.fontImageIdx+1].id > 0) { 8454 ctx.fontImages[ctx.fontImageIdx+1].ctx = ctx; 8455 ctx.nvg__imageIncRef(ctx.fontImages[ctx.fontImageIdx+1].id, false); // don't increment driver refcount 8456 } 8457 } 8458 ++ctx.fontImageIdx; 8459 ctx.fs.resetAtlas(iw, ih); 8460 return true; 8461 } 8462 8463 void nvg__renderText (NVGContext ctx, NVGVertex* verts, int nverts) nothrow @trusted @nogc { 8464 NVGstate* state = nvg__getState(ctx); 8465 NVGPaint paint = state.fill; 8466 8467 // Render triangles. 8468 paint.image = ctx.fontImages[ctx.fontImageIdx]; 8469 8470 // Apply global alpha 8471 paint.innerColor.a *= state.alpha; 8472 paint.middleColor.a *= state.alpha; 8473 paint.outerColor.a *= state.alpha; 8474 8475 ctx.params.renderTriangles(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &paint, &state.scissor, verts, nverts); 8476 8477 ++ctx.drawCallCount; 8478 ctx.textTriCount += nverts/3; 8479 } 8480 8481 /// Draws text string at specified location. Returns next x position. 8482 /// Group: text_api 8483 public float text(T) (NVGContext ctx, float x, float y, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 8484 NVGstate* state = nvg__getState(ctx); 8485 FONSTextIter!T iter, prevIter; 8486 FONSQuad q; 8487 NVGVertex* verts; 8488 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8489 float invscale = 1.0f/scale; 8490 int cverts = 0; 8491 int nverts = 0; 8492 8493 if (state.fontId == FONS_INVALID) return x; 8494 if (str.length == 0) return x; 8495 8496 ctx.fs.size = state.fontSize*scale; 8497 ctx.fs.spacing = state.letterSpacing*scale; 8498 ctx.fs.blur = state.fontBlur*scale; 8499 ctx.fs.textAlign = state.textAlign; 8500 ctx.fs.fontId = state.fontId; 8501 8502 cverts = nvg__max(2, cast(int)(str.length))*6; // conservative estimate 8503 verts = nvg__allocTempVerts(ctx, cverts); 8504 if (verts is null) return x; 8505 8506 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Required)) return x; 8507 prevIter = iter; 8508 while (iter.next(q)) { 8509 float[4*2] c = void; 8510 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8511 if (nverts != 0) { 8512 // TODO: add back-end bit to do this just once per frame 8513 nvg__flushTextTexture(ctx); 8514 nvg__renderText(ctx, verts, nverts); 8515 nverts = 0; 8516 } 8517 if (!nvg__allocTextAtlas(ctx)) break; // no memory :( 8518 iter = prevIter; 8519 iter.next(q); // try again 8520 if (iter.prevGlyphIndex < 0) { 8521 // still can not find glyph, try replacement 8522 iter = prevIter; 8523 if (!iter.getDummyChar(q)) break; 8524 } 8525 } 8526 prevIter = iter; 8527 // transform corners 8528 state.xform.point(&c[0], &c[1], q.x0*invscale, q.y0*invscale); 8529 state.xform.point(&c[2], &c[3], q.x1*invscale, q.y0*invscale); 8530 state.xform.point(&c[4], &c[5], q.x1*invscale, q.y1*invscale); 8531 state.xform.point(&c[6], &c[7], q.x0*invscale, q.y1*invscale); 8532 // create triangles 8533 if (nverts+6 <= cverts) { 8534 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts; 8535 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts; 8536 nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); ++nverts; 8537 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts; 8538 nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); ++nverts; 8539 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts; 8540 } 8541 } 8542 8543 // TODO: add back-end bit to do this just once per frame 8544 if (nverts > 0) { 8545 nvg__flushTextTexture(ctx); 8546 nvg__renderText(ctx, verts, nverts); 8547 } 8548 8549 return iter.nextx/scale; 8550 } 8551 8552 /** Draws multi-line text string at specified location wrapped at the specified width. 8553 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8554 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8555 * 8556 * Group: text_api 8557 */ 8558 public void textBox(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 8559 NVGstate* state = nvg__getState(ctx); 8560 if (state.fontId == FONS_INVALID) return; 8561 8562 NVGTextRow!T[2] rows; 8563 auto oldAlign = state.textAlign; 8564 scope(exit) state.textAlign = oldAlign; 8565 auto halign = state.textAlign.horizontal; 8566 float lineh = 0; 8567 8568 ctx.textMetrics(null, null, &lineh); 8569 state.textAlign.horizontal = NVGTextAlign.H.Left; 8570 for (;;) { 8571 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]); 8572 //{ import core.stdc.stdio : printf; printf("slen=%u; rlen=%u; bw=%f\n", cast(uint)str.length, cast(uint)rres.length, cast(double)breakRowWidth); } 8573 if (rres.length == 0) break; 8574 foreach (ref row; rres) { 8575 final switch (halign) { 8576 case NVGTextAlign.H.Left: ctx.text(x, y, row.row); break; 8577 case NVGTextAlign.H.Center: ctx.text(x+breakRowWidth*0.5f-row.width*0.5f, y, row.row); break; 8578 case NVGTextAlign.H.Right: ctx.text(x+breakRowWidth-row.width, y, row.row); break; 8579 } 8580 y += lineh*state.lineHeight; 8581 } 8582 str = rres[$-1].rest; 8583 } 8584 } 8585 8586 private template isGoodPositionDelegate(DG) { 8587 private DG dg; 8588 static if (is(typeof({ NVGGlyphPosition pos; bool res = dg(pos); })) || 8589 is(typeof({ NVGGlyphPosition pos; dg(pos); }))) 8590 enum isGoodPositionDelegate = true; 8591 else 8592 enum isGoodPositionDelegate = false; 8593 } 8594 8595 /** Calculates the glyph x positions of the specified text. 8596 * Measured values are returned in local coordinate space. 8597 * 8598 * Group: text_api 8599 */ 8600 public NVGGlyphPosition[] textGlyphPositions(T) (NVGContext ctx, float x, float y, const(T)[] str, NVGGlyphPosition[] positions) nothrow @trusted @nogc 8601 if (isAnyCharType!T) 8602 { 8603 if (str.length == 0 || positions.length == 0) return positions[0..0]; 8604 usize posnum; 8605 auto len = ctx.textGlyphPositions(x, y, str, (in ref NVGGlyphPosition pos) { 8606 positions.ptr[posnum++] = pos; 8607 return (posnum < positions.length); 8608 }); 8609 return positions[0..len]; 8610 } 8611 8612 /// Ditto. 8613 public int textGlyphPositions(T, DG) (NVGContext ctx, float x, float y, const(T)[] str, scope DG dg) 8614 if (isAnyCharType!T && isGoodPositionDelegate!DG) 8615 { 8616 import std.traits : ReturnType; 8617 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true; 8618 8619 NVGstate* state = nvg__getState(ctx); 8620 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8621 float invscale = 1.0f/scale; 8622 FONSTextIter!T iter, prevIter; 8623 FONSQuad q; 8624 int npos = 0; 8625 8626 if (str.length == 0) return 0; 8627 8628 ctx.fs.size = state.fontSize*scale; 8629 ctx.fs.spacing = state.letterSpacing*scale; 8630 ctx.fs.blur = state.fontBlur*scale; 8631 ctx.fs.textAlign = state.textAlign; 8632 ctx.fs.fontId = state.fontId; 8633 8634 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Optional)) return npos; 8635 prevIter = iter; 8636 while (iter.next(q)) { 8637 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8638 if (!nvg__allocTextAtlas(ctx)) break; // no memory 8639 iter = prevIter; 8640 iter.next(q); // try again 8641 if (iter.prevGlyphIndex < 0) { 8642 // still can not find glyph, try replacement 8643 iter = prevIter; 8644 if (!iter.getDummyChar(q)) break; 8645 } 8646 } 8647 prevIter = iter; 8648 NVGGlyphPosition position = void; //WARNING! 8649 position.strpos = cast(usize)(iter.stringp-str.ptr); 8650 position.x = iter.x*invscale; 8651 position.minx = nvg__min(iter.x, q.x0)*invscale; 8652 position.maxx = nvg__max(iter.nextx, q.x1)*invscale; 8653 ++npos; 8654 static if (RetBool) { if (!dg(position)) return npos; } else dg(position); 8655 } 8656 8657 return npos; 8658 } 8659 8660 private template isGoodRowDelegate(CT, DG) { 8661 private DG dg; 8662 static if (is(typeof({ NVGTextRow!CT row; bool res = dg(row); })) || 8663 is(typeof({ NVGTextRow!CT row; dg(row); }))) 8664 enum isGoodRowDelegate = true; 8665 else 8666 enum isGoodRowDelegate = false; 8667 } 8668 8669 /** Breaks the specified text into lines. 8670 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8671 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8672 * 8673 * Group: text_api 8674 */ 8675 public NVGTextRow!T[] textBreakLines(T) (NVGContext ctx, const(T)[] str, float breakRowWidth, NVGTextRow!T[] rows) nothrow @trusted @nogc 8676 if (isAnyCharType!T) 8677 { 8678 if (rows.length == 0) return rows; 8679 if (rows.length > int.max-1) rows = rows[0..int.max-1]; 8680 int nrow = 0; 8681 auto count = ctx.textBreakLines(str, breakRowWidth, (in ref NVGTextRow!T row) { 8682 rows[nrow++] = row; 8683 return (nrow < rows.length); 8684 }); 8685 return rows[0..count]; 8686 } 8687 8688 /** Breaks the specified text into lines. 8689 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8690 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8691 * Returns number of rows. 8692 * 8693 * Group: text_api 8694 */ 8695 public int textBreakLines(T, DG) (NVGContext ctx, const(T)[] str, float breakRowWidth, scope DG dg) 8696 if (isAnyCharType!T && isGoodRowDelegate!(T, DG)) 8697 { 8698 import std.traits : ReturnType; 8699 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true; 8700 8701 enum NVGcodepointType : int { 8702 Space, 8703 NewLine, 8704 Char, 8705 } 8706 8707 NVGstate* state = nvg__getState(ctx); 8708 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8709 float invscale = 1.0f/scale; 8710 FONSTextIter!T iter, prevIter; 8711 FONSQuad q; 8712 int nrows = 0; 8713 float rowStartX = 0; 8714 float rowWidth = 0; 8715 float rowMinX = 0; 8716 float rowMaxX = 0; 8717 int rowStart = 0; 8718 int rowEnd = 0; 8719 int wordStart = 0; 8720 float wordStartX = 0; 8721 float wordMinX = 0; 8722 int breakEnd = 0; 8723 float breakWidth = 0; 8724 float breakMaxX = 0; 8725 int type = NVGcodepointType.Space, ptype = NVGcodepointType.Space; 8726 uint pcodepoint = 0; 8727 8728 if (state.fontId == FONS_INVALID) return 0; 8729 if (str.length == 0 || dg is null) return 0; 8730 8731 ctx.fs.size = state.fontSize*scale; 8732 ctx.fs.spacing = state.letterSpacing*scale; 8733 ctx.fs.blur = state.fontBlur*scale; 8734 ctx.fs.textAlign = state.textAlign; 8735 ctx.fs.fontId = state.fontId; 8736 8737 breakRowWidth *= scale; 8738 8739 enum Phase { 8740 Normal, // searching for breaking point 8741 SkipBlanks, // skip leading blanks 8742 } 8743 Phase phase = Phase.SkipBlanks; // don't skip blanks on first line 8744 8745 if (!iter.setup(ctx.fs, 0, 0, str, FONSBitmapFlag.Optional)) return 0; 8746 prevIter = iter; 8747 while (iter.next(q)) { 8748 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8749 if (!nvg__allocTextAtlas(ctx)) break; // no memory 8750 iter = prevIter; 8751 iter.next(q); // try again 8752 if (iter.prevGlyphIndex < 0) { 8753 // still can not find glyph, try replacement 8754 iter = prevIter; 8755 if (!iter.getDummyChar(q)) break; 8756 } 8757 } 8758 prevIter = iter; 8759 switch (iter.codepoint) { 8760 case 9: // \t 8761 case 11: // \v 8762 case 12: // \f 8763 case 32: // space 8764 case 0x00a0: // NBSP 8765 type = NVGcodepointType.Space; 8766 break; 8767 case 10: // \n 8768 type = (pcodepoint == 13 ? NVGcodepointType.Space : NVGcodepointType.NewLine); 8769 break; 8770 case 13: // \r 8771 type = (pcodepoint == 10 ? NVGcodepointType.Space : NVGcodepointType.NewLine); 8772 break; 8773 case 0x0085: // NEL 8774 case 0x2028: // Line Separator 8775 case 0x2029: // Paragraph Separator 8776 type = NVGcodepointType.NewLine; 8777 break; 8778 default: 8779 type = NVGcodepointType.Char; 8780 break; 8781 } 8782 if (phase == Phase.SkipBlanks) { 8783 // fix row start 8784 rowStart = cast(int)(iter.stringp-str.ptr); 8785 rowEnd = rowStart; 8786 rowStartX = iter.x; 8787 rowWidth = iter.nextx-rowStartX; // q.x1-rowStartX; 8788 rowMinX = q.x0-rowStartX; 8789 rowMaxX = q.x1-rowStartX; 8790 wordStart = rowStart; 8791 wordStartX = iter.x; 8792 wordMinX = q.x0-rowStartX; 8793 breakEnd = rowStart; 8794 breakWidth = 0.0; 8795 breakMaxX = 0.0; 8796 if (type == NVGcodepointType.Space) continue; 8797 phase = Phase.Normal; 8798 } 8799 8800 if (type == NVGcodepointType.NewLine) { 8801 // always handle new lines 8802 NVGTextRow!T row; 8803 row.string = str; 8804 row.start = rowStart; 8805 row.end = rowEnd; 8806 row.width = rowWidth*invscale; 8807 row.minx = rowMinX*invscale; 8808 row.maxx = rowMaxX*invscale; 8809 ++nrows; 8810 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8811 phase = Phase.SkipBlanks; 8812 } else { 8813 float nextWidth = iter.nextx-rowStartX; 8814 // track last non-white space character 8815 if (type == NVGcodepointType.Char) { 8816 rowEnd = cast(int)(iter.nextp-str.ptr); 8817 rowWidth = iter.nextx-rowStartX; 8818 rowMaxX = q.x1-rowStartX; 8819 } 8820 // track last end of a word 8821 if (ptype == NVGcodepointType.Char && type == NVGcodepointType.Space) { 8822 breakEnd = cast(int)(iter.stringp-str.ptr); 8823 breakWidth = rowWidth; 8824 breakMaxX = rowMaxX; 8825 } 8826 // track last beginning of a word 8827 if (ptype == NVGcodepointType.Space && type == NVGcodepointType.Char) { 8828 wordStart = cast(int)(iter.stringp-str.ptr); 8829 wordStartX = iter.x; 8830 wordMinX = q.x0-rowStartX; 8831 } 8832 // break to new line when a character is beyond break width 8833 if (type == NVGcodepointType.Char && nextWidth > breakRowWidth) { 8834 // the run length is too long, need to break to new line 8835 NVGTextRow!T row; 8836 row.string = str; 8837 if (breakEnd == rowStart) { 8838 // the current word is longer than the row length, just break it from here 8839 row.start = rowStart; 8840 row.end = cast(int)(iter.stringp-str.ptr); 8841 row.width = rowWidth*invscale; 8842 row.minx = rowMinX*invscale; 8843 row.maxx = rowMaxX*invscale; 8844 ++nrows; 8845 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8846 rowStartX = iter.x; 8847 rowStart = cast(int)(iter.stringp-str.ptr); 8848 rowEnd = cast(int)(iter.nextp-str.ptr); 8849 rowWidth = iter.nextx-rowStartX; 8850 rowMinX = q.x0-rowStartX; 8851 rowMaxX = q.x1-rowStartX; 8852 wordStart = rowStart; 8853 wordStartX = iter.x; 8854 wordMinX = q.x0-rowStartX; 8855 } else { 8856 // break the line from the end of the last word, and start new line from the beginning of the new 8857 //{ import core.stdc.stdio : printf; printf("rowStart=%u; rowEnd=%u; breakEnd=%u; len=%u\n", rowStart, rowEnd, breakEnd, cast(uint)str.length); } 8858 row.start = rowStart; 8859 row.end = breakEnd; 8860 row.width = breakWidth*invscale; 8861 row.minx = rowMinX*invscale; 8862 row.maxx = breakMaxX*invscale; 8863 ++nrows; 8864 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8865 rowStartX = wordStartX; 8866 rowStart = wordStart; 8867 rowEnd = cast(int)(iter.nextp-str.ptr); 8868 rowWidth = iter.nextx-rowStartX; 8869 rowMinX = wordMinX; 8870 rowMaxX = q.x1-rowStartX; 8871 // no change to the word start 8872 } 8873 // set null break point 8874 breakEnd = rowStart; 8875 breakWidth = 0.0; 8876 breakMaxX = 0.0; 8877 } 8878 } 8879 8880 pcodepoint = iter.codepoint; 8881 ptype = type; 8882 } 8883 8884 // break the line from the end of the last word, and start new line from the beginning of the new 8885 if (phase != Phase.SkipBlanks && rowStart < str.length) { 8886 //{ import core.stdc.stdio : printf; printf(" rowStart=%u; len=%u\n", rowStart, cast(uint)str.length); } 8887 NVGTextRow!T row; 8888 row.string = str; 8889 row.start = rowStart; 8890 row.end = cast(int)str.length; 8891 row.width = rowWidth*invscale; 8892 row.minx = rowMinX*invscale; 8893 row.maxx = rowMaxX*invscale; 8894 ++nrows; 8895 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8896 } 8897 8898 return nrows; 8899 } 8900 8901 /** Returns iterator which you can use to calculate text bounds and advancement. 8902 * This is usable when you need to do some text layouting with wrapping, to avoid 8903 * guesswork ("will advancement for this space stay the same?"), and Schlemiel's 8904 * algorithm. Note that you can copy the returned struct to save iterator state. 8905 * 8906 * You can check if iterator is valid with [valid] property, put new chars with 8907 * [put] method, get current advance with [advance] property, and current 8908 * bounds with `getBounds(ref float[4] bounds)` method. 8909 * 8910 * $(WARNING Don't change font parameters while iterating! Or use [restoreFont] method.) 8911 * 8912 * Group: text_api 8913 */ 8914 public struct TextBoundsIterator { 8915 private: 8916 NVGContext ctx; 8917 FONSTextBoundsIterator fsiter; // fontstash iterator 8918 float scale, invscale, xscaled, yscaled; 8919 // font settings 8920 float fsSize, fsSpacing, fsBlur; 8921 int fsFontId; 8922 NVGTextAlign fsAlign; 8923 8924 public: 8925 /// Setups iteration. Takes current font parameters from the given NanoVega context. 8926 this (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { reset(actx, ax, ay); } 8927 8928 /// Resets iteration. Takes current font parameters from the given NanoVega context. 8929 void reset (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { 8930 fsiter = fsiter.init; 8931 this = this.init; 8932 if (actx is null) return; 8933 NVGstate* state = nvg__getState(actx); 8934 if (state is null) return; 8935 if (state.fontId == FONS_INVALID) { ctx = null; return; } 8936 8937 ctx = actx; 8938 scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8939 invscale = 1.0f/scale; 8940 8941 fsSize = state.fontSize*scale; 8942 fsSpacing = state.letterSpacing*scale; 8943 fsBlur = state.fontBlur*scale; 8944 fsAlign = state.textAlign; 8945 fsFontId = state.fontId; 8946 restoreFont(); 8947 8948 xscaled = ax*scale; 8949 yscaled = ay*scale; 8950 fsiter.reset(ctx.fs, xscaled, yscaled); 8951 } 8952 8953 /// Restart iteration. Will not restore font. 8954 void restart () nothrow @trusted @nogc { 8955 if (ctx !is null) fsiter.reset(ctx.fs, xscaled, yscaled); 8956 } 8957 8958 /// Restore font settings for the context. 8959 void restoreFont () nothrow @trusted @nogc { 8960 if (ctx !is null) { 8961 ctx.fs.size = fsSize; 8962 ctx.fs.spacing = fsSpacing; 8963 ctx.fs.blur = fsBlur; 8964 ctx.fs.textAlign = fsAlign; 8965 ctx.fs.fontId = fsFontId; 8966 } 8967 } 8968 8969 /// Is this iterator valid? 8970 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null); } 8971 8972 /// Add chars. 8973 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { pragma(inline, true); if (ctx !is null) fsiter.put(str[]); } 8974 8975 /// Returns current advance 8976 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null ? fsiter.advance*invscale : 0); } 8977 8978 /// Returns current text bounds. 8979 void getBounds (ref float[4] bounds) nothrow @trusted @nogc { 8980 if (ctx !is null) { 8981 fsiter.getBounds(bounds); 8982 ctx.fs.getLineBounds(yscaled, &bounds[1], &bounds[3]); 8983 bounds[0] *= invscale; 8984 bounds[1] *= invscale; 8985 bounds[2] *= invscale; 8986 bounds[3] *= invscale; 8987 } else { 8988 bounds[] = 0; 8989 } 8990 } 8991 8992 /// Returns current horizontal text bounds. 8993 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc { 8994 if (ctx !is null) { 8995 fsiter.getHBounds(xmin, xmax); 8996 xmin *= invscale; 8997 xmax *= invscale; 8998 } 8999 } 9000 9001 /// Returns current vertical text bounds. 9002 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc { 9003 if (ctx !is null) { 9004 //fsiter.getVBounds(ymin, ymax); 9005 ctx.fs.getLineBounds(yscaled, &ymin, &ymax); 9006 ymin *= invscale; 9007 ymax *= invscale; 9008 } 9009 } 9010 } 9011 9012 /// Returns font line height (without line spacing), measured in local coordinate space. 9013 /// Group: text_api 9014 public float textFontHeight (NVGContext ctx) nothrow @trusted @nogc { 9015 float res = void; 9016 ctx.textMetrics(null, null, &res); 9017 return res; 9018 } 9019 9020 /// Returns font ascender (positive), measured in local coordinate space. 9021 /// Group: text_api 9022 public float textFontAscender (NVGContext ctx) nothrow @trusted @nogc { 9023 float res = void; 9024 ctx.textMetrics(&res, null, null); 9025 return res; 9026 } 9027 9028 /// Returns font descender (negative), measured in local coordinate space. 9029 /// Group: text_api 9030 public float textFontDescender (NVGContext ctx) nothrow @trusted @nogc { 9031 float res = void; 9032 ctx.textMetrics(null, &res, null); 9033 return res; 9034 } 9035 9036 /** Measures the specified text string. Returns horizontal and vertical sizes of the measured text. 9037 * Measured values are returned in local coordinate space. 9038 * 9039 * Group: text_api 9040 */ 9041 public void textExtents(T) (NVGContext ctx, const(T)[] str, float *w, float *h) nothrow @trusted @nogc if (isAnyCharType!T) { 9042 float[4] bnd = void; 9043 ctx.textBounds(0, 0, str, bnd[]); 9044 if (!ctx.fs.getFontAA(nvg__getState(ctx).fontId)) { 9045 if (w !is null) *w = nvg__lrintf(bnd.ptr[2]-bnd.ptr[0]); 9046 if (h !is null) *h = nvg__lrintf(bnd.ptr[3]-bnd.ptr[1]); 9047 } else { 9048 if (w !is null) *w = bnd.ptr[2]-bnd.ptr[0]; 9049 if (h !is null) *h = bnd.ptr[3]-bnd.ptr[1]; 9050 } 9051 } 9052 9053 /** Measures the specified text string. Returns horizontal size of the measured text. 9054 * Measured values are returned in local coordinate space. 9055 * 9056 * Group: text_api 9057 */ 9058 public float textWidth(T) (NVGContext ctx, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 9059 float w = void; 9060 ctx.textExtents(str, &w, null); 9061 return w; 9062 } 9063 9064 /** Measures the specified text string. Parameter bounds should be a float[4], 9065 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax] 9066 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn). 9067 * Measured values are returned in local coordinate space. 9068 * 9069 * Group: text_api 9070 */ 9071 public float textBounds(T) (NVGContext ctx, float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc 9072 if (isAnyCharType!T) 9073 { 9074 NVGstate* state = nvg__getState(ctx); 9075 9076 if (state.fontId == FONS_INVALID) { 9077 bounds[] = 0; 9078 return 0; 9079 } 9080 9081 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9082 ctx.fs.size = state.fontSize*scale; 9083 ctx.fs.spacing = state.letterSpacing*scale; 9084 ctx.fs.blur = state.fontBlur*scale; 9085 ctx.fs.textAlign = state.textAlign; 9086 ctx.fs.fontId = state.fontId; 9087 9088 float[4] b = void; 9089 immutable float width = ctx.fs.getTextBounds(x*scale, y*scale, str, b[]); 9090 immutable float invscale = 1.0f/scale; 9091 if (bounds.length) { 9092 // use line bounds for height 9093 ctx.fs.getLineBounds(y*scale, b.ptr+1, b.ptr+3); 9094 if (bounds.length > 0) bounds.ptr[0] = b.ptr[0]*invscale; 9095 if (bounds.length > 1) bounds.ptr[1] = b.ptr[1]*invscale; 9096 if (bounds.length > 2) bounds.ptr[2] = b.ptr[2]*invscale; 9097 if (bounds.length > 3) bounds.ptr[3] = b.ptr[3]*invscale; 9098 } 9099 return width*invscale; 9100 } 9101 9102 /// Ditto. 9103 public void textBoxBounds(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str, float[] bounds) if (isAnyCharType!T) { 9104 NVGstate* state = nvg__getState(ctx); 9105 NVGTextRow!T[2] rows; 9106 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9107 float invscale = 1.0f/scale; 9108 float lineh = 0, rminy = 0, rmaxy = 0; 9109 float minx, miny, maxx, maxy; 9110 9111 if (state.fontId == FONS_INVALID) { 9112 bounds[] = 0; 9113 return; 9114 } 9115 9116 auto oldAlign = state.textAlign; 9117 scope(exit) state.textAlign = oldAlign; 9118 auto halign = state.textAlign.horizontal; 9119 9120 ctx.textMetrics(null, null, &lineh); 9121 state.textAlign.horizontal = NVGTextAlign.H.Left; 9122 9123 minx = maxx = x; 9124 miny = maxy = y; 9125 9126 ctx.fs.size = state.fontSize*scale; 9127 ctx.fs.spacing = state.letterSpacing*scale; 9128 ctx.fs.blur = state.fontBlur*scale; 9129 ctx.fs.textAlign = state.textAlign; 9130 ctx.fs.fontId = state.fontId; 9131 ctx.fs.getLineBounds(0, &rminy, &rmaxy); 9132 rminy *= invscale; 9133 rmaxy *= invscale; 9134 9135 for (;;) { 9136 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]); 9137 if (rres.length == 0) break; 9138 foreach (ref row; rres) { 9139 float rminx, rmaxx, dx = 0; 9140 // horizontal bounds 9141 final switch (halign) { 9142 case NVGTextAlign.H.Left: dx = 0; break; 9143 case NVGTextAlign.H.Center: dx = breakRowWidth*0.5f-row.width*0.5f; break; 9144 case NVGTextAlign.H.Right: dx = breakRowWidth-row.width; break; 9145 } 9146 rminx = x+row.minx+dx; 9147 rmaxx = x+row.maxx+dx; 9148 minx = nvg__min(minx, rminx); 9149 maxx = nvg__max(maxx, rmaxx); 9150 // vertical bounds 9151 miny = nvg__min(miny, y+rminy); 9152 maxy = nvg__max(maxy, y+rmaxy); 9153 y += lineh*state.lineHeight; 9154 } 9155 str = rres[$-1].rest; 9156 } 9157 9158 if (bounds.length) { 9159 if (bounds.length > 0) bounds.ptr[0] = minx; 9160 if (bounds.length > 1) bounds.ptr[1] = miny; 9161 if (bounds.length > 2) bounds.ptr[2] = maxx; 9162 if (bounds.length > 3) bounds.ptr[3] = maxy; 9163 } 9164 } 9165 9166 /// Returns the vertical metrics based on the current text style. Measured values are returned in local coordinate space. 9167 /// Group: text_api 9168 public void textMetrics (NVGContext ctx, float* ascender, float* descender, float* lineh) nothrow @trusted @nogc { 9169 NVGstate* state = nvg__getState(ctx); 9170 9171 if (state.fontId == FONS_INVALID) { 9172 if (ascender !is null) *ascender *= 0; 9173 if (descender !is null) *descender *= 0; 9174 if (lineh !is null) *lineh *= 0; 9175 return; 9176 } 9177 9178 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9179 immutable float invscale = 1.0f/scale; 9180 9181 ctx.fs.size = state.fontSize*scale; 9182 ctx.fs.spacing = state.letterSpacing*scale; 9183 ctx.fs.blur = state.fontBlur*scale; 9184 ctx.fs.textAlign = state.textAlign; 9185 ctx.fs.fontId = state.fontId; 9186 9187 ctx.fs.getVertMetrics(ascender, descender, lineh); 9188 if (ascender !is null) *ascender *= invscale; 9189 if (descender !is null) *descender *= invscale; 9190 if (lineh !is null) *lineh *= invscale; 9191 } 9192 9193 9194 // ////////////////////////////////////////////////////////////////////////// // 9195 // fontstash 9196 // ////////////////////////////////////////////////////////////////////////// // 9197 import core.stdc.stdlib : malloc, realloc, free; 9198 import core.stdc.string : memset, memcpy, strncpy, strcmp, strlen; 9199 import core.stdc.stdio : FILE, fopen, fclose, fseek, ftell, fread, SEEK_END, SEEK_SET; 9200 9201 public: 9202 // welcome to version hell! 9203 version(nanovg_force_stb_ttf) { 9204 } else { 9205 version(nanovg_force_detect) {} else version(nanovg_use_freetype) { version = nanovg_use_freetype_ii; } 9206 } 9207 version(nanovg_ignore_iv_stb_ttf) enum nanovg_ignore_iv_stb_ttf = true; else enum nanovg_ignore_iv_stb_ttf = false; 9208 //version(nanovg_ignore_mono); 9209 9210 version(nanovg_force_stb_ttf) { 9211 private enum NanoVegaForceFreeType = false; 9212 } else { 9213 version (nanovg_builtin_freetype_bindings) { 9214 version(Posix) { 9215 private enum NanoVegaForceFreeType = true; 9216 } else { 9217 private enum NanoVegaForceFreeType = false; 9218 } 9219 } else { 9220 version(Posix) { 9221 private enum NanoVegaForceFreeType = true; 9222 } else { 9223 private enum NanoVegaForceFreeType = false; 9224 } 9225 } 9226 } 9227 9228 version(nanovg_use_freetype_ii) { 9229 enum NanoVegaIsUsingSTBTTF = false; 9230 //pragma(msg, "iv.freetype: forced"); 9231 } else { 9232 static if (NanoVegaForceFreeType) { 9233 enum NanoVegaIsUsingSTBTTF = false; 9234 } else { 9235 static if (!nanovg_ignore_iv_stb_ttf && __traits(compiles, { import iv.stb.ttf; })) { 9236 import iv.stb.ttf; 9237 enum NanoVegaIsUsingSTBTTF = true; 9238 version(nanovg_report_stb_ttf) pragma(msg, "iv.stb.ttf"); 9239 } else static if (__traits(compiles, { import arsd.ttf; })) { 9240 import arsd.ttf; 9241 enum NanoVegaIsUsingSTBTTF = true; 9242 version(nanovg_report_stb_ttf) pragma(msg, "arsd.ttf"); 9243 } else static if (__traits(compiles, { import stb_truetype; })) { 9244 import stb_truetype; 9245 enum NanoVegaIsUsingSTBTTF = true; 9246 version(nanovg_report_stb_ttf) pragma(msg, "stb_truetype"); 9247 } else static if (__traits(compiles, { import iv.freetype; })) { 9248 version (nanovg_builtin_freetype_bindings) { 9249 enum NanoVegaIsUsingSTBTTF = false; 9250 version = nanovg_builtin_freetype_bindings; 9251 } else { 9252 import iv.freetype; 9253 enum NanoVegaIsUsingSTBTTF = false; 9254 } 9255 version(nanovg_report_stb_ttf) pragma(msg, "freetype"); 9256 } else { 9257 static assert(0, "no stb_ttf/iv.freetype found!"); 9258 } 9259 } 9260 } 9261 9262 9263 // ////////////////////////////////////////////////////////////////////////// // 9264 //version = nanovg_ft_mono; 9265 9266 /// Invald font id. 9267 /// Group: font_stash 9268 public enum FONS_INVALID = -1; 9269 9270 public enum FONSBitmapFlag : uint { 9271 Required = 0, 9272 Optional = 1, 9273 } 9274 9275 public enum FONSError : int { 9276 NoError = 0, 9277 AtlasFull = 1, // Font atlas is full. 9278 ScratchFull = 2, // Scratch memory used to render glyphs is full, requested size reported in 'val', you may need to bump up FONS_SCRATCH_BUF_SIZE. 9279 StatesOverflow = 3, // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES. 9280 StatesUnderflow = 4, // Trying to pop too many states fonsPopState(). 9281 } 9282 9283 /// Initial parameters for new FontStash. 9284 /// Group: font_stash 9285 public struct FONSParams { 9286 enum Flag : uint { 9287 ZeroTopLeft = 0U, // default 9288 ZeroBottomLeft = 1U, 9289 } 9290 int width, height; 9291 Flag flags = Flag.ZeroTopLeft; 9292 void* userPtr; 9293 bool function (void* uptr, int width, int height) nothrow @trusted @nogc renderCreate; 9294 int function (void* uptr, int width, int height) nothrow @trusted @nogc renderResize; 9295 void function (void* uptr, int* rect, const(ubyte)* data) nothrow @trusted @nogc renderUpdate; 9296 void function (void* uptr) nothrow @trusted @nogc renderDelete; 9297 @property bool isZeroTopLeft () const pure nothrow @trusted @nogc { pragma(inline, true); return ((flags&Flag.ZeroBottomLeft) == 0); } 9298 } 9299 9300 //TODO: document this 9301 public struct FONSQuad { 9302 float x0=0, y0=0, s0=0, t0=0; 9303 float x1=0, y1=0, s1=0, t1=0; 9304 } 9305 9306 //TODO: document this 9307 public struct FONSTextIter(CT) if (isAnyCharType!CT) { 9308 alias CharType = CT; 9309 float x=0, y=0, nextx=0, nexty=0, scale=0, spacing=0; 9310 uint codepoint; 9311 short isize, iblur; 9312 FONSContext stash; 9313 FONSfont* font; 9314 int prevGlyphIndex; 9315 const(CT)* s; // string 9316 const(CT)* n; // next 9317 const(CT)* e; // end 9318 FONSBitmapFlag bitmapOption; 9319 static if (is(CT == char)) { 9320 uint utf8state; 9321 } 9322 9323 this (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { setup(astash, ax, ay, astr, abitmapOption); } 9324 ~this () nothrow @trusted @nogc { pragma(inline, true); static if (is(CT == char)) utf8state = 0; s = n = e = null; } 9325 9326 @property const(CT)* stringp () const pure nothrow @trusted @nogc { pragma(inline, true); return s; } 9327 @property const(CT)* nextp () const pure nothrow @trusted @nogc { pragma(inline, true); return n; } 9328 @property const(CT)* endp () const pure nothrow @trusted @nogc { pragma(inline, true); return e; } 9329 9330 bool setup (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { 9331 import core.stdc.string : memset; 9332 9333 memset(&this, 0, this.sizeof); 9334 if (astash is null) return false; 9335 9336 FONSstate* state = astash.getState; 9337 9338 if (state.font < 0 || state.font >= astash.nfonts) return false; 9339 font = astash.fonts[state.font]; 9340 if (font is null || font.fdata is null) return false; 9341 9342 isize = cast(short)(state.size*10.0f); 9343 iblur = cast(short)state.blur; 9344 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 9345 9346 // align horizontally 9347 if (state.talign.left) { 9348 // empty 9349 } else if (state.talign.right) { 9350 immutable float width = astash.getTextBounds(ax, ay, astr, null); 9351 ax -= width; 9352 } else if (state.talign.center) { 9353 immutable float width = astash.getTextBounds(ax, ay, astr, null); 9354 ax -= width*0.5f; 9355 } 9356 9357 // align vertically 9358 ay += astash.getVertAlign(font, state.talign, isize); 9359 9360 x = nextx = ax; 9361 y = nexty = ay; 9362 spacing = state.spacing; 9363 9364 if (astr.ptr is null) { 9365 static if (is(CharType == char)) astr = ""; 9366 else static if (is(CharType == wchar)) astr = ""w; 9367 else static if (is(CharType == dchar)) astr = ""d; 9368 else static assert(0, "wtf?!"); 9369 } 9370 s = astr.ptr; 9371 n = astr.ptr; 9372 e = astr.ptr+astr.length; 9373 9374 codepoint = 0; 9375 prevGlyphIndex = -1; 9376 bitmapOption = abitmapOption; 9377 stash = astash; 9378 9379 return true; 9380 } 9381 9382 bool getDummyChar (ref FONSQuad quad) nothrow @trusted @nogc { 9383 if (stash is null || font is null) return false; 9384 // get glyph and quad 9385 x = nextx; 9386 y = nexty; 9387 FONSglyph* glyph = stash.getGlyph(font, 0xFFFD, isize, iblur, bitmapOption); 9388 if (glyph !is null) { 9389 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9390 prevGlyphIndex = glyph.index; 9391 return true; 9392 } else { 9393 prevGlyphIndex = -1; 9394 return false; 9395 } 9396 } 9397 9398 bool next (ref FONSQuad quad) nothrow @trusted @nogc { 9399 if (stash is null || font is null) return false; 9400 FONSglyph* glyph = null; 9401 static if (is(CharType == char)) { 9402 const(char)* str = this.n; 9403 this.s = this.n; 9404 if (str is this.e) return false; 9405 const(char)* e = this.e; 9406 for (; str !is e; ++str) { 9407 /*if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;*/ 9408 mixin(DecUtfMixin!("this.utf8state", "this.codepoint", "*cast(const(ubyte)*)str")); 9409 if (utf8state) continue; 9410 ++str; // 'cause we'll break anyway 9411 // get glyph and quad 9412 x = nextx; 9413 y = nexty; 9414 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption); 9415 if (glyph !is null) { 9416 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9417 prevGlyphIndex = glyph.index; 9418 } else { 9419 prevGlyphIndex = -1; 9420 } 9421 break; 9422 } 9423 this.n = str; 9424 } else { 9425 const(CharType)* str = this.n; 9426 this.s = this.n; 9427 if (str is this.e) return false; 9428 codepoint = cast(uint)(*str++); 9429 if (codepoint > dchar.max) codepoint = 0xFFFD; 9430 // get glyph and quad 9431 x = nextx; 9432 y = nexty; 9433 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption); 9434 if (glyph !is null) { 9435 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9436 prevGlyphIndex = glyph.index; 9437 } else { 9438 prevGlyphIndex = -1; 9439 } 9440 this.n = str; 9441 } 9442 return true; 9443 } 9444 } 9445 9446 9447 // ////////////////////////////////////////////////////////////////////////// // 9448 //static if (!HasAST) version = nanovg_use_freetype_ii_x; 9449 9450 /*version(nanovg_use_freetype_ii_x)*/ static if (!NanoVegaIsUsingSTBTTF) { 9451 version(nanovg_builtin_freetype_bindings) { 9452 pragma(lib, "freetype"); 9453 private extern(C) nothrow @trusted @nogc { 9454 private import core.stdc.config : c_long, c_ulong; 9455 alias FT_Pos = c_long; 9456 // config/ftconfig.h 9457 alias FT_Int16 = short; 9458 alias FT_UInt16 = ushort; 9459 alias FT_Int32 = int; 9460 alias FT_UInt32 = uint; 9461 alias FT_Fast = int; 9462 alias FT_UFast = uint; 9463 alias FT_Int64 = long; 9464 alias FT_Uint64 = ulong; 9465 // fttypes.h 9466 alias FT_Bool = ubyte; 9467 alias FT_FWord = short; 9468 alias FT_UFWord = ushort; 9469 alias FT_Char = char; 9470 alias FT_Byte = ubyte; 9471 alias FT_Bytes = FT_Byte*; 9472 alias FT_Tag = FT_UInt32; 9473 alias FT_String = char; 9474 alias FT_Short = short; 9475 alias FT_UShort = ushort; 9476 alias FT_Int = int; 9477 alias FT_UInt = uint; 9478 alias FT_Long = c_long; 9479 alias FT_ULong = c_ulong; 9480 alias FT_F2Dot14 = short; 9481 alias FT_F26Dot6 = c_long; 9482 alias FT_Fixed = c_long; 9483 alias FT_Error = int; 9484 alias FT_Pointer = void*; 9485 alias FT_Offset = usize; 9486 alias FT_PtrDist = ptrdiff_t; 9487 9488 struct FT_UnitVector { 9489 FT_F2Dot14 x; 9490 FT_F2Dot14 y; 9491 } 9492 9493 struct FT_Matrix { 9494 FT_Fixed xx, xy; 9495 FT_Fixed yx, yy; 9496 } 9497 9498 struct FT_Data { 9499 const(FT_Byte)* pointer; 9500 FT_Int length; 9501 } 9502 alias FT_Face = FT_FaceRec*; 9503 struct FT_FaceRec { 9504 FT_Long num_faces; 9505 FT_Long face_index; 9506 FT_Long face_flags; 9507 FT_Long style_flags; 9508 FT_Long num_glyphs; 9509 FT_String* family_name; 9510 FT_String* style_name; 9511 FT_Int num_fixed_sizes; 9512 FT_Bitmap_Size* available_sizes; 9513 FT_Int num_charmaps; 9514 FT_CharMap* charmaps; 9515 FT_Generic generic; 9516 FT_BBox bbox; 9517 FT_UShort units_per_EM; 9518 FT_Short ascender; 9519 FT_Short descender; 9520 FT_Short height; 9521 FT_Short max_advance_width; 9522 FT_Short max_advance_height; 9523 FT_Short underline_position; 9524 FT_Short underline_thickness; 9525 FT_GlyphSlot glyph; 9526 FT_Size size; 9527 FT_CharMap charmap; 9528 FT_Driver driver; 9529 FT_Memory memory; 9530 FT_Stream stream; 9531 FT_ListRec sizes_list; 9532 FT_Generic autohint; 9533 void* extensions; 9534 FT_Face_Internal internal; 9535 } 9536 struct FT_Bitmap_Size { 9537 FT_Short height; 9538 FT_Short width; 9539 FT_Pos size; 9540 FT_Pos x_ppem; 9541 FT_Pos y_ppem; 9542 } 9543 alias FT_CharMap = FT_CharMapRec*; 9544 struct FT_CharMapRec { 9545 FT_Face face; 9546 FT_Encoding encoding; 9547 FT_UShort platform_id; 9548 FT_UShort encoding_id; 9549 } 9550 extern(C) nothrow @nogc { alias FT_Generic_Finalizer = void function (void* object); } 9551 struct FT_Generic { 9552 void* data; 9553 FT_Generic_Finalizer finalizer; 9554 } 9555 struct FT_Vector { 9556 FT_Pos x; 9557 FT_Pos y; 9558 } 9559 struct FT_BBox { 9560 FT_Pos xMin, yMin; 9561 FT_Pos xMax, yMax; 9562 } 9563 alias FT_Pixel_Mode = int; 9564 enum { 9565 FT_PIXEL_MODE_NONE = 0, 9566 FT_PIXEL_MODE_MONO, 9567 FT_PIXEL_MODE_GRAY, 9568 FT_PIXEL_MODE_GRAY2, 9569 FT_PIXEL_MODE_GRAY4, 9570 FT_PIXEL_MODE_LCD, 9571 FT_PIXEL_MODE_LCD_V, 9572 FT_PIXEL_MODE_MAX 9573 } 9574 struct FT_Bitmap { 9575 uint rows; 9576 uint width; 9577 int pitch; 9578 ubyte* buffer; 9579 ushort num_grays; 9580 ubyte pixel_mode; 9581 ubyte palette_mode; 9582 void* palette; 9583 } 9584 struct FT_Outline { 9585 short n_contours; 9586 short n_points; 9587 FT_Vector* points; 9588 byte* tags; 9589 short* contours; 9590 int flags; 9591 } 9592 alias FT_GlyphSlot = FT_GlyphSlotRec*; 9593 struct FT_GlyphSlotRec { 9594 FT_Library library; 9595 FT_Face face; 9596 FT_GlyphSlot next; 9597 FT_UInt reserved; 9598 FT_Generic generic; 9599 FT_Glyph_Metrics metrics; 9600 FT_Fixed linearHoriAdvance; 9601 FT_Fixed linearVertAdvance; 9602 FT_Vector advance; 9603 FT_Glyph_Format format; 9604 FT_Bitmap bitmap; 9605 FT_Int bitmap_left; 9606 FT_Int bitmap_top; 9607 FT_Outline outline; 9608 FT_UInt num_subglyphs; 9609 FT_SubGlyph subglyphs; 9610 void* control_data; 9611 c_long control_len; 9612 FT_Pos lsb_delta; 9613 FT_Pos rsb_delta; 9614 void* other; 9615 FT_Slot_Internal internal; 9616 } 9617 alias FT_Size = FT_SizeRec*; 9618 struct FT_SizeRec { 9619 FT_Face face; 9620 FT_Generic generic; 9621 FT_Size_Metrics metrics; 9622 FT_Size_Internal internal; 9623 } 9624 alias FT_Encoding = FT_Tag; 9625 alias FT_Face_Internal = void*; 9626 alias FT_Driver = void*; 9627 alias FT_Memory = void*; 9628 alias FT_Stream = void*; 9629 alias FT_Library = void*; 9630 alias FT_SubGlyph = void*; 9631 alias FT_Slot_Internal = void*; 9632 alias FT_Size_Internal = void*; 9633 alias FT_ListNode = FT_ListNodeRec*; 9634 alias FT_List = FT_ListRec*; 9635 struct FT_ListNodeRec { 9636 FT_ListNode prev; 9637 FT_ListNode next; 9638 void* data; 9639 } 9640 struct FT_ListRec { 9641 FT_ListNode head; 9642 FT_ListNode tail; 9643 } 9644 struct FT_Glyph_Metrics { 9645 FT_Pos width; 9646 FT_Pos height; 9647 FT_Pos horiBearingX; 9648 FT_Pos horiBearingY; 9649 FT_Pos horiAdvance; 9650 FT_Pos vertBearingX; 9651 FT_Pos vertBearingY; 9652 FT_Pos vertAdvance; 9653 } 9654 alias FT_Glyph_Format = FT_Tag; 9655 FT_Tag FT_MAKE_TAG (char x1, char x2, char x3, char x4) pure nothrow @safe @nogc { 9656 pragma(inline, true); 9657 return cast(FT_UInt32)((x1<<24)|(x2<<16)|(x3<<8)|x4); 9658 } 9659 enum : FT_Tag { 9660 FT_GLYPH_FORMAT_NONE = 0, 9661 FT_GLYPH_FORMAT_COMPOSITE = FT_MAKE_TAG('c','o','m','p'), 9662 FT_GLYPH_FORMAT_BITMAP = FT_MAKE_TAG('b','i','t','s'), 9663 FT_GLYPH_FORMAT_OUTLINE = FT_MAKE_TAG('o','u','t','l'), 9664 FT_GLYPH_FORMAT_PLOTTER = FT_MAKE_TAG('p','l','o','t'), 9665 } 9666 struct FT_Size_Metrics { 9667 FT_UShort x_ppem; 9668 FT_UShort y_ppem; 9669 9670 FT_Fixed x_scale; 9671 FT_Fixed y_scale; 9672 9673 FT_Pos ascender; 9674 FT_Pos descender; 9675 FT_Pos height; 9676 FT_Pos max_advance; 9677 } 9678 enum FT_LOAD_DEFAULT = 0x0U; 9679 enum FT_LOAD_NO_SCALE = 1U<<0; 9680 enum FT_LOAD_NO_HINTING = 1U<<1; 9681 enum FT_LOAD_RENDER = 1U<<2; 9682 enum FT_LOAD_NO_BITMAP = 1U<<3; 9683 enum FT_LOAD_VERTICAL_LAYOUT = 1U<<4; 9684 enum FT_LOAD_FORCE_AUTOHINT = 1U<<5; 9685 enum FT_LOAD_CROP_BITMAP = 1U<<6; 9686 enum FT_LOAD_PEDANTIC = 1U<<7; 9687 enum FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1U<<9; 9688 enum FT_LOAD_NO_RECURSE = 1U<<10; 9689 enum FT_LOAD_IGNORE_TRANSFORM = 1U<<11; 9690 enum FT_LOAD_MONOCHROME = 1U<<12; 9691 enum FT_LOAD_LINEAR_DESIGN = 1U<<13; 9692 enum FT_LOAD_NO_AUTOHINT = 1U<<15; 9693 enum FT_LOAD_COLOR = 1U<<20; 9694 enum FT_LOAD_COMPUTE_METRICS = 1U<<21; 9695 enum FT_FACE_FLAG_KERNING = 1U<<6; 9696 alias FT_Kerning_Mode = int; 9697 enum /*FT_Kerning_Mode*/ { 9698 FT_KERNING_DEFAULT = 0, 9699 FT_KERNING_UNFITTED, 9700 FT_KERNING_UNSCALED 9701 } 9702 extern(C) nothrow @nogc { 9703 alias FT_Outline_MoveToFunc = int function (const(FT_Vector)*, void*); 9704 alias FT_Outline_LineToFunc = int function (const(FT_Vector)*, void*); 9705 alias FT_Outline_ConicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, void*); 9706 alias FT_Outline_CubicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, const(FT_Vector)*, void*); 9707 } 9708 struct FT_Outline_Funcs { 9709 FT_Outline_MoveToFunc move_to; 9710 FT_Outline_LineToFunc line_to; 9711 FT_Outline_ConicToFunc conic_to; 9712 FT_Outline_CubicToFunc cubic_to; 9713 int shift; 9714 FT_Pos delta; 9715 } 9716 9717 FT_Error FT_Init_FreeType (FT_Library*); 9718 FT_Error FT_New_Memory_Face (FT_Library, const(FT_Byte)*, FT_Long, FT_Long, FT_Face*); 9719 FT_UInt FT_Get_Char_Index (FT_Face, FT_ULong); 9720 FT_Error FT_Set_Pixel_Sizes (FT_Face, FT_UInt, FT_UInt); 9721 FT_Error FT_Load_Glyph (FT_Face, FT_UInt, FT_Int32); 9722 FT_Error FT_Get_Advance (FT_Face, FT_UInt, FT_Int32, FT_Fixed*); 9723 FT_Error FT_Get_Kerning (FT_Face, FT_UInt, FT_UInt, FT_UInt, FT_Vector*); 9724 void FT_Outline_Get_CBox (const(FT_Outline)*, FT_BBox*); 9725 FT_Error FT_Outline_Decompose (FT_Outline*, const(FT_Outline_Funcs)*, void*); 9726 } 9727 } else version(bindbc) { 9728 import bindbc.freetype; 9729 alias FT_KERNING_DEFAULT = FT_Kerning_Mode.FT_KERNING_DEFAULT; 9730 alias FT_KERNING_UNFITTED = FT_Kerning_Mode.FT_KERNING_UNFITTED; 9731 alias FT_KERNING_UNSCALED = FT_Kerning_Mode.FT_KERNING_UNSCALED; 9732 } else { 9733 import iv.freetype; 9734 } 9735 9736 struct FONSttFontImpl { 9737 FT_Face font; 9738 bool mono; // no aa? 9739 } 9740 9741 __gshared FT_Library ftLibrary; 9742 9743 int fons__tt_init (FONSContext context) nothrow @trusted @nogc { 9744 FT_Error ftError; 9745 //FONS_NOTUSED(context); 9746 ftError = FT_Init_FreeType(&ftLibrary); 9747 return (ftError == 0); 9748 } 9749 9750 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc { 9751 font.mono = v; 9752 } 9753 9754 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc { 9755 return font.mono; 9756 } 9757 9758 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc { 9759 FT_Error ftError; 9760 //font.font.userdata = stash; 9761 ftError = FT_New_Memory_Face(ftLibrary, cast(const(FT_Byte)*)data, dataSize, 0, &font.font); 9762 return ftError == 0; 9763 } 9764 9765 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc { 9766 *ascent = font.font.ascender; 9767 *descent = font.font.descender; 9768 *lineGap = font.font.height-(*ascent - *descent); 9769 } 9770 9771 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc { 9772 return size/(font.font.ascender-font.font.descender); 9773 } 9774 9775 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc { 9776 return FT_Get_Char_Index(font.font, codepoint); 9777 } 9778 9779 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 { 9780 FT_Error ftError; 9781 FT_GlyphSlot ftGlyph; 9782 //version(nanovg_ignore_mono) enum exflags = 0; 9783 //else version(nanovg_ft_mono) enum exflags = FT_LOAD_MONOCHROME; else enum exflags = 0; 9784 uint exflags = (font.mono ? FT_LOAD_MONOCHROME : 0); 9785 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))); 9786 if (ftError) return 0; 9787 ftError = FT_Load_Glyph(font.font, glyph, FT_LOAD_RENDER|/*FT_LOAD_NO_AUTOHINT|*/exflags); 9788 if (ftError) return 0; 9789 ftError = FT_Get_Advance(font.font, glyph, FT_LOAD_NO_SCALE|/*FT_LOAD_NO_AUTOHINT|*/exflags, cast(FT_Fixed*)advance); 9790 if (ftError) return 0; 9791 ftGlyph = font.font.glyph; 9792 *lsb = cast(int)ftGlyph.metrics.horiBearingX; 9793 *x0 = ftGlyph.bitmap_left; 9794 *x1 = *x0+ftGlyph.bitmap.width; 9795 *y0 = -ftGlyph.bitmap_top; 9796 *y1 = *y0+ftGlyph.bitmap.rows; 9797 return 1; 9798 } 9799 9800 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc { 9801 FT_GlyphSlot ftGlyph = font.font.glyph; 9802 //FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap 9803 //version(nanovg_ignore_mono) enum RenderAA = true; 9804 //else version(nanovg_ft_mono) enum RenderAA = false; 9805 //else enum RenderAA = true; 9806 if (font.mono) { 9807 auto src = ftGlyph.bitmap.buffer; 9808 auto dst = output; 9809 auto spt = ftGlyph.bitmap.pitch; 9810 if (spt < 0) spt = -spt; 9811 foreach (int y; 0..ftGlyph.bitmap.rows) { 9812 ubyte count = 0, b = 0; 9813 auto s = src; 9814 auto d = dst; 9815 foreach (int x; 0..ftGlyph.bitmap.width) { 9816 if (count-- == 0) { count = 7; b = *s++; } else b <<= 1; 9817 *d++ = (b&0x80 ? 255 : 0); 9818 } 9819 src += spt; 9820 dst += outStride; 9821 } 9822 } else { 9823 auto src = ftGlyph.bitmap.buffer; 9824 auto dst = output; 9825 auto spt = ftGlyph.bitmap.pitch; 9826 if (spt < 0) spt = -spt; 9827 foreach (int y; 0..ftGlyph.bitmap.rows) { 9828 import core.stdc.string : memcpy; 9829 //dst[0..ftGlyph.bitmap.width] = src[0..ftGlyph.bitmap.width]; 9830 memcpy(dst, src, ftGlyph.bitmap.width); 9831 src += spt; 9832 dst += outStride; 9833 } 9834 } 9835 } 9836 9837 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc { 9838 FT_Vector ftKerning; 9839 version(none) { 9840 // fitted kerning 9841 FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning); 9842 //{ import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d\n", glyph1, glyph2, ftKerning.x, ftKerning.y); } 9843 return cast(int)ftKerning.x; // round up and convert to integer 9844 } else { 9845 // unfitted kerning 9846 //FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNFITTED, &ftKerning); 9847 if (glyph1 <= 0 || glyph2 <= 0 || (font.font.face_flags&FT_FACE_FLAG_KERNING) == 0) return 0; 9848 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; 9849 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning)) return 0; 9850 version(none) { 9851 if (ftKerning.x) { 9852 //{ import core.stdc.stdio : printf; printf("has kerning: %u\n", cast(uint)(font.font.face_flags&FT_FACE_FLAG_KERNING)); } 9853 { import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d (size=%g)\n", glyph1, glyph2, ftKerning.x, ftKerning.y, cast(double)size); } 9854 } 9855 } 9856 version(none) { 9857 FT_Vector kk; 9858 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNSCALED, &kk)) assert(0, "wtf?!"); 9859 auto kadvfrac = FT_MulFix(kk.x, font.font.size.metrics.x_scale); // 1/64 of pixel 9860 //return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6); 9861 //assert(ftKerning.x == kadvfrac); 9862 if (ftKerning.x || kadvfrac) { 9863 { 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); } 9864 } 9865 //return cast(int)(kadvfrac+(kadvfrac < 0 ? -31 : 32)>>6); // round up and convert to integer 9866 return kadvfrac/64.0f; 9867 } 9868 //return cast(int)(ftKerning.x+(ftKerning.x < 0 ? -31 : 32)>>6); // round up and convert to integer 9869 return ftKerning.x/64.0f; 9870 } 9871 } 9872 9873 extern(C) nothrow @trusted @nogc { 9874 static struct OutlinerData { 9875 @disable this (this); 9876 void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); } 9877 NVGContext vg; 9878 NVGPathOutline.DataStore* ol; 9879 FT_BBox outlineBBox; 9880 nothrow @trusted @nogc: 9881 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; } 9882 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 9883 } 9884 9885 int fons__nvg__moveto_cb (const(FT_Vector)* to, void* user) { 9886 auto odata = cast(OutlinerData*)user; 9887 if (odata.vg !is null) odata.vg.moveTo(odata.transx(to.x), odata.transy(to.y)); 9888 if (odata.ol !is null) { 9889 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo); 9890 odata.ol.putArgs(odata.transx(to.x)); 9891 odata.ol.putArgs(odata.transy(to.y)); 9892 } 9893 return 0; 9894 } 9895 9896 int fons__nvg__lineto_cb (const(FT_Vector)* to, void* user) { 9897 auto odata = cast(OutlinerData*)user; 9898 if (odata.vg !is null) odata.vg.lineTo(odata.transx(to.x), odata.transy(to.y)); 9899 if (odata.ol !is null) { 9900 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo); 9901 odata.ol.putArgs(odata.transx(to.x)); 9902 odata.ol.putArgs(odata.transy(to.y)); 9903 } 9904 return 0; 9905 } 9906 9907 int fons__nvg__quadto_cb (const(FT_Vector)* c1, const(FT_Vector)* to, void* user) { 9908 auto odata = cast(OutlinerData*)user; 9909 if (odata.vg !is null) odata.vg.quadTo(odata.transx(c1.x), odata.transy(c1.y), odata.transx(to.x), odata.transy(to.y)); 9910 if (odata.ol !is null) { 9911 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo); 9912 odata.ol.putArgs(odata.transx(c1.x)); 9913 odata.ol.putArgs(odata.transy(c1.y)); 9914 odata.ol.putArgs(odata.transx(to.x)); 9915 odata.ol.putArgs(odata.transy(to.y)); 9916 } 9917 return 0; 9918 } 9919 9920 int fons__nvg__cubicto_cb (const(FT_Vector)* c1, const(FT_Vector)* c2, const(FT_Vector)* to, void* user) { 9921 auto odata = cast(OutlinerData*)user; 9922 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)); 9923 if (odata.ol !is null) { 9924 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo); 9925 odata.ol.putArgs(odata.transx(c1.x)); 9926 odata.ol.putArgs(odata.transy(c1.y)); 9927 odata.ol.putArgs(odata.transx(c2.x)); 9928 odata.ol.putArgs(odata.transy(c2.y)); 9929 odata.ol.putArgs(odata.transx(to.x)); 9930 odata.ol.putArgs(odata.transy(to.y)); 9931 } 9932 return 0; 9933 } 9934 } 9935 9936 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc { 9937 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 9938 9939 FT_Outline_Funcs funcs; 9940 funcs.move_to = &fons__nvg__moveto_cb; 9941 funcs.line_to = &fons__nvg__lineto_cb; 9942 funcs.conic_to = &fons__nvg__quadto_cb; 9943 funcs.cubic_to = &fons__nvg__cubicto_cb; 9944 9945 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 9946 if (err) { bounds[] = 0; return false; } 9947 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; } 9948 9949 FT_Outline outline = font.font.glyph.outline; 9950 9951 OutlinerData odata; 9952 odata.vg = vg; 9953 FT_Outline_Get_CBox(&outline, &odata.outlineBBox); 9954 9955 err = FT_Outline_Decompose(&outline, &funcs, &odata); 9956 if (err) { bounds[] = 0; return false; } 9957 if (bounds.length > 0) bounds.ptr[0] = odata.outlineBBox.xMin; 9958 if (bounds.length > 1) bounds.ptr[1] = -odata.outlineBBox.yMax; 9959 if (bounds.length > 2) bounds.ptr[2] = odata.outlineBBox.xMax; 9960 if (bounds.length > 3) bounds.ptr[3] = -odata.outlineBBox.yMin; 9961 return true; 9962 } 9963 9964 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 9965 FT_Outline_Funcs funcs; 9966 funcs.move_to = &fons__nvg__moveto_cb; 9967 funcs.line_to = &fons__nvg__lineto_cb; 9968 funcs.conic_to = &fons__nvg__quadto_cb; 9969 funcs.cubic_to = &fons__nvg__cubicto_cb; 9970 9971 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 9972 if (err) return false; 9973 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) return false; 9974 9975 FT_Outline outline = font.font.glyph.outline; 9976 9977 OutlinerData odata; 9978 odata.ol = ol; 9979 FT_Outline_Get_CBox(&outline, &odata.outlineBBox); 9980 9981 err = FT_Outline_Decompose(&outline, &funcs, &odata); 9982 if (err) return false; 9983 ol.bounds.ptr[0] = odata.outlineBBox.xMin; 9984 ol.bounds.ptr[1] = -odata.outlineBBox.yMax; 9985 ol.bounds.ptr[2] = odata.outlineBBox.xMax; 9986 ol.bounds.ptr[3] = -odata.outlineBBox.yMin; 9987 return true; 9988 } 9989 9990 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc { 9991 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 9992 9993 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 9994 if (err) return false; 9995 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; } 9996 9997 FT_Outline outline = font.font.glyph.outline; 9998 FT_BBox outlineBBox; 9999 FT_Outline_Get_CBox(&outline, &outlineBBox); 10000 if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin; 10001 if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax; 10002 if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax; 10003 if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin; 10004 return true; 10005 } 10006 10007 10008 } else { 10009 // ////////////////////////////////////////////////////////////////////////// // 10010 // sorry 10011 import std.traits : isFunctionPointer, isDelegate; 10012 private auto assumeNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) { 10013 import std.traits; 10014 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_; 10015 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 10016 } 10017 10018 private auto forceNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) { 10019 try { 10020 return assumeNoThrowNoGC(t)(); 10021 } catch (Exception e) { 10022 assert(0, "OOPS!"); 10023 } 10024 } 10025 10026 struct FONSttFontImpl { 10027 stbtt_fontinfo font; 10028 bool mono; // no aa? 10029 } 10030 10031 int fons__tt_init (FONSContext context) nothrow @trusted @nogc { 10032 return 1; 10033 } 10034 10035 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc { 10036 font.mono = v; 10037 } 10038 10039 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc { 10040 return font.mono; 10041 } 10042 10043 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc { 10044 int stbError; 10045 font.font.userdata = context; 10046 forceNoThrowNoGC({ stbError = stbtt_InitFont(&font.font, data, 0); }); 10047 return stbError; 10048 } 10049 10050 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc { 10051 forceNoThrowNoGC({ stbtt_GetFontVMetrics(&font.font, ascent, descent, lineGap); }); 10052 } 10053 10054 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc { 10055 float res = void; 10056 forceNoThrowNoGC({ res = stbtt_ScaleForPixelHeight(&font.font, size); }); 10057 return res; 10058 } 10059 10060 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc { 10061 int res; 10062 forceNoThrowNoGC({ res = stbtt_FindGlyphIndex(&font.font, codepoint); }); 10063 return res; 10064 } 10065 10066 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 { 10067 forceNoThrowNoGC({ stbtt_GetGlyphHMetrics(&font.font, glyph, advance, lsb); }); 10068 forceNoThrowNoGC({ stbtt_GetGlyphBitmapBox(&font.font, glyph, scale, scale, x0, y0, x1, y1); }); 10069 return 1; 10070 } 10071 10072 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc { 10073 forceNoThrowNoGC({ stbtt_MakeGlyphBitmap(&font.font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); }); 10074 } 10075 10076 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc { 10077 // FUnits -> pixels: pointSize * resolution / (72 points per inch * units_per_em) 10078 // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#converting 10079 float res = void; 10080 forceNoThrowNoGC({ 10081 res = stbtt_GetGlyphKernAdvance(&font.font, glyph1, glyph2); 10082 res *= stbtt_ScaleForPixelHeight(&font.font, size); 10083 }); 10084 /* 10085 if (res != 0) { 10086 { 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)); } 10087 } 10088 */ 10089 //k8: dunno if this is right; i guess it isn't but... 10090 return res; 10091 } 10092 10093 // old arsd.ttf sux! ;-) 10094 static if (is(typeof(STBTT_vcubic))) { 10095 10096 static struct OutlinerData { 10097 @disable this (this); 10098 void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); } 10099 NVGPathOutline.DataStore* ol; 10100 nothrow @trusted @nogc: 10101 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; } 10102 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 10103 } 10104 10105 10106 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc { 10107 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10108 10109 bool okflag = false; 10110 10111 forceNoThrowNoGC({ 10112 int x0, y0, x1, y1; 10113 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10114 bounds[] = 0; 10115 return; 10116 } 10117 10118 if (bounds.length > 0) bounds.ptr[0] = x0; 10119 if (bounds.length > 1) bounds.ptr[1] = -y1; 10120 if (bounds.length > 2) bounds.ptr[2] = x1; 10121 if (bounds.length > 3) bounds.ptr[3] = -y0; 10122 10123 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 10124 10125 stbtt_vertex* verts = null; 10126 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); } 10127 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts); 10128 if (vcount < 1) return; 10129 10130 foreach (const ref vt; verts[0..vcount]) { 10131 switch (vt.type) { 10132 case STBTT_vmove: vg.moveTo(vt.x, transy(vt.y)); break; 10133 case STBTT_vline: vg.lineTo(vt.x, transy(vt.y)); break; 10134 case STBTT_vcurve: vg.quadTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy)); break; 10135 case STBTT_vcubic: vg.bezierTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy), vt.cx1, transy(vt.cy1)); break; 10136 default: 10137 } 10138 } 10139 10140 okflag = true; 10141 }); 10142 10143 return okflag; 10144 } 10145 10146 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 10147 bool okflag = false; 10148 10149 forceNoThrowNoGC({ 10150 int x0, y0, x1, y1; 10151 10152 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10153 ol.bounds[] = 0; 10154 return; 10155 } 10156 10157 ol.bounds.ptr[0] = x0; 10158 ol.bounds.ptr[1] = -y1; 10159 ol.bounds.ptr[2] = x1; 10160 ol.bounds.ptr[3] = -y0; 10161 10162 stbtt_vertex* verts = null; 10163 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); } 10164 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts); 10165 if (vcount < 1) return; 10166 10167 OutlinerData odata; 10168 odata.ol = ol; 10169 10170 foreach (const ref vt; verts[0..vcount]) { 10171 switch (vt.type) { 10172 case STBTT_vmove: 10173 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo); 10174 odata.ol.putArgs(odata.transx(vt.x)); 10175 odata.ol.putArgs(odata.transy(vt.y)); 10176 break; 10177 case STBTT_vline: 10178 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo); 10179 odata.ol.putArgs(odata.transx(vt.x)); 10180 odata.ol.putArgs(odata.transy(vt.y)); 10181 break; 10182 case STBTT_vcurve: 10183 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo); 10184 odata.ol.putArgs(odata.transx(vt.x)); 10185 odata.ol.putArgs(odata.transy(vt.y)); 10186 odata.ol.putArgs(odata.transx(vt.cx)); 10187 odata.ol.putArgs(odata.transy(vt.cy)); 10188 break; 10189 case STBTT_vcubic: 10190 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo); 10191 odata.ol.putArgs(odata.transx(vt.x)); 10192 odata.ol.putArgs(odata.transy(vt.y)); 10193 odata.ol.putArgs(odata.transx(vt.cx)); 10194 odata.ol.putArgs(odata.transy(vt.cy)); 10195 odata.ol.putArgs(odata.transx(vt.cx1)); 10196 odata.ol.putArgs(odata.transy(vt.cy1)); 10197 break; 10198 default: 10199 } 10200 } 10201 10202 okflag = true; 10203 }); 10204 10205 return okflag; 10206 } 10207 10208 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc { 10209 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10210 10211 bool okflag = false; 10212 10213 forceNoThrowNoGC({ 10214 int x0, y0, x1, y1; 10215 if (stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10216 if (bounds.length > 0) bounds.ptr[0] = x0; 10217 if (bounds.length > 1) bounds.ptr[1] = -y1; 10218 if (bounds.length > 2) bounds.ptr[2] = x1; 10219 if (bounds.length > 3) bounds.ptr[3] = -y0; 10220 okflag = true; 10221 } else { 10222 bounds[] = 0; 10223 } 10224 }); 10225 10226 return okflag; 10227 } 10228 10229 } // check for old stb_ttf 10230 10231 10232 } // version 10233 10234 10235 // ////////////////////////////////////////////////////////////////////////// // 10236 private: 10237 enum FONS_SCRATCH_BUF_SIZE = 64000; 10238 enum FONS_HASH_LUT_SIZE = 256; 10239 enum FONS_INIT_FONTS = 4; 10240 enum FONS_INIT_GLYPHS = 256; 10241 enum FONS_INIT_ATLAS_NODES = 256; 10242 enum FONS_VERTEX_COUNT = 1024; 10243 enum FONS_MAX_STATES = 20; 10244 enum FONS_MAX_FALLBACKS = 20; 10245 10246 10247 struct FONSglyph { 10248 uint codepoint; 10249 int index; 10250 int next; 10251 short size, blur; 10252 short x0, y0, x1, y1; 10253 short xadv, xoff, yoff; 10254 } 10255 10256 // refcounted 10257 struct FONSfontData { 10258 ubyte* data; 10259 int dataSize; 10260 bool freeData; 10261 int rc; 10262 10263 @disable this (this); // no copies 10264 void opAssign() (in auto ref FONSfontData a) { static assert(0, "no copies!"); } 10265 } 10266 10267 // won't set rc to 1 10268 FONSfontData* fons__createFontData (ubyte* adata, int asize, bool afree) nothrow @trusted @nogc { 10269 import core.stdc.stdlib : malloc; 10270 assert(adata !is null); 10271 assert(asize > 0); 10272 auto res = cast(FONSfontData*)malloc(FONSfontData.sizeof); 10273 if (res is null) assert(0, "FONS: out of memory"); 10274 res.data = adata; 10275 res.dataSize = asize; 10276 res.freeData = afree; 10277 res.rc = 0; 10278 return res; 10279 } 10280 10281 void incref (FONSfontData* fd) pure nothrow @trusted @nogc { 10282 pragma(inline, true); 10283 if (fd !is null) ++fd.rc; 10284 } 10285 10286 void decref (ref FONSfontData* fd) nothrow @trusted @nogc { 10287 if (fd !is null) { 10288 if (--fd.rc == 0) { 10289 import core.stdc.stdlib : free; 10290 if (fd.freeData && fd.data !is null) { 10291 free(fd.data); 10292 fd.data = null; 10293 } 10294 free(fd); 10295 fd = null; 10296 } 10297 } 10298 } 10299 10300 // as creating and destroying fonts is a rare operation, malloc some data 10301 struct FONSfont { 10302 FONSttFontImpl font; 10303 char* name; // malloced, strz, always lowercase 10304 uint namelen; 10305 uint namehash; 10306 char* path; // malloced, strz 10307 FONSfontData* fdata; 10308 float ascender; 10309 float descender; 10310 float lineh; 10311 FONSglyph* glyphs; 10312 int cglyphs; 10313 int nglyphs; 10314 int[FONS_HASH_LUT_SIZE] lut; 10315 int[FONS_MAX_FALLBACKS] fallbacks; 10316 int nfallbacks; 10317 10318 @disable this (this); 10319 void opAssign() (in auto ref FONSfont a) { static assert(0, "no copies"); } 10320 10321 static uint djbhash (const(void)[] s) pure nothrow @safe @nogc { 10322 uint hash = 5381; 10323 foreach (ubyte b; cast(const(ubyte)[])s) { 10324 if (b >= 'A' && b <= 'Z') b += 32; // poor man's tolower 10325 hash = ((hash<<5)+hash)+b; 10326 } 10327 return hash; 10328 } 10329 10330 // except glyphs 10331 void freeMemory () nothrow @trusted @nogc { 10332 import core.stdc.stdlib : free; 10333 if (name !is null) { free(name); name = null; } 10334 namelen = namehash = 0; 10335 if (path !is null) { free(path); path = null; } 10336 fdata.decref(); 10337 } 10338 10339 // this also calcs name hash 10340 void setName (const(char)[] aname) nothrow @trusted @nogc { 10341 //{ import core.stdc.stdio; printf("setname: [%.*s]\n", cast(uint)aname.length, aname.ptr); } 10342 import core.stdc.stdlib : realloc; 10343 if (aname.length > int.max/32) assert(0, "FONS: invalid font name"); 10344 namelen = cast(uint)aname.length; 10345 name = cast(char*)realloc(name, namelen+1); 10346 if (name is null) assert(0, "FONS: out of memory"); 10347 if (aname.length) name[0..aname.length] = aname[]; 10348 name[namelen] = 0; 10349 // lowercase it 10350 foreach (ref char ch; name[0..namelen]) if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower 10351 namehash = djbhash(name[0..namelen]); 10352 //{ import core.stdc.stdio; printf(" [%s] [%.*s] [0x%08x]\n", name, namelen, name, namehash); } 10353 } 10354 10355 void setPath (const(char)[] apath) nothrow @trusted @nogc { 10356 import core.stdc.stdlib : realloc; 10357 if (apath.length > int.max/32) assert(0, "FONS: invalid font path"); 10358 path = cast(char*)realloc(path, apath.length+1); 10359 if (path is null) assert(0, "FONS: out of memory"); 10360 if (apath.length) path[0..apath.length] = apath[]; 10361 path[apath.length] = 0; 10362 } 10363 10364 // this won't check hash 10365 bool nameEqu (const(char)[] aname) const pure nothrow @trusted @nogc { 10366 //{ import core.stdc.stdio; printf("nameEqu: aname=[%.*s]; namelen=%u; aslen=%u\n", cast(uint)aname.length, aname.ptr, namelen, cast(uint)aname.length); } 10367 if (namelen != aname.length) return false; 10368 const(char)* ns = name; 10369 // name part 10370 foreach (char ch; aname) { 10371 if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower 10372 if (ch != *ns++) return false; 10373 } 10374 // done (length was checked earlier) 10375 return true; 10376 } 10377 10378 void clear () nothrow @trusted @nogc { 10379 import core.stdc.stdlib : free; 10380 import core.stdc.string : memset; 10381 if (glyphs !is null) free(glyphs); 10382 freeMemory(); 10383 memset(&this, 0, this.sizeof); 10384 } 10385 10386 FONSglyph* allocGlyph () nothrow @trusted @nogc { 10387 if (nglyphs+1 > cglyphs) { 10388 import core.stdc.stdlib : realloc; 10389 cglyphs = (cglyphs == 0 ? 8 : cglyphs*2); 10390 glyphs = cast(FONSglyph*)realloc(glyphs, FONSglyph.sizeof*cglyphs); 10391 if (glyphs is null) assert(0, "FontStash: out of memory"); 10392 } 10393 ++nglyphs; 10394 return &glyphs[nglyphs-1]; 10395 } 10396 } 10397 10398 void kill (ref FONSfont* font) nothrow @trusted @nogc { 10399 if (font !is null) { 10400 import core.stdc.stdlib : free; 10401 font.clear(); 10402 free(font); 10403 font = null; 10404 } 10405 } 10406 10407 10408 // ////////////////////////////////////////////////////////////////////////// // 10409 struct FONSstate { 10410 int font; 10411 NVGTextAlign talign; 10412 float size = 0; 10413 float blur = 0; 10414 float spacing = 0; 10415 } 10416 10417 10418 // ////////////////////////////////////////////////////////////////////////// // 10419 // atlas based on Skyline Bin Packer by Jukka Jylänki 10420 alias FONSAtlas = FONSatlasInternal*; 10421 10422 struct FONSatlasInternal { 10423 static struct Node { 10424 short x, y, width; 10425 } 10426 10427 int width, height; 10428 Node* nodes; 10429 int nnodes; 10430 int cnodes; 10431 10432 @disable this (this); 10433 void opAssign() (in auto ref FONSatlasInternal a) { static assert(0, "no copies"); } 10434 10435 nothrow @trusted @nogc: 10436 static FONSAtlas create (int w, int h, int nnodes) { 10437 import core.stdc.stdlib : malloc; 10438 import core.stdc.string : memset; 10439 10440 FONSAtlas atlas = cast(FONSAtlas)malloc(FONSatlasInternal.sizeof); 10441 if (atlas is null) assert(0, "FontStash: out of memory"); 10442 memset(atlas, 0, FONSatlasInternal.sizeof); 10443 10444 atlas.width = w; 10445 atlas.height = h; 10446 10447 // allocate space for skyline nodes 10448 atlas.nodes = cast(Node*)malloc(Node.sizeof*nnodes); 10449 if (atlas.nodes is null) assert(0, "FontStash: out of memory"); 10450 memset(atlas.nodes, 0, Node.sizeof*nnodes); 10451 atlas.nnodes = 0; 10452 atlas.cnodes = nnodes; 10453 10454 // init root node 10455 atlas.nodes[0].x = 0; 10456 atlas.nodes[0].y = 0; 10457 atlas.nodes[0].width = cast(short)w; 10458 ++atlas.nnodes; 10459 10460 return atlas; 10461 } 10462 10463 void clear () { 10464 import core.stdc.stdlib : free; 10465 import core.stdc.string : memset; 10466 10467 if (nodes !is null) free(nodes); 10468 memset(&this, 0, this.sizeof); 10469 } 10470 10471 void insertNode (int idx, int x, int y, int w) { 10472 if (nnodes+1 > cnodes) { 10473 import core.stdc.stdlib : realloc; 10474 cnodes = (cnodes == 0 ? 8 : cnodes*2); 10475 nodes = cast(Node*)realloc(nodes, Node.sizeof*cnodes); 10476 if (nodes is null) assert(0, "FontStash: out of memory"); 10477 } 10478 for (int i = nnodes; i > idx; --i) nodes[i] = nodes[i-1]; 10479 nodes[idx].x = cast(short)x; 10480 nodes[idx].y = cast(short)y; 10481 nodes[idx].width = cast(short)w; 10482 ++nnodes; 10483 } 10484 10485 void removeNode (int idx) { 10486 if (nnodes == 0) return; 10487 foreach (immutable int i; idx+1..nnodes) nodes[i-1] = nodes[i]; 10488 --nnodes; 10489 } 10490 10491 // insert node for empty space 10492 void expand (int w, int h) { 10493 if (w > width) insertNode(nnodes, width, 0, w-width); 10494 width = w; 10495 height = h; 10496 } 10497 10498 void reset (int w, int h) { 10499 width = w; 10500 height = h; 10501 nnodes = 0; 10502 // init root node 10503 nodes[0].x = 0; 10504 nodes[0].y = 0; 10505 nodes[0].width = cast(short)w; 10506 ++nnodes; 10507 } 10508 10509 void addSkylineLevel (int idx, int x, int y, int w, int h) { 10510 insertNode(idx, x, y+h, w); 10511 10512 // delete skyline segments that fall under the shadow of the new segment 10513 for (int i = idx+1; i < nnodes; ++i) { 10514 if (nodes[i].x < nodes[i-1].x+nodes[i-1].width) { 10515 int shrink = nodes[i-1].x+nodes[i-1].width-nodes[i].x; 10516 nodes[i].x += cast(short)shrink; 10517 nodes[i].width -= cast(short)shrink; 10518 if (nodes[i].width <= 0) { 10519 removeNode(i); 10520 --i; 10521 } else { 10522 break; 10523 } 10524 } else { 10525 break; 10526 } 10527 } 10528 10529 // Merge same height skyline segments that are next to each other 10530 for (int i = 0; i < nnodes-1; ++i) { 10531 if (nodes[i].y == nodes[i+1].y) { 10532 nodes[i].width += nodes[i+1].width; 10533 removeNode(i+1); 10534 --i; 10535 } 10536 } 10537 } 10538 10539 // checks if there is enough space at the location of skyline span 'i', 10540 // and return the max height of all skyline spans under that at that location, 10541 // (think tetris block being dropped at that position); or -1 if no space found 10542 int rectFits (int i, int w, int h) { 10543 int x = nodes[i].x; 10544 int y = nodes[i].y; 10545 if (x+w > width) return -1; 10546 int spaceLeft = w; 10547 while (spaceLeft > 0) { 10548 if (i == nnodes) return -1; 10549 y = nvg__max(y, nodes[i].y); 10550 if (y+h > height) return -1; 10551 spaceLeft -= nodes[i].width; 10552 ++i; 10553 } 10554 return y; 10555 } 10556 10557 bool addRect (int rw, int rh, int* rx, int* ry) { 10558 int besth = height, bestw = width, besti = -1; 10559 int bestx = -1, besty = -1; 10560 10561 // Bottom left fit heuristic. 10562 for (int i = 0; i < nnodes; ++i) { 10563 int y = rectFits(i, rw, rh); 10564 if (y != -1) { 10565 if (y+rh < besth || (y+rh == besth && nodes[i].width < bestw)) { 10566 besti = i; 10567 bestw = nodes[i].width; 10568 besth = y+rh; 10569 bestx = nodes[i].x; 10570 besty = y; 10571 } 10572 } 10573 } 10574 10575 if (besti == -1) return false; 10576 10577 // perform the actual packing 10578 addSkylineLevel(besti, bestx, besty, rw, rh); 10579 10580 *rx = bestx; 10581 *ry = besty; 10582 10583 return true; 10584 } 10585 } 10586 10587 void kill (ref FONSAtlas atlas) nothrow @trusted @nogc { 10588 if (atlas !is null) { 10589 import core.stdc.stdlib : free; 10590 atlas.clear(); 10591 free(atlas); 10592 atlas = null; 10593 } 10594 } 10595 10596 10597 // ////////////////////////////////////////////////////////////////////////// // 10598 /// FontStash context (internal definition). Don't use it derectly, it was made public only to generate documentation. 10599 /// Group: font_stash 10600 public struct FONScontextInternal { 10601 private: 10602 FONSParams params; 10603 float itw, ith; 10604 ubyte* texData; 10605 int[4] dirtyRect; 10606 FONSfont** fonts; // actually, a simple hash table; can't grow yet 10607 int cfonts; // allocated 10608 int nfonts; // used (so we can track hash table stats) 10609 int* hashidx; // [hsize] items; holds indicies in [fonts] array 10610 int hused, hsize;// used items and total items in [hashidx] 10611 FONSAtlas atlas; 10612 ubyte* scratch; 10613 int nscratch; 10614 FONSstate[FONS_MAX_STATES] states; 10615 int nstates; 10616 10617 void delegate (FONSError error, int val) nothrow @trusted @nogc handleError; 10618 10619 @disable this (this); 10620 void opAssign() (in auto ref FONScontextInternal ctx) { static assert(0, "FONS copying is not allowed"); } 10621 10622 private: 10623 static bool strequci (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 10624 if (s0.length != s1.length) return false; 10625 const(char)* sp0 = s0.ptr; 10626 const(char)* sp1 = s1.ptr; 10627 foreach (immutable _; 0..s0.length) { 10628 char c0 = *sp0++; 10629 char c1 = *sp1++; 10630 if (c0 != c1) { 10631 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man tolower 10632 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man tolower 10633 if (c0 != c1) return false; 10634 } 10635 } 10636 return true; 10637 } 10638 10639 inout(FONSstate)* getState () inout pure nothrow @trusted @nogc { 10640 pragma(inline, true); 10641 return cast(inout)(&states[(nstates > 0 ? nstates-1 : 0)]); 10642 } 10643 10644 // simple linear probing; returns [FONS_INVALID] if not found 10645 int findNameInHash (const(char)[] name) const pure nothrow @trusted @nogc { 10646 if (nfonts == 0) return FONS_INVALID; 10647 auto nhash = FONSfont.djbhash(name); 10648 //{ import core.stdc.stdio; printf("findinhash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10649 auto res = nhash%hsize; 10650 // hash will never be 100% full, so this loop is safe 10651 for (;;) { 10652 int idx = hashidx[res]; 10653 if (idx == -1) break; 10654 auto font = fonts[idx]; 10655 if (font is null) assert(0, "FONS internal error"); 10656 if (font.namehash == nhash && font.nameEqu(name)) return idx; 10657 //{ import core.stdc.stdio; printf("findinhash chained: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10658 res = (res+1)%hsize; 10659 } 10660 return FONS_INVALID; 10661 } 10662 10663 // should be called $(B before) freeing `fonts[fidx]` 10664 void removeIndexFromHash (int fidx) nothrow @trusted @nogc { 10665 if (fidx < 0 || fidx >= nfonts) assert(0, "FONS internal error"); 10666 if (fonts[fidx] is null) assert(0, "FONS internal error"); 10667 if (hused != nfonts) assert(0, "FONS internal error"); 10668 auto nhash = fonts[fidx].namehash; 10669 auto res = nhash%hsize; 10670 // hash will never be 100% full, so this loop is safe 10671 for (;;) { 10672 int idx = hashidx[res]; 10673 if (idx == -1) assert(0, "FONS INTERNAL ERROR"); 10674 if (idx == fidx) { 10675 // i found her! copy rest here 10676 int nidx = (res+1)%hsize; 10677 for (;;) { 10678 if ((hashidx[res] = hashidx[nidx]) == -1) break; // so it will copy `-1` too 10679 res = nidx; 10680 nidx = (nidx+1)%hsize; 10681 } 10682 return; 10683 } 10684 res = (res+1)%hsize; 10685 } 10686 } 10687 10688 // add font with the given index to hash 10689 // prerequisite: font should not exists in hash 10690 void addIndexToHash (int idx) nothrow @trusted @nogc { 10691 if (idx < 0 || idx >= nfonts) assert(0, "FONS internal error"); 10692 if (fonts[idx] is null) assert(0, "FONS internal error"); 10693 import core.stdc.stdlib : realloc; 10694 auto nhash = fonts[idx].namehash; 10695 //{ import core.stdc.stdio; printf("addtohash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10696 // allocate new hash table if there was none 10697 if (hsize == 0) { 10698 enum InitSize = 256; 10699 auto newlist = cast(int*)realloc(null, InitSize*hashidx[0].sizeof); 10700 if (newlist is null) assert(0, "FONS: out of memory"); 10701 newlist[0..InitSize] = -1; 10702 hsize = InitSize; 10703 hused = 0; 10704 hashidx = newlist; 10705 } 10706 int res = cast(int)(nhash%hsize); 10707 // need to rehash? we want our hash table 50% full at max 10708 if (hashidx[res] != -1 && hused >= hsize/2) { 10709 uint nsz = hsize*2; 10710 if (nsz > 1024*1024) assert(0, "FONS: out of memory for fonts"); 10711 auto newlist = cast(int*)realloc(fonts, nsz*hashidx[0].sizeof); 10712 if (newlist is null) assert(0, "FONS: out of memory"); 10713 newlist[0..nsz] = -1; 10714 hused = 0; 10715 // rehash 10716 foreach (immutable fidx, FONSfont* ff; fonts[0..nfonts]) { 10717 if (ff is null) continue; 10718 // find slot for this font (guaranteed to have one) 10719 uint newslot = ff.namehash%nsz; 10720 while (newlist[newslot] != -1) newslot = (newslot+1)%nsz; 10721 newlist[newslot] = cast(int)fidx; 10722 ++hused; 10723 } 10724 hsize = nsz; 10725 hashidx = newlist; 10726 // we added everything, including [idx], so nothing more to do here 10727 } else { 10728 // find slot (guaranteed to have one) 10729 while (hashidx[res] != -1) res = (res+1)%hsize; 10730 // i found her! 10731 hashidx[res] = idx; 10732 ++hused; 10733 } 10734 } 10735 10736 void addWhiteRect (int w, int h) nothrow @trusted @nogc { 10737 int gx, gy; 10738 ubyte* dst; 10739 10740 if (!atlas.addRect(w, h, &gx, &gy)) return; 10741 10742 // Rasterize 10743 dst = &texData[gx+gy*params.width]; 10744 foreach (int y; 0..h) { 10745 foreach (int x; 0..w) { 10746 dst[x] = 0xff; 10747 } 10748 dst += params.width; 10749 } 10750 10751 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], gx); 10752 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], gy); 10753 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], gx+w); 10754 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], gy+h); 10755 } 10756 10757 // returns fid, not hash slot 10758 int allocFontAt (int atidx) nothrow @trusted @nogc { 10759 if (atidx >= 0 && atidx >= nfonts) assert(0, "internal NanoVega fontstash error"); 10760 10761 if (atidx < 0) { 10762 if (nfonts >= cfonts) { 10763 import core.stdc.stdlib : realloc; 10764 import core.stdc.string : memset; 10765 assert(nfonts == cfonts); 10766 int newsz = cfonts+64; 10767 if (newsz > 65535) assert(0, "FONS: too many fonts"); 10768 auto newlist = cast(FONSfont**)realloc(fonts, newsz*(FONSfont*).sizeof); 10769 if (newlist is null) assert(0, "FONS: out of memory"); 10770 memset(newlist+cfonts, 0, (newsz-cfonts)*(FONSfont*).sizeof); 10771 fonts = newlist; 10772 cfonts = newsz; 10773 } 10774 assert(nfonts < cfonts); 10775 } 10776 10777 FONSfont* font = cast(FONSfont*)malloc(FONSfont.sizeof); 10778 if (font is null) assert(0, "FONS: out of memory"); 10779 memset(font, 0, FONSfont.sizeof); 10780 10781 font.glyphs = cast(FONSglyph*)malloc(FONSglyph.sizeof*FONS_INIT_GLYPHS); 10782 if (font.glyphs is null) assert(0, "FONS: out of memory"); 10783 font.cglyphs = FONS_INIT_GLYPHS; 10784 font.nglyphs = 0; 10785 10786 if (atidx < 0) { 10787 fonts[nfonts] = font; 10788 return nfonts++; 10789 } else { 10790 fonts[atidx] = font; 10791 return atidx; 10792 } 10793 } 10794 10795 // 0: ooops 10796 int findGlyphForCP (FONSfont *font, dchar dch, FONSfont** renderfont) nothrow @trusted @nogc { 10797 if (renderfont !is null) *renderfont = font; 10798 if (font is null || font.fdata is null) return 0; 10799 auto g = fons__tt_getGlyphIndex(&font.font, cast(uint)dch); 10800 // try to find the glyph in fallback fonts 10801 if (g == 0) { 10802 foreach (immutable i; 0..font.nfallbacks) { 10803 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]]; 10804 if (fallbackFont !is null) { 10805 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, cast(uint)dch); 10806 if (fallbackIndex != 0) { 10807 if (renderfont !is null) *renderfont = fallbackFont; 10808 return g; 10809 } 10810 } 10811 } 10812 // no char, try to find replacement one 10813 if (dch != 0xFFFD) { 10814 g = fons__tt_getGlyphIndex(&font.font, 0xFFFD); 10815 if (g == 0) { 10816 foreach (immutable i; 0..font.nfallbacks) { 10817 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]]; 10818 if (fallbackFont !is null) { 10819 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, 0xFFFD); 10820 if (fallbackIndex != 0) { 10821 if (renderfont !is null) *renderfont = fallbackFont; 10822 return g; 10823 } 10824 } 10825 } 10826 } 10827 } 10828 } 10829 return g; 10830 } 10831 10832 void clear () nothrow @trusted @nogc { 10833 import core.stdc.stdlib : free; 10834 10835 if (params.renderDelete !is null) params.renderDelete(params.userPtr); 10836 foreach (immutable int i; 0..nfonts) fonts[i].kill(); 10837 10838 if (atlas !is null) atlas.kill(); 10839 if (fonts !is null) free(fonts); 10840 if (texData !is null) free(texData); 10841 if (scratch !is null) free(scratch); 10842 if (hashidx !is null) free(hashidx); 10843 } 10844 10845 // add font from another fontstash 10846 int addCookedFont (FONSfont* font) nothrow @trusted @nogc { 10847 if (font is null || font.fdata is null) return FONS_INVALID; 10848 font.fdata.incref(); 10849 auto res = addFontWithData(font.name[0..font.namelen], font.fdata, !font.font.mono); 10850 if (res == FONS_INVALID) font.fdata.decref(); // oops 10851 return res; 10852 } 10853 10854 // fdata refcount must be already increased; it won't be changed 10855 int addFontWithData (const(char)[] name, FONSfontData* fdata, bool defAA) nothrow @trusted @nogc { 10856 int i, ascent, descent, fh, lineGap; 10857 10858 if (name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID; 10859 if (name.length > 32767) return FONS_INVALID; 10860 if (fdata is null) return FONS_INVALID; 10861 10862 // find a font with the given name 10863 int newidx; 10864 FONSfont* oldfont = null; 10865 int oldidx = findNameInHash(name); 10866 if (oldidx != FONS_INVALID) { 10867 // replacement font 10868 oldfont = fonts[oldidx]; 10869 newidx = oldidx; 10870 } else { 10871 // new font, allocate new bucket 10872 newidx = -1; 10873 } 10874 10875 newidx = allocFontAt(newidx); 10876 FONSfont* font = fonts[newidx]; 10877 font.setName(name); 10878 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; // init hash lookup 10879 font.fdata = fdata; // set the font data (don't change reference count) 10880 fons__tt_setMono(&this, &font.font, !defAA); 10881 10882 // init font 10883 nscratch = 0; 10884 if (!fons__tt_loadFont(&this, &font.font, fdata.data, fdata.dataSize)) { 10885 // we promised to not free data on error, so just clear the data store (it will be freed by the caller) 10886 font.fdata = null; 10887 font.kill(); 10888 if (oldidx != FONS_INVALID) { 10889 assert(oldidx == newidx); 10890 fonts[oldidx] = oldfont; 10891 } else { 10892 assert(newidx == nfonts-1); 10893 fonts[newidx] = null; 10894 --nfonts; 10895 } 10896 return FONS_INVALID; 10897 } else { 10898 // free old font data, if any 10899 if (oldfont !is null) oldfont.kill(); 10900 } 10901 10902 // add font to name hash 10903 if (oldidx == FONS_INVALID) addIndexToHash(newidx); 10904 10905 // store normalized line height 10906 // the real line height is got by multiplying the lineh by font size 10907 fons__tt_getFontVMetrics(&font.font, &ascent, &descent, &lineGap); 10908 fh = ascent-descent; 10909 font.ascender = cast(float)ascent/cast(float)fh; 10910 font.descender = cast(float)descent/cast(float)fh; 10911 font.lineh = cast(float)(fh+lineGap)/cast(float)fh; 10912 10913 //{ import core.stdc.stdio; printf("created font [%.*s] (idx=%d)...\n", cast(uint)name.length, name.ptr, idx); } 10914 return newidx; 10915 } 10916 10917 // isize: size*10 10918 float getVertAlign (FONSfont* font, NVGTextAlign talign, short isize) pure nothrow @trusted @nogc { 10919 if (params.isZeroTopLeft) { 10920 final switch (talign.vertical) { 10921 case NVGTextAlign.V.Top: return font.ascender*cast(float)isize/10.0f; 10922 case NVGTextAlign.V.Middle: return (font.ascender+font.descender)/2.0f*cast(float)isize/10.0f; 10923 case NVGTextAlign.V.Baseline: return 0.0f; 10924 case NVGTextAlign.V.Bottom: return font.descender*cast(float)isize/10.0f; 10925 } 10926 } else { 10927 final switch (talign.vertical) { 10928 case NVGTextAlign.V.Top: return -font.ascender*cast(float)isize/10.0f; 10929 case NVGTextAlign.V.Middle: return -(font.ascender+font.descender)/2.0f*cast(float)isize/10.0f; 10930 case NVGTextAlign.V.Baseline: return 0.0f; 10931 case NVGTextAlign.V.Bottom: return -font.descender*cast(float)isize/10.0f; 10932 } 10933 } 10934 assert(0); 10935 } 10936 10937 public: 10938 /** Create new FontStash context. It can be destroyed with `fs.kill()` later. 10939 * 10940 * Note that if you don't plan to rasterize glyphs (i.e. you will use created 10941 * FontStash only to measure text), you can simply pass `FONSParams.init`). 10942 */ 10943 static FONSContext create() (in auto ref FONSParams params) nothrow @trusted @nogc { 10944 import core.stdc.string : memcpy; 10945 10946 FONSContext stash = null; 10947 10948 // allocate memory for the font stash 10949 stash = cast(FONSContext)malloc(FONScontextInternal.sizeof); 10950 if (stash is null) goto error; 10951 memset(stash, 0, FONScontextInternal.sizeof); 10952 10953 memcpy(&stash.params, ¶ms, params.sizeof); 10954 if (stash.params.width < 1) stash.params.width = 32; 10955 if (stash.params.height < 1) stash.params.height = 32; 10956 10957 // allocate scratch buffer 10958 stash.scratch = cast(ubyte*)malloc(FONS_SCRATCH_BUF_SIZE); 10959 if (stash.scratch is null) goto error; 10960 10961 // initialize implementation library 10962 if (!fons__tt_init(stash)) goto error; 10963 10964 if (stash.params.renderCreate !is null) { 10965 if (!stash.params.renderCreate(stash.params.userPtr, stash.params.width, stash.params.height)) goto error; 10966 } 10967 10968 stash.atlas = FONSAtlas.create(stash.params.width, stash.params.height, FONS_INIT_ATLAS_NODES); 10969 if (stash.atlas is null) goto error; 10970 10971 // don't allocate space for fonts: hash manager will do that for us later 10972 //stash.cfonts = 0; 10973 //stash.nfonts = 0; 10974 10975 // create texture for the cache 10976 stash.itw = 1.0f/stash.params.width; 10977 stash.ith = 1.0f/stash.params.height; 10978 stash.texData = cast(ubyte*)malloc(stash.params.width*stash.params.height); 10979 if (stash.texData is null) goto error; 10980 memset(stash.texData, 0, stash.params.width*stash.params.height); 10981 10982 stash.dirtyRect.ptr[0] = stash.params.width; 10983 stash.dirtyRect.ptr[1] = stash.params.height; 10984 stash.dirtyRect.ptr[2] = 0; 10985 stash.dirtyRect.ptr[3] = 0; 10986 10987 // add white rect at 0, 0 for debug drawing 10988 stash.addWhiteRect(2, 2); 10989 10990 stash.pushState(); 10991 stash.clearState(); 10992 10993 return stash; 10994 10995 error: 10996 stash.kill(); 10997 return null; 10998 } 10999 11000 public: 11001 /// Add fallback font (FontStash will try to find missing glyph in all fallback fonts before giving up). 11002 bool addFallbackFont (int base, int fallback) nothrow @trusted @nogc { 11003 FONSfont* baseFont = fonts[base]; 11004 if (baseFont !is null && baseFont.nfallbacks < FONS_MAX_FALLBACKS) { 11005 baseFont.fallbacks.ptr[baseFont.nfallbacks++] = fallback; 11006 return true; 11007 } 11008 return false; 11009 } 11010 11011 @property void size (float size) nothrow @trusted @nogc { pragma(inline, true); getState.size = size; } /// Set current font size. 11012 @property float size () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.size; } /// Get current font size. 11013 11014 @property void spacing (float spacing) nothrow @trusted @nogc { pragma(inline, true); getState.spacing = spacing; } /// Set current letter spacing. 11015 @property float spacing () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.spacing; } /// Get current letter spacing. 11016 11017 @property void blur (float blur) nothrow @trusted @nogc { pragma(inline, true); getState.blur = blur; } /// Set current letter blur. 11018 @property float blur () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.blur; } /// Get current letter blur. 11019 11020 @property void textAlign (NVGTextAlign talign) nothrow @trusted @nogc { pragma(inline, true); getState.talign = talign; } /// Set current text align. 11021 @property NVGTextAlign textAlign () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.talign; } /// Get current text align. 11022 11023 @property void fontId (int font) nothrow @trusted @nogc { pragma(inline, true); getState.font = font; } /// Set current font id. 11024 @property int fontId () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.font; } /// Get current font id. 11025 11026 @property void fontId (const(char)[] name) nothrow @trusted @nogc { pragma(inline, true); getState.font = getFontByName(name); } /// Set current font using its name. 11027 11028 /// Check if FontStash has a font with the given name loaded. 11029 bool hasFont (const(char)[] name) const pure nothrow @trusted @nogc { pragma(inline, true); return (getFontByName(name) >= 0); } 11030 11031 /// Get AA for the current font, or for the specified font. 11032 bool getFontAA (int font=-1) nothrow @trusted @nogc { 11033 FONSstate* state = getState; 11034 if (font < 0) font = state.font; 11035 if (font < 0 || font >= nfonts) return false; 11036 FONSfont* f = fonts[font]; 11037 return (f !is null ? !f.font.mono : false); 11038 } 11039 11040 /// Push current state. Returns `false` if state stack overflowed. 11041 bool pushState () nothrow @trusted @nogc { 11042 if (nstates >= FONS_MAX_STATES) { 11043 if (handleError !is null) handleError(FONSError.StatesOverflow, 0); 11044 return false; 11045 } 11046 if (nstates > 0) { 11047 import core.stdc.string : memcpy; 11048 memcpy(&states[nstates], &states[nstates-1], FONSstate.sizeof); 11049 } 11050 ++nstates; 11051 return true; 11052 } 11053 11054 /// Pop current state. Returns `false` if state stack underflowed. 11055 bool popState () nothrow @trusted @nogc { 11056 if (nstates <= 1) { 11057 if (handleError !is null) handleError(FONSError.StatesUnderflow, 0); 11058 return false; 11059 } 11060 --nstates; 11061 return true; 11062 } 11063 11064 /// Clear current state (i.e. set it to some sane defaults). 11065 void clearState () nothrow @trusted @nogc { 11066 FONSstate* state = getState; 11067 state.size = 12.0f; 11068 state.font = 0; 11069 state.blur = 0; 11070 state.spacing = 0; 11071 state.talign.reset; 11072 } 11073 11074 private enum NoAlias = ":noaa"; 11075 11076 /** Add font to FontStash. 11077 * 11078 * Load scalable font from disk, and add it to FontStash. If you will try to load a font 11079 * with same name and path several times, FontStash will load it only once. Also, you can 11080 * load new disk font for any existing logical font. 11081 * 11082 * Params: 11083 * name = logical font name, that will be used to select this font later. 11084 * path = path to disk file with your font. 11085 * defAA = should FontStash use antialiased font rasterizer? 11086 * 11087 * Returns: 11088 * font id or [FONS_INVALID]. 11089 */ 11090 int addFont (const(char)[] name, const(char)[] path, bool defAA=false) nothrow @trusted { 11091 if (path.length == 0 || name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID; 11092 if (path.length > 32768) return FONS_INVALID; // arbitrary limit 11093 11094 // if font path ends with ":noaa", turn off antialiasing 11095 if (path.length >= NoAlias.length && strequci(path[$-NoAlias.length..$], NoAlias)) { 11096 path = path[0..$-NoAlias.length]; 11097 if (path.length == 0) return FONS_INVALID; 11098 defAA = false; 11099 } 11100 11101 // if font name ends with ":noaa", turn off antialiasing 11102 if (name.length > NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) { 11103 name = name[0..$-NoAlias.length]; 11104 defAA = false; 11105 } 11106 11107 // find a font with the given name 11108 int fidx = findNameInHash(name); 11109 //{ import core.stdc.stdio; printf("loading font '%.*s' [%s] (fidx=%d)...\n", cast(uint)path.length, path.ptr, fontnamebuf.ptr, fidx); } 11110 11111 int loadFontFile (const(char)[] path) { 11112 // check if existing font (if any) has the same path 11113 if (fidx >= 0) { 11114 import core.stdc.string : strlen; 11115 auto plen = (fonts[fidx].path !is null ? strlen(fonts[fidx].path) : 0); 11116 version(Posix) { 11117 //{ 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); } 11118 if (plen == path.length && fonts[fidx].path[0..plen] == path) { 11119 //{ import core.stdc.stdio; printf("*** font [%.*s] already loaded from [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)plen, path.ptr); } 11120 // i found her! 11121 return fidx; 11122 } 11123 } else { 11124 if (plen == path.length && strequci(fonts[fidx].path[0..plen], path)) { 11125 // i found her! 11126 return fidx; 11127 } 11128 } 11129 } 11130 version(Windows) { 11131 // special shitdows check: this will reject fontconfig font names (but still allow things like "c:myfont") 11132 foreach (immutable char ch; path[(path.length >= 2 && path[1] == ':' ? 2 : 0)..$]) if (ch == ':') return FONS_INVALID; 11133 } 11134 // either no such font, or different path 11135 //{ import core.stdc.stdio; printf("trying font [%.*s] from file [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)path.length, path.ptr); } 11136 int xres = FONS_INVALID; 11137 try { 11138 import core.stdc.stdlib : free, malloc; 11139 static if (NanoVegaHasIVVFS) { 11140 auto fl = VFile(path); 11141 auto dataSize = fl.size; 11142 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID; 11143 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize); 11144 if (data is null) assert(0, "out of memory in NanoVega fontstash"); 11145 scope(failure) free(data); // oops 11146 fl.rawReadExact(data[0..cast(uint)dataSize]); 11147 fl.close(); 11148 } else { 11149 import core.stdc.stdio : FILE, fopen, fclose, fread, ftell, fseek; 11150 import std.internal.cstring : tempCString; 11151 auto fl = fopen(path.tempCString, "rb"); 11152 if (fl is null) return FONS_INVALID; 11153 scope(exit) fclose(fl); 11154 if (fseek(fl, 0, 2/*SEEK_END*/) != 0) return FONS_INVALID; 11155 auto dataSize = ftell(fl); 11156 if (fseek(fl, 0, 0/*SEEK_SET*/) != 0) return FONS_INVALID; 11157 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID; 11158 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize); 11159 if (data is null) assert(0, "out of memory in NanoVega fontstash"); 11160 scope(failure) free(data); // oops 11161 ubyte* dptr = data; 11162 auto left = cast(uint)dataSize; 11163 while (left > 0) { 11164 auto rd = fread(dptr, 1, left, fl); 11165 if (rd == 0) { free(data); return FONS_INVALID; } // unexpected EOF or reading error, it doesn't matter 11166 dptr += rd; 11167 left -= rd; 11168 } 11169 } 11170 scope(failure) free(data); // oops 11171 // create font data 11172 FONSfontData* fdata = fons__createFontData(data, cast(int)dataSize, true); // free data 11173 fdata.incref(); 11174 xres = addFontWithData(name, fdata, defAA); 11175 if (xres == FONS_INVALID) { 11176 fdata.decref(); // this will free [data] and [fdata] 11177 } else { 11178 // remember path 11179 fonts[xres].setPath(path); 11180 } 11181 } catch (Exception e) { 11182 // oops; sorry 11183 } 11184 return xres; 11185 } 11186 11187 // first try direct path 11188 auto res = loadFontFile(path); 11189 // if loading failed, try fontconfig (if fontconfig is available) 11190 static if (NanoVegaHasFontConfig) { 11191 if (res == FONS_INVALID && fontconfigAvailable) { 11192 // idiotic fontconfig NEVER fails; let's skip it if `path` looks like a path 11193 bool ok = true; 11194 if (path.length > 4 && (path[$-4..$] == ".ttf" || path[$-4..$] == ".ttc")) ok = false; 11195 if (ok) { foreach (immutable char ch; path) if (ch == '/') { ok = false; break; } } 11196 if (ok) { 11197 import std.internal.cstring : tempCString; 11198 FcPattern* pat = FcNameParse(path.tempCString); 11199 if (pat !is null) { 11200 scope(exit) FcPatternDestroy(pat); 11201 if (FcConfigSubstitute(null, pat, FcMatchPattern)) { 11202 FcDefaultSubstitute(pat); 11203 // find the font 11204 FcResult result; 11205 FcPattern* font = FcFontMatch(null, pat, &result); 11206 if (font !is null) { 11207 scope(exit) FcPatternDestroy(font); 11208 char* file = null; 11209 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) { 11210 if (file !is null && file[0]) { 11211 import core.stdc.string : strlen; 11212 res = loadFontFile(file[0..strlen(file)]); 11213 } 11214 } 11215 } 11216 } 11217 } 11218 } 11219 } 11220 } 11221 return res; 11222 } 11223 11224 /** Add font to FontStash, using data from memory. 11225 * 11226 * And already loaded font to FontStash. You can replace existing logical fonts. 11227 * But note that you can't remove logical font by passing "empty" data. 11228 * 11229 * $(WARNING If [FONS_INVALID] returned, `data` won't be freed even if `freeData` is `true`.) 11230 * 11231 * Params: 11232 * name = logical font name, that will be used to select this font later. 11233 * data = font data. 11234 * dataSize = font data size. 11235 * freeData = should FontStash take ownership of the font data? 11236 * defAA = should FontStash use antialiased font rasterizer? 11237 * 11238 * Returns: 11239 * font id or [FONS_INVALID]. 11240 */ 11241 int addFontMem (const(char)[] name, ubyte* data, int dataSize, bool freeData, bool defAA=false) nothrow @trusted @nogc { 11242 if (data is null || dataSize < 16) return FONS_INVALID; 11243 FONSfontData* fdata = fons__createFontData(data, dataSize, freeData); 11244 fdata.incref(); 11245 auto res = addFontWithData(name, fdata, defAA); 11246 if (res == FONS_INVALID) { 11247 // we promised to not free data on error 11248 fdata.freeData = false; 11249 fdata.decref(); // this will free [fdata] 11250 } 11251 return res; 11252 } 11253 11254 /** Add fonts from another FontStash. 11255 * 11256 * This is more effective (and faster) than reloading fonts, because internally font data 11257 * is reference counted. 11258 */ 11259 void addFontsFrom (FONSContext source) nothrow @trusted @nogc { 11260 if (source is null) return; 11261 foreach (FONSfont* font; source.fonts[0..source.nfonts]) { 11262 if (font !is null) { 11263 auto newidx = addCookedFont(font); 11264 FONSfont* newfont = fonts[newidx]; 11265 assert(newfont !is null); 11266 assert(newfont.path is null); 11267 // copy path 11268 if (font.path !is null && font.path[0]) { 11269 import core.stdc.stdlib : malloc; 11270 import core.stdc.string : strcpy, strlen; 11271 newfont.path = cast(char*)malloc(strlen(font.path)+1); 11272 if (newfont.path is null) assert(0, "FONS: out of memory"); 11273 strcpy(newfont.path, font.path); 11274 } 11275 } 11276 } 11277 } 11278 11279 /// Returns logical font name corresponding to the given font id, or `null`. 11280 /// $(WARNING Copy returned name, as name buffer can be invalidated by next FontStash API call!) 11281 const(char)[] getNameById (int idx) const pure nothrow @trusted @nogc { 11282 if (idx < 0 || idx >= nfonts || fonts[idx] is null) return null; 11283 return fonts[idx].name[0..fonts[idx].namelen]; 11284 } 11285 11286 /// Returns font id corresponding to the given logical font name, or [FONS_INVALID]. 11287 int getFontByName (const(char)[] name) const pure nothrow @trusted @nogc { 11288 //{ import core.stdc.stdio; printf("fonsGetFontByName: [%.*s]\n", cast(uint)name.length, name.ptr); } 11289 // remove ":noaa" suffix 11290 if (name.length >= NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) { 11291 name = name[0..$-NoAlias.length]; 11292 } 11293 if (name.length == 0) return FONS_INVALID; 11294 return findNameInHash(name); 11295 } 11296 11297 /** Measures the specified text string. Parameter bounds should be a float[4], 11298 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax] 11299 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn). 11300 */ 11301 float getTextBounds(T) (float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc if (isAnyCharType!T) { 11302 FONSstate* state = getState; 11303 uint codepoint; 11304 uint utf8state = 0; 11305 FONSQuad q; 11306 FONSglyph* glyph = null; 11307 int prevGlyphIndex = -1; 11308 short isize = cast(short)(state.size*10.0f); 11309 short iblur = cast(short)state.blur; 11310 FONSfont* font; 11311 11312 if (state.font < 0 || state.font >= nfonts) return 0; 11313 font = fonts[state.font]; 11314 if (font is null || font.fdata is null) return 0; 11315 11316 float scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 11317 11318 // Align vertically. 11319 y += getVertAlign(font, state.talign, isize); 11320 11321 float minx = x, maxx = x; 11322 float miny = y, maxy = y; 11323 float startx = x; 11324 11325 foreach (T ch; str) { 11326 static if (T.sizeof == 1) { 11327 //if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue; 11328 mixin(DecUtfMixin!("utf8state", "codepoint", "(cast(ubyte)ch)")); 11329 if (utf8state) continue; 11330 } else { 11331 static if (T.sizeof == 4) { 11332 if (ch > dchar.max) ch = 0xFFFD; 11333 } 11334 codepoint = cast(uint)ch; 11335 } 11336 glyph = getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional); 11337 if (glyph !is null) { 11338 getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q); 11339 if (q.x0 < minx) minx = q.x0; 11340 if (q.x1 > maxx) maxx = q.x1; 11341 if (params.isZeroTopLeft) { 11342 if (q.y0 < miny) miny = q.y0; 11343 if (q.y1 > maxy) maxy = q.y1; 11344 } else { 11345 if (q.y1 < miny) miny = q.y1; 11346 if (q.y0 > maxy) maxy = q.y0; 11347 } 11348 prevGlyphIndex = glyph.index; 11349 } else { 11350 //{ import core.stdc.stdio; printf("NO GLYPH FOR 0x%04x\n", cast(uint)codepoint); } 11351 prevGlyphIndex = -1; 11352 } 11353 } 11354 11355 float advance = x-startx; 11356 //{ import core.stdc.stdio; printf("***: x=%g; startx=%g; advance=%g\n", cast(double)x, cast(double)startx, cast(double)advance); } 11357 11358 // Align horizontally 11359 if (state.talign.left) { 11360 // empty 11361 } else if (state.talign.right) { 11362 minx -= advance; 11363 maxx -= advance; 11364 } else if (state.talign.center) { 11365 minx -= advance*0.5f; 11366 maxx -= advance*0.5f; 11367 } 11368 11369 if (bounds.length) { 11370 if (bounds.length > 0) bounds.ptr[0] = minx; 11371 if (bounds.length > 1) bounds.ptr[1] = miny; 11372 if (bounds.length > 2) bounds.ptr[2] = maxx; 11373 if (bounds.length > 3) bounds.ptr[3] = maxy; 11374 } 11375 11376 return advance; 11377 } 11378 11379 /// Returns various font metrics. Any argument can be `null` if you aren't interested in its value. 11380 void getVertMetrics (float* ascender, float* descender, float* lineh) nothrow @trusted @nogc { 11381 FONSstate* state = getState; 11382 if (state.font < 0 || state.font >= nfonts) { 11383 if (ascender !is null) *ascender = 0; 11384 if (descender !is null) *descender = 0; 11385 if (lineh !is null) *lineh = 0; 11386 } else { 11387 FONSfont* font = fonts[state.font]; 11388 if (font is null || font.fdata is null) { 11389 if (ascender !is null) *ascender = 0; 11390 if (descender !is null) *descender = 0; 11391 if (lineh !is null) *lineh = 0; 11392 } else { 11393 short isize = cast(short)(state.size*10.0f); 11394 if (ascender !is null) *ascender = font.ascender*isize/10.0f; 11395 if (descender !is null) *descender = font.descender*isize/10.0f; 11396 if (lineh !is null) *lineh = font.lineh*isize/10.0f; 11397 } 11398 } 11399 } 11400 11401 /// Returns line bounds. Any argument can be `null` if you aren't interested in its value. 11402 void getLineBounds (float y, float* minyp, float* maxyp) nothrow @trusted @nogc { 11403 FONSfont* font; 11404 FONSstate* state = getState; 11405 short isize; 11406 11407 if (minyp !is null) *minyp = 0; 11408 if (maxyp !is null) *maxyp = 0; 11409 11410 if (state.font < 0 || state.font >= nfonts) return; 11411 font = fonts[state.font]; 11412 isize = cast(short)(state.size*10.0f); 11413 if (font is null || font.fdata is null) return; 11414 11415 y += getVertAlign(font, state.talign, isize); 11416 11417 if (params.isZeroTopLeft) { 11418 immutable float miny = y-font.ascender*cast(float)isize/10.0f; 11419 immutable float maxy = miny+font.lineh*isize/10.0f; 11420 if (minyp !is null) *minyp = miny; 11421 if (maxyp !is null) *maxyp = maxy; 11422 } else { 11423 immutable float maxy = y+font.descender*cast(float)isize/10.0f; 11424 immutable float miny = maxy-font.lineh*isize/10.0f; 11425 if (minyp !is null) *minyp = miny; 11426 if (maxyp !is null) *maxyp = maxy; 11427 } 11428 } 11429 11430 /// Returns font line height. 11431 float fontHeight () nothrow @trusted @nogc { 11432 float res = void; 11433 getVertMetrics(null, null, &res); 11434 return res; 11435 } 11436 11437 /// Returns font ascender (positive). 11438 float fontAscender () nothrow @trusted @nogc { 11439 float res = void; 11440 getVertMetrics(&res, null, null); 11441 return res; 11442 } 11443 11444 /// Returns font descender (negative). 11445 float fontDescender () nothrow @trusted @nogc { 11446 float res = void; 11447 getVertMetrics(null, &res, null); 11448 return res; 11449 } 11450 11451 //TODO: document this 11452 const(ubyte)* getTextureData (int* width, int* height) nothrow @trusted @nogc { 11453 if (width !is null) *width = params.width; 11454 if (height !is null) *height = params.height; 11455 return texData; 11456 } 11457 11458 //TODO: document this 11459 bool validateTexture (int* dirty) nothrow @trusted @nogc { 11460 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) { 11461 dirty[0] = dirtyRect.ptr[0]; 11462 dirty[1] = dirtyRect.ptr[1]; 11463 dirty[2] = dirtyRect.ptr[2]; 11464 dirty[3] = dirtyRect.ptr[3]; 11465 // reset dirty rect 11466 dirtyRect.ptr[0] = params.width; 11467 dirtyRect.ptr[1] = params.height; 11468 dirtyRect.ptr[2] = 0; 11469 dirtyRect.ptr[3] = 0; 11470 return true; 11471 } 11472 return false; 11473 } 11474 11475 //TODO: document this 11476 void errorCallback (void delegate (FONSError error, int val) nothrow @trusted @nogc callback) nothrow @trusted @nogc { 11477 handleError = callback; 11478 } 11479 11480 //TODO: document this 11481 void getAtlasSize (int* width, int* height) const pure nothrow @trusted @nogc { 11482 if (width !is null) *width = params.width; 11483 if (height !is null) *height = params.height; 11484 } 11485 11486 //TODO: document this 11487 bool expandAtlas (int width, int height) nothrow @trusted @nogc { 11488 import core.stdc.stdlib : free; 11489 import core.stdc.string : memcpy, memset; 11490 11491 int maxy = 0; 11492 ubyte* data = null; 11493 11494 width = nvg__max(width, params.width); 11495 height = nvg__max(height, params.height); 11496 11497 if (width == params.width && height == params.height) return true; 11498 11499 // Flush pending glyphs. 11500 flush(); 11501 11502 // Create new texture 11503 if (params.renderResize !is null) { 11504 if (params.renderResize(params.userPtr, width, height) == 0) return false; 11505 } 11506 // Copy old texture data over. 11507 data = cast(ubyte*)malloc(width*height); 11508 if (data is null) return 0; 11509 foreach (immutable int i; 0..params.height) { 11510 ubyte* dst = &data[i*width]; 11511 ubyte* src = &texData[i*params.width]; 11512 memcpy(dst, src, params.width); 11513 if (width > params.width) memset(dst+params.width, 0, width-params.width); 11514 } 11515 if (height > params.height) memset(&data[params.height*width], 0, (height-params.height)*width); 11516 11517 free(texData); 11518 texData = data; 11519 11520 // Increase atlas size 11521 atlas.expand(width, height); 11522 11523 // Add existing data as dirty. 11524 foreach (immutable int i; 0..atlas.nnodes) maxy = nvg__max(maxy, atlas.nodes[i].y); 11525 dirtyRect.ptr[0] = 0; 11526 dirtyRect.ptr[1] = 0; 11527 dirtyRect.ptr[2] = params.width; 11528 dirtyRect.ptr[3] = maxy; 11529 11530 params.width = width; 11531 params.height = height; 11532 itw = 1.0f/params.width; 11533 ith = 1.0f/params.height; 11534 11535 return true; 11536 } 11537 11538 //TODO: document this 11539 bool resetAtlas (int width, int height) nothrow @trusted @nogc { 11540 import core.stdc.stdlib : realloc; 11541 import core.stdc.string : memcpy, memset; 11542 11543 // flush pending glyphs 11544 flush(); 11545 11546 // create new texture 11547 if (params.renderResize !is null) { 11548 if (params.renderResize(params.userPtr, width, height) == 0) return false; 11549 } 11550 11551 // reset atlas 11552 atlas.reset(width, height); 11553 11554 // clear texture data 11555 texData = cast(ubyte*)realloc(texData, width*height); 11556 if (texData is null) assert(0, "FONS: out of memory"); 11557 memset(texData, 0, width*height); 11558 11559 // reset dirty rect 11560 dirtyRect.ptr[0] = width; 11561 dirtyRect.ptr[1] = height; 11562 dirtyRect.ptr[2] = 0; 11563 dirtyRect.ptr[3] = 0; 11564 11565 // Reset cached glyphs 11566 foreach (FONSfont* font; fonts[0..nfonts]) { 11567 if (font !is null) { 11568 font.nglyphs = 0; 11569 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; 11570 } 11571 } 11572 11573 params.width = width; 11574 params.height = height; 11575 itw = 1.0f/params.width; 11576 ith = 1.0f/params.height; 11577 11578 // Add white rect at 0, 0 for debug drawing. 11579 addWhiteRect(2, 2); 11580 11581 return true; 11582 } 11583 11584 //TODO: document this 11585 bool getPathBounds (dchar dch, float[] bounds) nothrow @trusted @nogc { 11586 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 11587 static if (is(typeof(&fons__nvg__bounds))) { 11588 FONSstate* state = getState; 11589 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; } 11590 FONSfont* font; 11591 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11592 if (g == 0) { bounds[] = 0; return false; } 11593 assert(font !is null); 11594 return fons__nvg__bounds(&font.font, g, bounds); 11595 } else { 11596 bounds[] = 0; 11597 return false; 11598 } 11599 } 11600 11601 //TODO: document this 11602 bool toPath() (NVGContext vg, dchar dch, float[] bounds=null) nothrow @trusted @nogc { 11603 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 11604 static if (is(typeof(&fons__nvg__toPath))) { 11605 if (vg is null) { bounds[] = 0; return false; } 11606 FONSstate* state = getState; 11607 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; } 11608 FONSfont* font; 11609 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11610 if (g == 0) { bounds[] = 0; return false; } 11611 assert(font !is null); 11612 return fons__nvg__toPath(vg, &font.font, g, bounds); 11613 } else { 11614 bounds[] = 0; 11615 return false; 11616 } 11617 } 11618 11619 //TODO: document this 11620 bool toOutline (dchar dch, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 11621 if (ol is null) return false; 11622 static if (is(typeof(&fons__nvg__toOutline))) { 11623 FONSstate* state = getState; 11624 if (state.font < 0 || state.font >= nfonts) return false; 11625 FONSfont* font; 11626 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11627 if (g == 0) return false; 11628 assert(font !is null); 11629 return fons__nvg__toOutline(&font.font, g, ol); 11630 } else { 11631 return false; 11632 } 11633 } 11634 11635 //TODO: document this 11636 FONSglyph* getGlyph (FONSfont* font, uint codepoint, short isize, short iblur, FONSBitmapFlag bitmapOption) nothrow @trusted @nogc { 11637 static uint fons__hashint() (uint a) pure nothrow @safe @nogc { 11638 pragma(inline, true); 11639 a += ~(a<<15); 11640 a ^= (a>>10); 11641 a += (a<<3); 11642 a ^= (a>>6); 11643 a += ~(a<<11); 11644 a ^= (a>>16); 11645 return a; 11646 } 11647 11648 // based on Exponential blur, Jani Huhtanen, 2006 11649 enum APREC = 16; 11650 enum ZPREC = 7; 11651 11652 static void fons__blurCols (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc { 11653 foreach (immutable int y; 0..h) { 11654 int z = 0; // force zero border 11655 foreach (int x; 1..w) { 11656 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC; 11657 dst[x] = cast(ubyte)(z>>ZPREC); 11658 } 11659 dst[w-1] = 0; // force zero border 11660 z = 0; 11661 for (int x = w-2; x >= 0; --x) { 11662 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC; 11663 dst[x] = cast(ubyte)(z>>ZPREC); 11664 } 11665 dst[0] = 0; // force zero border 11666 dst += dstStride; 11667 } 11668 } 11669 11670 static void fons__blurRows (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc { 11671 foreach (immutable int x; 0..w) { 11672 int z = 0; // force zero border 11673 for (int y = dstStride; y < h*dstStride; y += dstStride) { 11674 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC; 11675 dst[y] = cast(ubyte)(z>>ZPREC); 11676 } 11677 dst[(h-1)*dstStride] = 0; // force zero border 11678 z = 0; 11679 for (int y = (h-2)*dstStride; y >= 0; y -= dstStride) { 11680 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC; 11681 dst[y] = cast(ubyte)(z>>ZPREC); 11682 } 11683 dst[0] = 0; // force zero border 11684 ++dst; 11685 } 11686 } 11687 11688 static void fons__blur (ubyte* dst, int w, int h, int dstStride, int blur) nothrow @trusted @nogc { 11689 import std.math : expf = exp; 11690 if (blur < 1) return; 11691 // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity) 11692 immutable float sigma = cast(float)blur*0.57735f; // 1/sqrt(3) 11693 int alpha = cast(int)((1<<APREC)*(1.0f-expf(-2.3f/(sigma+1.0f)))); 11694 fons__blurRows(dst, w, h, dstStride, alpha); 11695 fons__blurCols(dst, w, h, dstStride, alpha); 11696 fons__blurRows(dst, w, h, dstStride, alpha); 11697 fons__blurCols(dst, w, h, dstStride, alpha); 11698 //fons__blurrows(dst, w, h, dstStride, alpha); 11699 //fons__blurcols(dst, w, h, dstStride, alpha); 11700 } 11701 11702 int advance, lsb, x0, y0, x1, y1, gx, gy; 11703 FONSglyph* glyph = null; 11704 float size = isize/10.0f; 11705 FONSfont* renderFont = font; 11706 11707 if (isize < 2) return null; 11708 if (iblur > 20) iblur = 20; 11709 int pad = iblur+2; 11710 11711 // Reset allocator. 11712 nscratch = 0; 11713 11714 // Find code point and size. 11715 uint h = fons__hashint(codepoint)&(FONS_HASH_LUT_SIZE-1); 11716 int i = font.lut.ptr[h]; 11717 while (i != -1) { 11718 //if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) return &font.glyphs[i]; 11719 if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) { 11720 glyph = &font.glyphs[i]; 11721 // Negative coordinate indicates there is no bitmap data created. 11722 if (bitmapOption == FONSBitmapFlag.Optional || (glyph.x0 >= 0 && glyph.y0 >= 0)) return glyph; 11723 // At this point, glyph exists but the bitmap data is not yet created. 11724 break; 11725 } 11726 i = font.glyphs[i].next; 11727 } 11728 11729 // Create a new glyph or rasterize bitmap data for a cached glyph. 11730 //scale = fons__tt_getPixelHeightScale(&font.font, size); 11731 int g = findGlyphForCP(font, cast(dchar)codepoint, &renderFont); 11732 // It is possible that we did not find a fallback glyph. 11733 // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph. 11734 11735 float scale = fons__tt_getPixelHeightScale(&renderFont.font, size); 11736 fons__tt_buildGlyphBitmap(&renderFont.font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1); 11737 int gw = x1-x0+pad*2; 11738 int gh = y1-y0+pad*2; 11739 11740 // Determines the spot to draw glyph in the atlas. 11741 if (bitmapOption == FONSBitmapFlag.Required) { 11742 // Find free spot for the rect in the atlas. 11743 bool added = atlas.addRect(gw, gh, &gx, &gy); 11744 if (!added && handleError !is null) { 11745 // Atlas is full, let the user to resize the atlas (or not), and try again. 11746 handleError(FONSError.AtlasFull, 0); 11747 added = atlas.addRect(gw, gh, &gx, &gy); 11748 } 11749 if (!added) return null; 11750 } else { 11751 // Negative coordinate indicates there is no bitmap data created. 11752 gx = -1; 11753 gy = -1; 11754 } 11755 11756 // Init glyph. 11757 if (glyph is null) { 11758 glyph = font.allocGlyph(); 11759 glyph.codepoint = codepoint; 11760 glyph.size = isize; 11761 glyph.blur = iblur; 11762 glyph.next = 0; 11763 11764 // Insert char to hash lookup. 11765 glyph.next = font.lut.ptr[h]; 11766 font.lut.ptr[h] = font.nglyphs-1; 11767 } 11768 glyph.index = g; 11769 glyph.x0 = cast(short)gx; 11770 glyph.y0 = cast(short)gy; 11771 glyph.x1 = cast(short)(glyph.x0+gw); 11772 glyph.y1 = cast(short)(glyph.y0+gh); 11773 glyph.xadv = cast(short)(scale*advance*10.0f); 11774 glyph.xoff = cast(short)(x0-pad); 11775 glyph.yoff = cast(short)(y0-pad); 11776 11777 if (bitmapOption == FONSBitmapFlag.Optional) return glyph; 11778 11779 // Rasterize 11780 ubyte* dst = &texData[(glyph.x0+pad)+(glyph.y0+pad)*params.width]; 11781 fons__tt_renderGlyphBitmap(&font.font, dst, gw-pad*2, gh-pad*2, params.width, scale, scale, g); 11782 11783 // Make sure there is one pixel empty border. 11784 dst = &texData[glyph.x0+glyph.y0*params.width]; 11785 foreach (immutable int y; 0..gh) { 11786 dst[y*params.width] = 0; 11787 dst[gw-1+y*params.width] = 0; 11788 } 11789 foreach (immutable int x; 0..gw) { 11790 dst[x] = 0; 11791 dst[x+(gh-1)*params.width] = 0; 11792 } 11793 11794 // Debug code to color the glyph background 11795 version(none) { 11796 foreach (immutable yy; 0..gh) { 11797 foreach (immutable xx; 0..gw) { 11798 int a = cast(int)dst[xx+yy*params.width]+42; 11799 if (a > 255) a = 255; 11800 dst[xx+yy*params.width] = cast(ubyte)a; 11801 } 11802 } 11803 } 11804 11805 // Blur 11806 if (iblur > 0) { 11807 nscratch = 0; 11808 ubyte* bdst = &texData[glyph.x0+glyph.y0*params.width]; 11809 fons__blur(bdst, gw, gh, params.width, iblur); 11810 } 11811 11812 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], glyph.x0); 11813 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], glyph.y0); 11814 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], glyph.x1); 11815 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], glyph.y1); 11816 11817 return glyph; 11818 } 11819 11820 //TODO: document this 11821 void getQuad (FONSfont* font, int prevGlyphIndex, FONSglyph* glyph, float size, float scale, float spacing, float* x, float* y, FONSQuad* q) nothrow @trusted @nogc { 11822 if (prevGlyphIndex >= 0) { 11823 immutable float adv = fons__tt_getGlyphKernAdvance(&font.font, size, prevGlyphIndex, glyph.index)/**scale*/; //k8: do we really need scale here? 11824 //if (adv != 0) { import core.stdc.stdio; printf("adv=%g (scale=%g; spacing=%g)\n", cast(double)adv, cast(double)scale, cast(double)spacing); } 11825 *x += cast(int)(adv+spacing /*+0.5f*/); //k8: for me, it looks better this way (with non-aa fonts) 11826 } 11827 11828 // Each glyph has 2px border to allow good interpolation, 11829 // one pixel to prevent leaking, and one to allow good interpolation for rendering. 11830 // Inset the texture region by one pixel for correct interpolation. 11831 immutable float xoff = cast(short)(glyph.xoff+1); 11832 immutable float yoff = cast(short)(glyph.yoff+1); 11833 immutable float x0 = cast(float)(glyph.x0+1); 11834 immutable float y0 = cast(float)(glyph.y0+1); 11835 immutable float x1 = cast(float)(glyph.x1-1); 11836 immutable float y1 = cast(float)(glyph.y1-1); 11837 11838 if (params.isZeroTopLeft) { 11839 immutable float rx = cast(float)cast(int)(*x+xoff); 11840 immutable float ry = cast(float)cast(int)(*y+yoff); 11841 11842 q.x0 = rx; 11843 q.y0 = ry; 11844 q.x1 = rx+x1-x0; 11845 q.y1 = ry+y1-y0; 11846 11847 q.s0 = x0*itw; 11848 q.t0 = y0*ith; 11849 q.s1 = x1*itw; 11850 q.t1 = y1*ith; 11851 } else { 11852 immutable float rx = cast(float)cast(int)(*x+xoff); 11853 immutable float ry = cast(float)cast(int)(*y-yoff); 11854 11855 q.x0 = rx; 11856 q.y0 = ry; 11857 q.x1 = rx+x1-x0; 11858 q.y1 = ry-y1+y0; 11859 11860 q.s0 = x0*itw; 11861 q.t0 = y0*ith; 11862 q.s1 = x1*itw; 11863 q.t1 = y1*ith; 11864 } 11865 11866 *x += cast(int)(glyph.xadv/10.0f+0.5f); 11867 } 11868 11869 void flush () nothrow @trusted @nogc { 11870 // flush texture 11871 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) { 11872 if (params.renderUpdate !is null) params.renderUpdate(params.userPtr, dirtyRect.ptr, texData); 11873 // reset dirty rect 11874 dirtyRect.ptr[0] = params.width; 11875 dirtyRect.ptr[1] = params.height; 11876 dirtyRect.ptr[2] = 0; 11877 dirtyRect.ptr[3] = 0; 11878 } 11879 } 11880 } 11881 11882 /// Free all resources used by the `stash`, and `stash` itself. 11883 /// Group: font_stash 11884 public void kill (ref FONSContext stash) nothrow @trusted @nogc { 11885 import core.stdc.stdlib : free; 11886 if (stash is null) return; 11887 stash.clear(); 11888 free(stash); 11889 stash = null; 11890 } 11891 11892 11893 // ////////////////////////////////////////////////////////////////////////// // 11894 void* fons__tmpalloc (usize size, void* up) nothrow @trusted @nogc { 11895 ubyte* ptr; 11896 FONSContext stash = cast(FONSContext)up; 11897 // 16-byte align the returned pointer 11898 size = (size+0xf)&~0xf; 11899 if (stash.nscratch+cast(int)size > FONS_SCRATCH_BUF_SIZE) { 11900 if (stash.handleError !is null) stash.handleError(FONSError.ScratchFull, stash.nscratch+cast(int)size); 11901 return null; 11902 } 11903 ptr = stash.scratch+stash.nscratch; 11904 stash.nscratch += cast(int)size; 11905 return ptr; 11906 } 11907 11908 void fons__tmpfree (void* ptr, void* up) nothrow @trusted @nogc { 11909 // empty 11910 } 11911 11912 11913 // ////////////////////////////////////////////////////////////////////////// // 11914 // Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de> 11915 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. 11916 11917 enum FONS_UTF8_ACCEPT = 0; 11918 enum FONS_UTF8_REJECT = 12; 11919 11920 static immutable ubyte[364] utf8d = [ 11921 // The first part of the table maps bytes to character classes that 11922 // to reduce the size of the transition table and create bitmasks. 11923 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, 11924 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, 11925 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, 11926 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, 11927 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, 11928 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, 11929 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, 11930 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, 11931 11932 // The second part is a transition table that maps a combination 11933 // of a state of the automaton and a character class to a state. 11934 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 11935 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, 11936 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 11937 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 11938 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 11939 ]; 11940 11941 private enum DecUtfMixin(string state, string codep, string byte_) = 11942 `{ 11943 uint type_ = utf8d.ptr[`~byte_~`]; 11944 `~codep~` = (`~state~` != FONS_UTF8_ACCEPT ? (`~byte_~`&0x3fu)|(`~codep~`<<6) : (0xff>>type_)&`~byte_~`); 11945 if ((`~state~` = utf8d.ptr[256+`~state~`+type_]) == FONS_UTF8_REJECT) { 11946 `~state~` = FONS_UTF8_ACCEPT; 11947 `~codep~` = 0xFFFD; 11948 } 11949 }`; 11950 11951 /* 11952 uint fons__decutf8 (uint* state, uint* codep, uint byte_) { 11953 pragma(inline, true); 11954 uint type = utf8d.ptr[byte_]; 11955 *codep = (*state != FONS_UTF8_ACCEPT ? (byte_&0x3fu)|(*codep<<6) : (0xff>>type)&byte_); 11956 *state = utf8d.ptr[256 + *state+type]; 11957 return *state; 11958 } 11959 */ 11960 11961 11962 // ////////////////////////////////////////////////////////////////////////// // 11963 /// This iterator can be used to do text measurement. 11964 /// $(WARNING Don't add new fonts to stash while you are iterating, or you WILL get segfault!) 11965 /// Group: font_stash 11966 public struct FONSTextBoundsIterator { 11967 private: 11968 FONSContext stash; 11969 FONSstate state; 11970 uint codepoint = 0xFFFD; 11971 uint utf8state = 0; 11972 int prevGlyphIndex = -1; 11973 short isize, iblur; 11974 float scale = 0; 11975 FONSfont* font; 11976 float startx = 0, x = 0, y = 0; 11977 float minx = 0, miny = 0, maxx = 0, maxy = 0; 11978 11979 private: 11980 void clear () nothrow @trusted @nogc { 11981 import core.stdc.string : memset; 11982 memset(&this, 0, this.sizeof); 11983 this.prevGlyphIndex = -1; 11984 this.codepoint = 0xFFFD; 11985 } 11986 11987 public: 11988 /// Initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator. 11989 this (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { reset(astash, ax, ay); } 11990 11991 /// (Re)initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator. 11992 void reset (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { 11993 clear(); 11994 11995 if (astash is null || astash.nstates == 0) return; 11996 11997 stash = astash; 11998 state = *stash.getState; 11999 12000 if (state.font < 0 || state.font >= stash.nfonts) { clear(); return; } 12001 font = stash.fonts[state.font]; 12002 if (font is null || font.fdata is null) { clear(); return; } 12003 12004 x = ax; 12005 y = ay; 12006 isize = cast(short)(state.size*10.0f); 12007 iblur = cast(short)state.blur; 12008 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 12009 12010 // align vertically 12011 y += astash.getVertAlign(font, state.talign, isize); 12012 12013 minx = maxx = x; 12014 miny = maxy = y; 12015 startx = x; 12016 } 12017 12018 /// Can this iterator be used? 12019 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null); } 12020 12021 /// Put some text into iterator, calculate new values. 12022 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { 12023 enum DoCodePointMixin = q{ 12024 glyph = stash.getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional); 12025 if (glyph !is null) { 12026 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q); 12027 if (q.x0 < minx) minx = q.x0; 12028 if (q.x1 > maxx) maxx = q.x1; 12029 if (stash.params.isZeroTopLeft) { 12030 if (q.y0 < miny) miny = q.y0; 12031 if (q.y1 > maxy) maxy = q.y1; 12032 } else { 12033 if (q.y1 < miny) miny = q.y1; 12034 if (q.y0 > maxy) maxy = q.y0; 12035 } 12036 prevGlyphIndex = glyph.index; 12037 } else { 12038 prevGlyphIndex = -1; 12039 } 12040 }; 12041 12042 if (stash is null || str.length == 0) return; // alas 12043 12044 FONSQuad q; 12045 FONSglyph* glyph; 12046 12047 static if (is(T == char)) { 12048 foreach (char ch; str) { 12049 mixin(DecUtfMixin!("utf8state", "codepoint", "cast(ubyte)ch")); 12050 if (utf8state) continue; // full char is not collected yet 12051 mixin(DoCodePointMixin); 12052 } 12053 } else { 12054 if (utf8state) { 12055 utf8state = 0; 12056 codepoint = 0xFFFD; 12057 mixin(DoCodePointMixin); 12058 } 12059 foreach (T dch; str) { 12060 static if (is(T == dchar)) { 12061 if (dch > dchar.max) dch = 0xFFFD; 12062 } 12063 codepoint = cast(uint)dch; 12064 mixin(DoCodePointMixin); 12065 } 12066 } 12067 } 12068 12069 /// Returns current advance. 12070 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null ? x-startx : 0); } 12071 12072 /// Returns current text bounds. 12073 void getBounds (ref float[4] bounds) const pure nothrow @safe @nogc { 12074 if (stash is null) { bounds[] = 0; return; } 12075 float lminx = minx, lmaxx = maxx; 12076 // align horizontally 12077 if (state.talign.left) { 12078 // empty 12079 } else if (state.talign.right) { 12080 float ca = advance; 12081 lminx -= ca; 12082 lmaxx -= ca; 12083 } else if (state.talign.center) { 12084 float ca = advance*0.5f; 12085 lminx -= ca; 12086 lmaxx -= ca; 12087 } 12088 bounds[0] = lminx; 12089 bounds[1] = miny; 12090 bounds[2] = lmaxx; 12091 bounds[3] = maxy; 12092 } 12093 12094 /// Returns current horizontal text bounds. 12095 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc { 12096 if (stash !is null) { 12097 float lminx = minx, lmaxx = maxx; 12098 // align horizontally 12099 if (state.talign.left) { 12100 // empty 12101 } else if (state.talign.right) { 12102 float ca = advance; 12103 lminx -= ca; 12104 lmaxx -= ca; 12105 } else if (state.talign.center) { 12106 float ca = advance*0.5f; 12107 lminx -= ca; 12108 lmaxx -= ca; 12109 } 12110 xmin = lminx; 12111 xmax = lmaxx; 12112 } else { 12113 xmin = xmax = 0; 12114 } 12115 } 12116 12117 /// Returns current vertical text bounds. 12118 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc { 12119 pragma(inline, true); 12120 if (stash !is null) { 12121 ymin = miny; 12122 ymax = maxy; 12123 } else { 12124 ymin = ymax = 0; 12125 } 12126 } 12127 12128 /// Returns font line height. 12129 float lineHeight () nothrow @trusted @nogc { 12130 pragma(inline, true); 12131 return (stash !is null ? stash.fonts[state.font].lineh*cast(short)(state.size*10.0f)/10.0f : 0); 12132 } 12133 12134 /// Returns font ascender (positive). 12135 float ascender () nothrow @trusted @nogc { 12136 pragma(inline, true); 12137 return (stash !is null ? stash.fonts[state.font].ascender*cast(short)(state.size*10.0f)/10.0f : 0); 12138 } 12139 12140 /// Returns font descender (negative). 12141 float descender () nothrow @trusted @nogc { 12142 pragma(inline, true); 12143 return (stash !is null ? stash.fonts[state.font].descender*cast(short)(state.size*10.0f)/10.0f : 0); 12144 } 12145 } 12146 12147 12148 // ////////////////////////////////////////////////////////////////////////// // 12149 // backgl 12150 // ////////////////////////////////////////////////////////////////////////// // 12151 import core.stdc.stdlib : malloc, realloc, free; 12152 import core.stdc.string : memcpy, memset; 12153 12154 static if (__VERSION__ < 2076) { 12155 private auto DGNoThrowNoGC(T) (scope T t) /*if (isFunctionPointer!T || isDelegate!T)*/ { 12156 import std.traits; 12157 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_; 12158 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 12159 } 12160 } 12161 12162 12163 //import arsd.simpledisplay; 12164 version(nanovg_bindbc_opengl_bindings) { 12165 import bindbc.opengl; 12166 } else version(nanovg_builtin_opengl_bindings) { 12167 import arsd.simpledisplay; 12168 } else { 12169 import iv.glbinds; 12170 } 12171 12172 private: 12173 // sdpy is missing that yet 12174 static if (!is(typeof(GL_STENCIL_BUFFER_BIT))) enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 12175 12176 12177 12178 version(bindbc){ 12179 private extern(System) nothrow @nogc: 12180 // this definition doesn't exist in regular OpenGL (?) 12181 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 12182 private void nanovgInitOpenGL () { 12183 // i'm not aware of calling the load multiple times having negative side effects, so i don't do an initialization check 12184 GLSupport support = loadOpenGL(); 12185 if (support == GLSupport.noLibrary) 12186 assert(0, "OpenGL initialization failed: shared library failed to load"); 12187 else if (support == GLSupport.badLibrary) 12188 assert(0, "OpenGL initialization failed: a context-independent symbol failed to load"); 12189 else if (support == GLSupport.noContext) 12190 assert(0, "OpenGL initialization failed: a context needs to be created prior to initialization"); 12191 } 12192 } else { // OpenGL API missing from simpledisplay 12193 private extern(System) nothrow @nogc { 12194 alias GLvoid = void; 12195 alias GLboolean = ubyte; 12196 alias GLuint = uint; 12197 alias GLenum = uint; 12198 alias GLchar = char; 12199 alias GLsizei = int; 12200 alias GLfloat = float; 12201 alias GLintptr = size_t; 12202 alias GLsizeiptr = ptrdiff_t; 12203 12204 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 12205 12206 enum uint GL_INVALID_ENUM = 0x0500; 12207 12208 enum uint GL_ZERO = 0; 12209 enum uint GL_ONE = 1; 12210 12211 enum uint GL_FLOAT = 0x1406; 12212 12213 enum uint GL_STREAM_DRAW = 0x88E0; 12214 12215 enum uint GL_CCW = 0x0901; 12216 12217 enum uint GL_STENCIL_TEST = 0x0B90; 12218 enum uint GL_SCISSOR_TEST = 0x0C11; 12219 12220 enum uint GL_EQUAL = 0x0202; 12221 enum uint GL_NOTEQUAL = 0x0205; 12222 12223 enum uint GL_ALWAYS = 0x0207; 12224 enum uint GL_KEEP = 0x1E00; 12225 12226 enum uint GL_INCR = 0x1E02; 12227 12228 enum uint GL_INCR_WRAP = 0x8507; 12229 enum uint GL_DECR_WRAP = 0x8508; 12230 12231 enum uint GL_CULL_FACE = 0x0B44; 12232 enum uint GL_BACK = 0x0405; 12233 12234 enum uint GL_FRAGMENT_SHADER = 0x8B30; 12235 enum uint GL_VERTEX_SHADER = 0x8B31; 12236 12237 enum uint GL_COMPILE_STATUS = 0x8B81; 12238 enum uint GL_LINK_STATUS = 0x8B82; 12239 12240 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 12241 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 12242 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 12243 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 12244 12245 enum uint GL_GENERATE_MIPMAP = 0x8191; 12246 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 12247 12248 enum uint GL_RED = 0x1903; 12249 12250 enum uint GL_TEXTURE0 = 0x84C0U; 12251 enum uint GL_TEXTURE1 = 0x84C1U; 12252 12253 enum uint GL_ARRAY_BUFFER = 0x8892; 12254 12255 enum uint GL_SRC_COLOR = 0x0300; 12256 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 12257 enum uint GL_SRC_ALPHA = 0x0302; 12258 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 12259 enum uint GL_DST_ALPHA = 0x0304; 12260 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 12261 enum uint GL_DST_COLOR = 0x0306; 12262 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 12263 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 12264 12265 enum uint GL_INVERT = 0x150AU; 12266 12267 enum uint GL_DEPTH_STENCIL = 0x84F9U; 12268 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 12269 12270 enum uint GL_FRAMEBUFFER = 0x8D40U; 12271 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 12272 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 12273 12274 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 12275 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 12276 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 12277 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 12278 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 12279 12280 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 12281 enum uint GL_CLEAR = 0x1500U; 12282 enum uint GL_COPY = 0x1503U; 12283 enum uint GL_XOR = 0x1506U; 12284 12285 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 12286 12287 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 12288 12289 /* 12290 version(Windows) { 12291 private void* kglLoad (const(char)* name) { 12292 void* res = glGetProcAddress(name); 12293 if (res is null) { 12294 import core.sys.windows.windef, core.sys.windows.winbase; 12295 static HINSTANCE dll = null; 12296 if (dll is null) { 12297 dll = LoadLibraryA("opengl32.dll"); 12298 if (dll is null) return null; // <32, but idc 12299 return GetProcAddress(dll, name); 12300 } 12301 } 12302 } 12303 } else { 12304 alias kglLoad = glGetProcAddress; 12305 } 12306 */ 12307 12308 alias glbfn_glGenVertexArrays = void function(GLsizei, GLuint*); 12309 __gshared glbfn_glGenVertexArrays glGenVertexArrays_NVGLZ; alias glGenVertexArrays = glGenVertexArrays_NVGLZ; 12310 alias glbfn_glBindVertexArray = void function(GLuint); 12311 __gshared glbfn_glBindVertexArray glBindVertexArray_NVGLZ; alias glBindVertexArray = glBindVertexArray_NVGLZ; 12312 alias glbfn_glDeleteVertexArrays = void function(GLsizei, const(GLuint)*); 12313 __gshared glbfn_glDeleteVertexArrays glDeleteVertexArrays_NVGLZ; alias glDeleteVertexArrays = glDeleteVertexArrays_NVGLZ; 12314 alias glbfn_glGenerateMipmap = void function(GLenum); 12315 __gshared glbfn_glGenerateMipmap glGenerateMipmap_NVGLZ; alias glGenerateMipmap = glGenerateMipmap_NVGLZ; 12316 alias glbfn_glBufferSubData = void function(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 12317 __gshared glbfn_glBufferSubData glBufferSubData_NVGLZ; alias glBufferSubData = glBufferSubData_NVGLZ; 12318 12319 alias glbfn_glStencilMask = void function(GLuint); 12320 __gshared glbfn_glStencilMask glStencilMask_NVGLZ; alias glStencilMask = glStencilMask_NVGLZ; 12321 alias glbfn_glStencilFunc = void function(GLenum, GLint, GLuint); 12322 __gshared glbfn_glStencilFunc glStencilFunc_NVGLZ; alias glStencilFunc = glStencilFunc_NVGLZ; 12323 alias glbfn_glGetShaderInfoLog = void function(GLuint, GLsizei, GLsizei*, GLchar*); 12324 __gshared glbfn_glGetShaderInfoLog glGetShaderInfoLog_NVGLZ; alias glGetShaderInfoLog = glGetShaderInfoLog_NVGLZ; 12325 alias glbfn_glGetProgramInfoLog = void function(GLuint, GLsizei, GLsizei*, GLchar*); 12326 __gshared glbfn_glGetProgramInfoLog glGetProgramInfoLog_NVGLZ; alias glGetProgramInfoLog = glGetProgramInfoLog_NVGLZ; 12327 alias glbfn_glCreateProgram = GLuint function(); 12328 __gshared glbfn_glCreateProgram glCreateProgram_NVGLZ; alias glCreateProgram = glCreateProgram_NVGLZ; 12329 alias glbfn_glCreateShader = GLuint function(GLenum); 12330 __gshared glbfn_glCreateShader glCreateShader_NVGLZ; alias glCreateShader = glCreateShader_NVGLZ; 12331 alias glbfn_glShaderSource = void function(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 12332 __gshared glbfn_glShaderSource glShaderSource_NVGLZ; alias glShaderSource = glShaderSource_NVGLZ; 12333 alias glbfn_glCompileShader = void function(GLuint); 12334 __gshared glbfn_glCompileShader glCompileShader_NVGLZ; alias glCompileShader = glCompileShader_NVGLZ; 12335 alias glbfn_glGetShaderiv = void function(GLuint, GLenum, GLint*); 12336 __gshared glbfn_glGetShaderiv glGetShaderiv_NVGLZ; alias glGetShaderiv = glGetShaderiv_NVGLZ; 12337 alias glbfn_glAttachShader = void function(GLuint, GLuint); 12338 __gshared glbfn_glAttachShader glAttachShader_NVGLZ; alias glAttachShader = glAttachShader_NVGLZ; 12339 alias glbfn_glBindAttribLocation = void function(GLuint, GLuint, const(GLchar)*); 12340 __gshared glbfn_glBindAttribLocation glBindAttribLocation_NVGLZ; alias glBindAttribLocation = glBindAttribLocation_NVGLZ; 12341 alias glbfn_glLinkProgram = void function(GLuint); 12342 __gshared glbfn_glLinkProgram glLinkProgram_NVGLZ; alias glLinkProgram = glLinkProgram_NVGLZ; 12343 alias glbfn_glGetProgramiv = void function(GLuint, GLenum, GLint*); 12344 __gshared glbfn_glGetProgramiv glGetProgramiv_NVGLZ; alias glGetProgramiv = glGetProgramiv_NVGLZ; 12345 alias glbfn_glDeleteProgram = void function(GLuint); 12346 __gshared glbfn_glDeleteProgram glDeleteProgram_NVGLZ; alias glDeleteProgram = glDeleteProgram_NVGLZ; 12347 alias glbfn_glDeleteShader = void function(GLuint); 12348 __gshared glbfn_glDeleteShader glDeleteShader_NVGLZ; alias glDeleteShader = glDeleteShader_NVGLZ; 12349 alias glbfn_glGetUniformLocation = GLint function(GLuint, const(GLchar)*); 12350 __gshared glbfn_glGetUniformLocation glGetUniformLocation_NVGLZ; alias glGetUniformLocation = glGetUniformLocation_NVGLZ; 12351 alias glbfn_glGenBuffers = void function(GLsizei, GLuint*); 12352 __gshared glbfn_glGenBuffers glGenBuffers_NVGLZ; alias glGenBuffers = glGenBuffers_NVGLZ; 12353 alias glbfn_glPixelStorei = void function(GLenum, GLint); 12354 __gshared glbfn_glPixelStorei glPixelStorei_NVGLZ; alias glPixelStorei = glPixelStorei_NVGLZ; 12355 alias glbfn_glUniform4fv = void function(GLint, GLsizei, const(GLfloat)*); 12356 __gshared glbfn_glUniform4fv glUniform4fv_NVGLZ; alias glUniform4fv = glUniform4fv_NVGLZ; 12357 alias glbfn_glColorMask = void function(GLboolean, GLboolean, GLboolean, GLboolean); 12358 __gshared glbfn_glColorMask glColorMask_NVGLZ; alias glColorMask = glColorMask_NVGLZ; 12359 alias glbfn_glStencilOpSeparate = void function(GLenum, GLenum, GLenum, GLenum); 12360 __gshared glbfn_glStencilOpSeparate glStencilOpSeparate_NVGLZ; alias glStencilOpSeparate = glStencilOpSeparate_NVGLZ; 12361 alias glbfn_glDrawArrays = void function(GLenum, GLint, GLsizei); 12362 __gshared glbfn_glDrawArrays glDrawArrays_NVGLZ; alias glDrawArrays = glDrawArrays_NVGLZ; 12363 alias glbfn_glStencilOp = void function(GLenum, GLenum, GLenum); 12364 __gshared glbfn_glStencilOp glStencilOp_NVGLZ; alias glStencilOp = glStencilOp_NVGLZ; 12365 alias glbfn_glUseProgram = void function(GLuint); 12366 __gshared glbfn_glUseProgram glUseProgram_NVGLZ; alias glUseProgram = glUseProgram_NVGLZ; 12367 alias glbfn_glCullFace = void function(GLenum); 12368 __gshared glbfn_glCullFace glCullFace_NVGLZ; alias glCullFace = glCullFace_NVGLZ; 12369 alias glbfn_glFrontFace = void function(GLenum); 12370 __gshared glbfn_glFrontFace glFrontFace_NVGLZ; alias glFrontFace = glFrontFace_NVGLZ; 12371 alias glbfn_glActiveTexture = void function(GLenum); 12372 __gshared glbfn_glActiveTexture glActiveTexture_NVGLZ; alias glActiveTexture = glActiveTexture_NVGLZ; 12373 alias glbfn_glBindBuffer = void function(GLenum, GLuint); 12374 __gshared glbfn_glBindBuffer glBindBuffer_NVGLZ; alias glBindBuffer = glBindBuffer_NVGLZ; 12375 alias glbfn_glBufferData = void function(GLenum, GLsizeiptr, const(void)*, GLenum); 12376 __gshared glbfn_glBufferData glBufferData_NVGLZ; alias glBufferData = glBufferData_NVGLZ; 12377 alias glbfn_glEnableVertexAttribArray = void function(GLuint); 12378 __gshared glbfn_glEnableVertexAttribArray glEnableVertexAttribArray_NVGLZ; alias glEnableVertexAttribArray = glEnableVertexAttribArray_NVGLZ; 12379 alias glbfn_glVertexAttribPointer = void function(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 12380 __gshared glbfn_glVertexAttribPointer glVertexAttribPointer_NVGLZ; alias glVertexAttribPointer = glVertexAttribPointer_NVGLZ; 12381 alias glbfn_glUniform1i = void function(GLint, GLint); 12382 __gshared glbfn_glUniform1i glUniform1i_NVGLZ; alias glUniform1i = glUniform1i_NVGLZ; 12383 alias glbfn_glUniform2fv = void function(GLint, GLsizei, const(GLfloat)*); 12384 __gshared glbfn_glUniform2fv glUniform2fv_NVGLZ; alias glUniform2fv = glUniform2fv_NVGLZ; 12385 alias glbfn_glDisableVertexAttribArray = void function(GLuint); 12386 __gshared glbfn_glDisableVertexAttribArray glDisableVertexAttribArray_NVGLZ; alias glDisableVertexAttribArray = glDisableVertexAttribArray_NVGLZ; 12387 alias glbfn_glDeleteBuffers = void function(GLsizei, const(GLuint)*); 12388 __gshared glbfn_glDeleteBuffers glDeleteBuffers_NVGLZ; alias glDeleteBuffers = glDeleteBuffers_NVGLZ; 12389 alias glbfn_glBlendFuncSeparate = void function(GLenum, GLenum, GLenum, GLenum); 12390 __gshared glbfn_glBlendFuncSeparate glBlendFuncSeparate_NVGLZ; alias glBlendFuncSeparate = glBlendFuncSeparate_NVGLZ; 12391 12392 alias glbfn_glLogicOp = void function (GLenum opcode); 12393 __gshared glbfn_glLogicOp glLogicOp_NVGLZ; alias glLogicOp = glLogicOp_NVGLZ; 12394 alias glbfn_glFramebufferTexture2D = void function (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 12395 __gshared glbfn_glFramebufferTexture2D glFramebufferTexture2D_NVGLZ; alias glFramebufferTexture2D = glFramebufferTexture2D_NVGLZ; 12396 alias glbfn_glDeleteFramebuffers = void function (GLsizei n, const(GLuint)* framebuffers); 12397 __gshared glbfn_glDeleteFramebuffers glDeleteFramebuffers_NVGLZ; alias glDeleteFramebuffers = glDeleteFramebuffers_NVGLZ; 12398 alias glbfn_glGenFramebuffers = void function (GLsizei n, GLuint* framebuffers); 12399 __gshared glbfn_glGenFramebuffers glGenFramebuffers_NVGLZ; alias glGenFramebuffers = glGenFramebuffers_NVGLZ; 12400 alias glbfn_glCheckFramebufferStatus = GLenum function (GLenum target); 12401 __gshared glbfn_glCheckFramebufferStatus glCheckFramebufferStatus_NVGLZ; alias glCheckFramebufferStatus = glCheckFramebufferStatus_NVGLZ; 12402 alias glbfn_glBindFramebuffer = void function (GLenum target, GLuint framebuffer); 12403 __gshared glbfn_glBindFramebuffer glBindFramebuffer_NVGLZ; alias glBindFramebuffer = glBindFramebuffer_NVGLZ; 12404 12405 alias glbfn_glGetIntegerv = void function (GLenum pname, GLint* data); 12406 __gshared glbfn_glGetIntegerv glGetIntegerv_NVGLZ; alias glGetIntegerv = glGetIntegerv_NVGLZ; 12407 12408 private void nanovgInitOpenGL () { 12409 __gshared bool initialized = false; 12410 if (initialized) return; 12411 glGenVertexArrays_NVGLZ = cast(glbfn_glGenVertexArrays)glbindGetProcAddress(`glGenVertexArrays`); 12412 if (glGenVertexArrays_NVGLZ is null) assert(0, `OpenGL function 'glGenVertexArrays' not found!`); 12413 glBindVertexArray_NVGLZ = cast(glbfn_glBindVertexArray)glbindGetProcAddress(`glBindVertexArray`); 12414 if (glBindVertexArray_NVGLZ is null) assert(0, `OpenGL function 'glBindVertexArray' not found!`); 12415 glDeleteVertexArrays_NVGLZ = cast(glbfn_glDeleteVertexArrays)glbindGetProcAddress(`glDeleteVertexArrays`); 12416 if (glDeleteVertexArrays_NVGLZ is null) assert(0, `OpenGL function 'glDeleteVertexArrays' not found!`); 12417 glGenerateMipmap_NVGLZ = cast(glbfn_glGenerateMipmap)glbindGetProcAddress(`glGenerateMipmap`); 12418 if (glGenerateMipmap_NVGLZ is null) assert(0, `OpenGL function 'glGenerateMipmap' not found!`); 12419 glBufferSubData_NVGLZ = cast(glbfn_glBufferSubData)glbindGetProcAddress(`glBufferSubData`); 12420 if (glBufferSubData_NVGLZ is null) assert(0, `OpenGL function 'glBufferSubData' not found!`); 12421 12422 glStencilMask_NVGLZ = cast(glbfn_glStencilMask)glbindGetProcAddress(`glStencilMask`); 12423 if (glStencilMask_NVGLZ is null) assert(0, `OpenGL function 'glStencilMask' not found!`); 12424 glStencilFunc_NVGLZ = cast(glbfn_glStencilFunc)glbindGetProcAddress(`glStencilFunc`); 12425 if (glStencilFunc_NVGLZ is null) assert(0, `OpenGL function 'glStencilFunc' not found!`); 12426 glGetShaderInfoLog_NVGLZ = cast(glbfn_glGetShaderInfoLog)glbindGetProcAddress(`glGetShaderInfoLog`); 12427 if (glGetShaderInfoLog_NVGLZ is null) assert(0, `OpenGL function 'glGetShaderInfoLog' not found!`); 12428 glGetProgramInfoLog_NVGLZ = cast(glbfn_glGetProgramInfoLog)glbindGetProcAddress(`glGetProgramInfoLog`); 12429 if (glGetProgramInfoLog_NVGLZ is null) assert(0, `OpenGL function 'glGetProgramInfoLog' not found!`); 12430 glCreateProgram_NVGLZ = cast(glbfn_glCreateProgram)glbindGetProcAddress(`glCreateProgram`); 12431 if (glCreateProgram_NVGLZ is null) assert(0, `OpenGL function 'glCreateProgram' not found!`); 12432 glCreateShader_NVGLZ = cast(glbfn_glCreateShader)glbindGetProcAddress(`glCreateShader`); 12433 if (glCreateShader_NVGLZ is null) assert(0, `OpenGL function 'glCreateShader' not found!`); 12434 glShaderSource_NVGLZ = cast(glbfn_glShaderSource)glbindGetProcAddress(`glShaderSource`); 12435 if (glShaderSource_NVGLZ is null) assert(0, `OpenGL function 'glShaderSource' not found!`); 12436 glCompileShader_NVGLZ = cast(glbfn_glCompileShader)glbindGetProcAddress(`glCompileShader`); 12437 if (glCompileShader_NVGLZ is null) assert(0, `OpenGL function 'glCompileShader' not found!`); 12438 glGetShaderiv_NVGLZ = cast(glbfn_glGetShaderiv)glbindGetProcAddress(`glGetShaderiv`); 12439 if (glGetShaderiv_NVGLZ is null) assert(0, `OpenGL function 'glGetShaderiv' not found!`); 12440 glAttachShader_NVGLZ = cast(glbfn_glAttachShader)glbindGetProcAddress(`glAttachShader`); 12441 if (glAttachShader_NVGLZ is null) assert(0, `OpenGL function 'glAttachShader' not found!`); 12442 glBindAttribLocation_NVGLZ = cast(glbfn_glBindAttribLocation)glbindGetProcAddress(`glBindAttribLocation`); 12443 if (glBindAttribLocation_NVGLZ is null) assert(0, `OpenGL function 'glBindAttribLocation' not found!`); 12444 glLinkProgram_NVGLZ = cast(glbfn_glLinkProgram)glbindGetProcAddress(`glLinkProgram`); 12445 if (glLinkProgram_NVGLZ is null) assert(0, `OpenGL function 'glLinkProgram' not found!`); 12446 glGetProgramiv_NVGLZ = cast(glbfn_glGetProgramiv)glbindGetProcAddress(`glGetProgramiv`); 12447 if (glGetProgramiv_NVGLZ is null) assert(0, `OpenGL function 'glGetProgramiv' not found!`); 12448 glDeleteProgram_NVGLZ = cast(glbfn_glDeleteProgram)glbindGetProcAddress(`glDeleteProgram`); 12449 if (glDeleteProgram_NVGLZ is null) assert(0, `OpenGL function 'glDeleteProgram' not found!`); 12450 glDeleteShader_NVGLZ = cast(glbfn_glDeleteShader)glbindGetProcAddress(`glDeleteShader`); 12451 if (glDeleteShader_NVGLZ is null) assert(0, `OpenGL function 'glDeleteShader' not found!`); 12452 glGetUniformLocation_NVGLZ = cast(glbfn_glGetUniformLocation)glbindGetProcAddress(`glGetUniformLocation`); 12453 if (glGetUniformLocation_NVGLZ is null) assert(0, `OpenGL function 'glGetUniformLocation' not found!`); 12454 glGenBuffers_NVGLZ = cast(glbfn_glGenBuffers)glbindGetProcAddress(`glGenBuffers`); 12455 if (glGenBuffers_NVGLZ is null) assert(0, `OpenGL function 'glGenBuffers' not found!`); 12456 glPixelStorei_NVGLZ = cast(glbfn_glPixelStorei)glbindGetProcAddress(`glPixelStorei`); 12457 if (glPixelStorei_NVGLZ is null) assert(0, `OpenGL function 'glPixelStorei' not found!`); 12458 glUniform4fv_NVGLZ = cast(glbfn_glUniform4fv)glbindGetProcAddress(`glUniform4fv`); 12459 if (glUniform4fv_NVGLZ is null) assert(0, `OpenGL function 'glUniform4fv' not found!`); 12460 glColorMask_NVGLZ = cast(glbfn_glColorMask)glbindGetProcAddress(`glColorMask`); 12461 if (glColorMask_NVGLZ is null) assert(0, `OpenGL function 'glColorMask' not found!`); 12462 glStencilOpSeparate_NVGLZ = cast(glbfn_glStencilOpSeparate)glbindGetProcAddress(`glStencilOpSeparate`); 12463 if (glStencilOpSeparate_NVGLZ is null) assert(0, `OpenGL function 'glStencilOpSeparate' not found!`); 12464 glDrawArrays_NVGLZ = cast(glbfn_glDrawArrays)glbindGetProcAddress(`glDrawArrays`); 12465 if (glDrawArrays_NVGLZ is null) assert(0, `OpenGL function 'glDrawArrays' not found!`); 12466 glStencilOp_NVGLZ = cast(glbfn_glStencilOp)glbindGetProcAddress(`glStencilOp`); 12467 if (glStencilOp_NVGLZ is null) assert(0, `OpenGL function 'glStencilOp' not found!`); 12468 glUseProgram_NVGLZ = cast(glbfn_glUseProgram)glbindGetProcAddress(`glUseProgram`); 12469 if (glUseProgram_NVGLZ is null) assert(0, `OpenGL function 'glUseProgram' not found!`); 12470 glCullFace_NVGLZ = cast(glbfn_glCullFace)glbindGetProcAddress(`glCullFace`); 12471 if (glCullFace_NVGLZ is null) assert(0, `OpenGL function 'glCullFace' not found!`); 12472 glFrontFace_NVGLZ = cast(glbfn_glFrontFace)glbindGetProcAddress(`glFrontFace`); 12473 if (glFrontFace_NVGLZ is null) assert(0, `OpenGL function 'glFrontFace' not found!`); 12474 glActiveTexture_NVGLZ = cast(glbfn_glActiveTexture)glbindGetProcAddress(`glActiveTexture`); 12475 if (glActiveTexture_NVGLZ is null) assert(0, `OpenGL function 'glActiveTexture' not found!`); 12476 glBindBuffer_NVGLZ = cast(glbfn_glBindBuffer)glbindGetProcAddress(`glBindBuffer`); 12477 if (glBindBuffer_NVGLZ is null) assert(0, `OpenGL function 'glBindBuffer' not found!`); 12478 glBufferData_NVGLZ = cast(glbfn_glBufferData)glbindGetProcAddress(`glBufferData`); 12479 if (glBufferData_NVGLZ is null) assert(0, `OpenGL function 'glBufferData' not found!`); 12480 glEnableVertexAttribArray_NVGLZ = cast(glbfn_glEnableVertexAttribArray)glbindGetProcAddress(`glEnableVertexAttribArray`); 12481 if (glEnableVertexAttribArray_NVGLZ is null) assert(0, `OpenGL function 'glEnableVertexAttribArray' not found!`); 12482 glVertexAttribPointer_NVGLZ = cast(glbfn_glVertexAttribPointer)glbindGetProcAddress(`glVertexAttribPointer`); 12483 if (glVertexAttribPointer_NVGLZ is null) assert(0, `OpenGL function 'glVertexAttribPointer' not found!`); 12484 glUniform1i_NVGLZ = cast(glbfn_glUniform1i)glbindGetProcAddress(`glUniform1i`); 12485 if (glUniform1i_NVGLZ is null) assert(0, `OpenGL function 'glUniform1i' not found!`); 12486 glUniform2fv_NVGLZ = cast(glbfn_glUniform2fv)glbindGetProcAddress(`glUniform2fv`); 12487 if (glUniform2fv_NVGLZ is null) assert(0, `OpenGL function 'glUniform2fv' not found!`); 12488 glDisableVertexAttribArray_NVGLZ = cast(glbfn_glDisableVertexAttribArray)glbindGetProcAddress(`glDisableVertexAttribArray`); 12489 if (glDisableVertexAttribArray_NVGLZ is null) assert(0, `OpenGL function 'glDisableVertexAttribArray' not found!`); 12490 glDeleteBuffers_NVGLZ = cast(glbfn_glDeleteBuffers)glbindGetProcAddress(`glDeleteBuffers`); 12491 if (glDeleteBuffers_NVGLZ is null) assert(0, `OpenGL function 'glDeleteBuffers' not found!`); 12492 glBlendFuncSeparate_NVGLZ = cast(glbfn_glBlendFuncSeparate)glbindGetProcAddress(`glBlendFuncSeparate`); 12493 if (glBlendFuncSeparate_NVGLZ is null) assert(0, `OpenGL function 'glBlendFuncSeparate' not found!`); 12494 12495 glLogicOp_NVGLZ = cast(glbfn_glLogicOp)glbindGetProcAddress(`glLogicOp`); 12496 if (glLogicOp_NVGLZ is null) assert(0, `OpenGL function 'glLogicOp' not found!`); 12497 glFramebufferTexture2D_NVGLZ = cast(glbfn_glFramebufferTexture2D)glbindGetProcAddress(`glFramebufferTexture2D`); 12498 if (glFramebufferTexture2D_NVGLZ is null) assert(0, `OpenGL function 'glFramebufferTexture2D' not found!`); 12499 glDeleteFramebuffers_NVGLZ = cast(glbfn_glDeleteFramebuffers)glbindGetProcAddress(`glDeleteFramebuffers`); 12500 if (glDeleteFramebuffers_NVGLZ is null) assert(0, `OpenGL function 'glDeleteFramebuffers' not found!`); 12501 glGenFramebuffers_NVGLZ = cast(glbfn_glGenFramebuffers)glbindGetProcAddress(`glGenFramebuffers`); 12502 if (glGenFramebuffers_NVGLZ is null) assert(0, `OpenGL function 'glGenFramebuffers' not found!`); 12503 glCheckFramebufferStatus_NVGLZ = cast(glbfn_glCheckFramebufferStatus)glbindGetProcAddress(`glCheckFramebufferStatus`); 12504 if (glCheckFramebufferStatus_NVGLZ is null) assert(0, `OpenGL function 'glCheckFramebufferStatus' not found!`); 12505 glBindFramebuffer_NVGLZ = cast(glbfn_glBindFramebuffer)glbindGetProcAddress(`glBindFramebuffer`); 12506 if (glBindFramebuffer_NVGLZ is null) assert(0, `OpenGL function 'glBindFramebuffer' not found!`); 12507 12508 glGetIntegerv_NVGLZ = cast(glbfn_glGetIntegerv)glbindGetProcAddress(`glGetIntegerv`); 12509 if (glGetIntegerv_NVGLZ is null) assert(0, `OpenGL function 'glGetIntegerv' not found!`); 12510 initialized = true; 12511 } 12512 } 12513 } 12514 12515 12516 12517 /// Context creation flags. 12518 /// Group: context_management 12519 public enum NVGContextFlag : int { 12520 /// Nothing special, i.e. empty flag. 12521 None = 0, 12522 /// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA). 12523 Antialias = 1U<<0, 12524 /** Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little 12525 * slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. */ 12526 StencilStrokes = 1U<<1, 12527 /// Flag indicating that additional debug checks are done. 12528 Debug = 1U<<2, 12529 /// Filter (antialias) fonts 12530 FontAA = 1U<<7, 12531 /// Don't filter (antialias) fonts 12532 FontNoAA = 1U<<8, 12533 /// You can use this as a substitute for default flags, for cases like this: `nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);`. 12534 Default = 1U<<31, 12535 } 12536 12537 public enum NANOVG_GL_USE_STATE_FILTER = true; 12538 12539 /// Returns flags for glClear(). 12540 /// Group: context_management 12541 public uint glNVGClearFlags () pure nothrow @safe @nogc { 12542 pragma(inline, true); 12543 return (GL_COLOR_BUFFER_BIT|/*GL_DEPTH_BUFFER_BIT|*/GL_STENCIL_BUFFER_BIT); 12544 } 12545 12546 12547 // ////////////////////////////////////////////////////////////////////////// // 12548 private: 12549 12550 version = nanovega_shared_stencil; 12551 //version = nanovega_debug_clipping; 12552 12553 enum GLNVGuniformLoc { 12554 ViewSize, 12555 Tex, 12556 Frag, 12557 TMat, 12558 TTr, 12559 ClipTex, 12560 } 12561 12562 alias GLNVGshaderType = int; 12563 enum /*GLNVGshaderType*/ { 12564 NSVG_SHADER_FILLCOLOR, 12565 NSVG_SHADER_FILLGRAD, 12566 NSVG_SHADER_FILLIMG, 12567 NSVG_SHADER_SIMPLE, // also used for clipfill 12568 NSVG_SHADER_IMG, 12569 } 12570 12571 struct GLNVGshader { 12572 GLuint prog; 12573 GLuint frag; 12574 GLuint vert; 12575 GLint[GLNVGuniformLoc.max+1] loc; 12576 } 12577 12578 struct GLNVGtexture { 12579 int id; 12580 GLuint tex; 12581 int width, height; 12582 NVGtexture type; 12583 int flags; 12584 shared int rc; // this can be 0 with tex != 0 -- postponed deletion 12585 int nextfree; 12586 } 12587 12588 struct GLNVGblend { 12589 bool simple; 12590 GLenum srcRGB; 12591 GLenum dstRGB; 12592 GLenum srcAlpha; 12593 GLenum dstAlpha; 12594 } 12595 12596 alias GLNVGcallType = int; 12597 enum /*GLNVGcallType*/ { 12598 GLNVG_NONE = 0, 12599 GLNVG_FILL, 12600 GLNVG_CONVEXFILL, 12601 GLNVG_STROKE, 12602 GLNVG_TRIANGLES, 12603 GLNVG_AFFINE, // change affine transformation matrix 12604 GLNVG_PUSHCLIP, 12605 GLNVG_POPCLIP, 12606 GLNVG_RESETCLIP, 12607 GLNVG_CLIP_DDUMP_ON, 12608 GLNVG_CLIP_DDUMP_OFF, 12609 } 12610 12611 struct GLNVGcall { 12612 int type; 12613 int evenOdd; // for fill 12614 int image; 12615 int pathOffset; 12616 int pathCount; 12617 int triangleOffset; 12618 int triangleCount; 12619 int uniformOffset; 12620 NVGMatrix affine; 12621 GLNVGblend blendFunc; 12622 NVGClipMode clipmode; 12623 } 12624 12625 struct GLNVGpath { 12626 int fillOffset; 12627 int fillCount; 12628 int strokeOffset; 12629 int strokeCount; 12630 } 12631 12632 align(1) struct GLNVGfragUniforms { 12633 align(1): 12634 enum UNIFORM_ARRAY_SIZE = 13; 12635 // note: after modifying layout or size of uniform array, 12636 // don't forget to also update the fragment shader source! 12637 align(1) union { 12638 align(1): 12639 align(1) struct { 12640 align(1): 12641 float[12] scissorMat; // matrices are actually 3 vec4s 12642 float[12] paintMat; 12643 NVGColor innerCol; 12644 NVGColor middleCol; 12645 NVGColor outerCol; 12646 float[2] scissorExt; 12647 float[2] scissorScale; 12648 float[2] extent; 12649 float radius; 12650 float feather; 12651 float strokeMult; 12652 float strokeThr; 12653 float texType; 12654 float type; 12655 float doclip; 12656 float midp; // for gradients 12657 float unused2, unused3; 12658 } 12659 float[4][UNIFORM_ARRAY_SIZE] uniformArray; 12660 } 12661 } 12662 12663 enum GLMaskState { 12664 DontMask = -1, 12665 Uninitialized = 0, 12666 Initialized = 1, 12667 JustCleared = 2, 12668 } 12669 12670 final class GLNVGTextureLocker {} 12671 12672 struct GLNVGcontext { 12673 private import core.thread : ThreadID; 12674 12675 GLNVGshader shader; 12676 GLNVGtexture* textures; 12677 float[2] view; 12678 int freetexid; // -1: none 12679 int ntextures; 12680 int ctextures; 12681 GLuint vertBuf; 12682 int fragSize; 12683 int flags; 12684 // FBOs for masks 12685 GLuint[NVG_MAX_STATES] fbo; 12686 GLuint[2][NVG_MAX_STATES] fboTex; // FBO textures: [0] is color, [1] is stencil 12687 int fboWidth, fboHeight; 12688 GLMaskState[NVG_MAX_STATES] maskStack; 12689 int msp; // mask stack pointer; starts from `0`; points to next free item; see below for logic description 12690 int lastClipFBO; // -666: cache invalidated; -1: don't mask 12691 int lastClipUniOfs; 12692 bool doClipUnion; // specal mode 12693 GLNVGshader shaderFillFBO; 12694 GLNVGshader shaderCopyFBO; 12695 12696 bool inFrame; // will be `true` if we can perform OpenGL operations (used in texture deletion) 12697 shared bool mustCleanTextures; // will be `true` if we should delete some textures 12698 ThreadID mainTID; 12699 uint mainFBO; 12700 12701 // Per frame buffers 12702 GLNVGcall* calls; 12703 int ccalls; 12704 int ncalls; 12705 GLNVGpath* paths; 12706 int cpaths; 12707 int npaths; 12708 NVGVertex* verts; 12709 int cverts; 12710 int nverts; 12711 ubyte* uniforms; 12712 int cuniforms; 12713 int nuniforms; 12714 NVGMatrix lastAffine; 12715 12716 // cached state 12717 static if (NANOVG_GL_USE_STATE_FILTER) { 12718 GLuint boundTexture; 12719 GLuint stencilMask; 12720 GLenum stencilFunc; 12721 GLint stencilFuncRef; 12722 GLuint stencilFuncMask; 12723 GLNVGblend blendFunc; 12724 } 12725 } 12726 12727 int glnvg__maxi() (int a, int b) { pragma(inline, true); return (a > b ? a : b); } 12728 12729 void glnvg__bindTexture (GLNVGcontext* gl, GLuint tex) nothrow @trusted @nogc { 12730 static if (NANOVG_GL_USE_STATE_FILTER) { 12731 if (gl.boundTexture != tex) { 12732 gl.boundTexture = tex; 12733 glBindTexture(GL_TEXTURE_2D, tex); 12734 } 12735 } else { 12736 glBindTexture(GL_TEXTURE_2D, tex); 12737 } 12738 } 12739 12740 void glnvg__stencilMask (GLNVGcontext* gl, GLuint mask) nothrow @trusted @nogc { 12741 static if (NANOVG_GL_USE_STATE_FILTER) { 12742 if (gl.stencilMask != mask) { 12743 gl.stencilMask = mask; 12744 glStencilMask(mask); 12745 } 12746 } else { 12747 glStencilMask(mask); 12748 } 12749 } 12750 12751 void glnvg__stencilFunc (GLNVGcontext* gl, GLenum func, GLint ref_, GLuint mask) nothrow @trusted @nogc { 12752 static if (NANOVG_GL_USE_STATE_FILTER) { 12753 if (gl.stencilFunc != func || gl.stencilFuncRef != ref_ || gl.stencilFuncMask != mask) { 12754 gl.stencilFunc = func; 12755 gl.stencilFuncRef = ref_; 12756 gl.stencilFuncMask = mask; 12757 glStencilFunc(func, ref_, mask); 12758 } 12759 } else { 12760 glStencilFunc(func, ref_, mask); 12761 } 12762 } 12763 12764 // texture id is never zero 12765 // sets refcount to one 12766 GLNVGtexture* glnvg__allocTexture (GLNVGcontext* gl) nothrow @trusted @nogc { 12767 GLNVGtexture* tex = null; 12768 12769 int tid = gl.freetexid; 12770 if (tid == -1) { 12771 if (gl.ntextures >= gl.ctextures) { 12772 assert(gl.ntextures == gl.ctextures); 12773 //pragma(msg, GLNVGtexture.sizeof*32); 12774 int ctextures = (gl.ctextures == 0 ? 32 : gl.ctextures+gl.ctextures/2); // 1.5x overallocate 12775 GLNVGtexture* textures = cast(GLNVGtexture*)realloc(gl.textures, GLNVGtexture.sizeof*ctextures); 12776 if (textures is null) assert(0, "NanoVega: out of memory for textures"); 12777 memset(&textures[gl.ctextures], 0, (ctextures-gl.ctextures)*GLNVGtexture.sizeof); 12778 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated more textures (n=%d; c=%d; nc=%d)\n", gl.ntextures, gl.ctextures, ctextures); }} 12779 gl.textures = textures; 12780 gl.ctextures = ctextures; 12781 } 12782 assert(gl.ntextures+1 <= gl.ctextures); 12783 tid = gl.ntextures++; 12784 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf(" got next free texture id %d, ntextures=%d\n", tid+1, gl.ntextures); }} 12785 } else { 12786 gl.freetexid = gl.textures[tid].nextfree; 12787 } 12788 assert(tid <= gl.ntextures); 12789 12790 assert(gl.textures[tid].id == 0); 12791 tex = &gl.textures[tid]; 12792 memset(tex, 0, (*tex).sizeof); 12793 tex.id = tid+1; 12794 tex.rc = 1; 12795 tex.nextfree = -1; 12796 12797 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated texture with id %d (%d)\n", tex.id, tid+1); }} 12798 12799 return tex; 12800 } 12801 12802 GLNVGtexture* glnvg__findTexture (GLNVGcontext* gl, int id) nothrow @trusted @nogc { 12803 if (id <= 0 || id > gl.ntextures) return null; 12804 if (gl.textures[id-1].id == 0) return null; // free one 12805 assert(gl.textures[id-1].id == id); 12806 return &gl.textures[id-1]; 12807 } 12808 12809 bool glnvg__deleteTexture (GLNVGcontext* gl, ref int id) nothrow @trusted @nogc { 12810 if (id <= 0 || id > gl.ntextures) return false; 12811 auto tx = &gl.textures[id-1]; 12812 if (tx.id == 0) { id = 0; return false; } // free one 12813 assert(tx.id == id); 12814 assert(tx.tex != 0); 12815 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("decrefing texture with id %d (%d)\n", tx.id, id); }} 12816 import core.atomic : atomicOp; 12817 if (atomicOp!"-="(tx.rc, 1) == 0) { 12818 import core.thread : ThreadID; 12819 ThreadID mytid; 12820 static if (__VERSION__ < 2076) { 12821 DGNoThrowNoGC(() { 12822 import core.thread; mytid = Thread.getThis.id; 12823 })(); 12824 } else { 12825 try { import core.thread; mytid = Thread.getThis.id; } catch (Exception e) {} 12826 } 12827 if (gl.mainTID == mytid && gl.inFrame) { 12828 // can delete it right now 12829 if ((tx.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tx.tex); 12830 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** deleted texture with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }} 12831 memset(tx, 0, (*tx).sizeof); 12832 //{ import core.stdc.stdio; printf("deleting texture with id %d\n", id); } 12833 tx.nextfree = gl.freetexid; 12834 gl.freetexid = id-1; 12835 } else { 12836 // alas, we aren't doing frame business, so we should postpone deletion 12837 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** POSTPONED texture deletion with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }} 12838 version(aliced) { 12839 synchronized(GLNVGTextureLocker.classinfo) { 12840 tx.id = 0; // mark it as dead 12841 gl.mustCleanTextures = true; // set "need cleanup" flag 12842 } 12843 } else { 12844 try { 12845 synchronized(GLNVGTextureLocker.classinfo) { 12846 tx.id = 0; // mark it as dead 12847 gl.mustCleanTextures = true; // set "need cleanup" flag 12848 } 12849 } catch (Exception e) {} 12850 } 12851 } 12852 } 12853 id = 0; 12854 return true; 12855 } 12856 12857 void glnvg__dumpShaderError (GLuint shader, const(char)* name, const(char)* type) nothrow @trusted @nogc { 12858 import core.stdc.stdio : fprintf, stderr; 12859 GLchar[512+1] str = 0; 12860 GLsizei len = 0; 12861 glGetShaderInfoLog(shader, 512, &len, str.ptr); 12862 if (len > 512) len = 512; 12863 str[len] = '\0'; 12864 fprintf(stderr, "Shader %s/%s error:\n%s\n", name, type, str.ptr); 12865 } 12866 12867 void glnvg__dumpProgramError (GLuint prog, const(char)* name) nothrow @trusted @nogc { 12868 import core.stdc.stdio : fprintf, stderr; 12869 GLchar[512+1] str = 0; 12870 GLsizei len = 0; 12871 glGetProgramInfoLog(prog, 512, &len, str.ptr); 12872 if (len > 512) len = 512; 12873 str[len] = '\0'; 12874 fprintf(stderr, "Program %s error:\n%s\n", name, str.ptr); 12875 } 12876 12877 void glnvg__resetError(bool force=false) (GLNVGcontext* gl) nothrow @trusted @nogc { 12878 static if (!force) { 12879 if ((gl.flags&NVGContextFlag.Debug) == 0) return; 12880 } 12881 glGetError(); 12882 } 12883 12884 void glnvg__checkError(bool force=false) (GLNVGcontext* gl, const(char)* str) nothrow @trusted @nogc { 12885 GLenum err; 12886 static if (!force) { 12887 if ((gl.flags&NVGContextFlag.Debug) == 0) return; 12888 } 12889 err = glGetError(); 12890 if (err != GL_NO_ERROR) { 12891 import core.stdc.stdio : fprintf, stderr; 12892 fprintf(stderr, "Error %08x after %s\n", err, str); 12893 return; 12894 } 12895 } 12896 12897 bool glnvg__createShader (GLNVGshader* shader, const(char)* name, const(char)* header, const(char)* opts, const(char)* vshader, const(char)* fshader) nothrow @trusted @nogc { 12898 GLint status; 12899 GLuint prog, vert, frag; 12900 const(char)*[3] str; 12901 12902 memset(shader, 0, (*shader).sizeof); 12903 12904 prog = glCreateProgram(); 12905 vert = glCreateShader(GL_VERTEX_SHADER); 12906 frag = glCreateShader(GL_FRAGMENT_SHADER); 12907 str[0] = header; 12908 str[1] = (opts !is null ? opts : ""); 12909 str[2] = vshader; 12910 glShaderSource(vert, 3, cast(const(char)**)str.ptr, null); 12911 12912 glCompileShader(vert); 12913 glGetShaderiv(vert, GL_COMPILE_STATUS, &status); 12914 if (status != GL_TRUE) { 12915 glnvg__dumpShaderError(vert, name, "vert"); 12916 return false; 12917 } 12918 12919 str[0] = header; 12920 str[1] = (opts !is null ? opts : ""); 12921 str[2] = fshader; 12922 glShaderSource(frag, 3, cast(const(char)**)str.ptr, null); 12923 12924 glCompileShader(frag); 12925 glGetShaderiv(frag, GL_COMPILE_STATUS, &status); 12926 if (status != GL_TRUE) { 12927 glnvg__dumpShaderError(frag, name, "frag"); 12928 return false; 12929 } 12930 12931 glAttachShader(prog, vert); 12932 glAttachShader(prog, frag); 12933 12934 glBindAttribLocation(prog, 0, "vertex"); 12935 glBindAttribLocation(prog, 1, "tcoord"); 12936 12937 glLinkProgram(prog); 12938 glGetProgramiv(prog, GL_LINK_STATUS, &status); 12939 if (status != GL_TRUE) { 12940 glnvg__dumpProgramError(prog, name); 12941 return false; 12942 } 12943 12944 shader.prog = prog; 12945 shader.vert = vert; 12946 shader.frag = frag; 12947 12948 return true; 12949 } 12950 12951 void glnvg__deleteShader (GLNVGshader* shader) nothrow @trusted @nogc { 12952 if (shader.prog != 0) glDeleteProgram(shader.prog); 12953 if (shader.vert != 0) glDeleteShader(shader.vert); 12954 if (shader.frag != 0) glDeleteShader(shader.frag); 12955 } 12956 12957 void glnvg__getUniforms (GLNVGshader* shader) nothrow @trusted @nogc { 12958 shader.loc[GLNVGuniformLoc.ViewSize] = glGetUniformLocation(shader.prog, "viewSize"); 12959 shader.loc[GLNVGuniformLoc.Tex] = glGetUniformLocation(shader.prog, "tex"); 12960 shader.loc[GLNVGuniformLoc.Frag] = glGetUniformLocation(shader.prog, "frag"); 12961 shader.loc[GLNVGuniformLoc.TMat] = glGetUniformLocation(shader.prog, "tmat"); 12962 shader.loc[GLNVGuniformLoc.TTr] = glGetUniformLocation(shader.prog, "ttr"); 12963 shader.loc[GLNVGuniformLoc.ClipTex] = glGetUniformLocation(shader.prog, "clipTex"); 12964 } 12965 12966 void glnvg__killFBOs (GLNVGcontext* gl) nothrow @trusted @nogc { 12967 foreach (immutable fidx, ref GLuint fbo; gl.fbo[]) { 12968 if (fbo != 0) { 12969 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); 12970 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); 12971 foreach (ref GLuint tid; gl.fboTex.ptr[fidx][]) if (tid != 0) { glDeleteTextures(1, &tid); tid = 0; } 12972 glDeleteFramebuffers(1, &fbo); 12973 fbo = 0; 12974 } 12975 } 12976 gl.fboWidth = gl.fboHeight = 0; 12977 } 12978 12979 // returns `true` is new FBO was created 12980 // will not unbind buffer, if it was created 12981 bool glnvg__allocFBO (GLNVGcontext* gl, int fidx, bool doclear=true) nothrow @trusted @nogc { 12982 assert(fidx >= 0 && fidx < gl.fbo.length); 12983 assert(gl.fboWidth > 0); 12984 assert(gl.fboHeight > 0); 12985 12986 if (gl.fbo.ptr[fidx] != 0) return false; // nothing to do, this FBO is already initialized 12987 12988 glnvg__resetError(gl); 12989 12990 // allocate FBO object 12991 GLuint fbo = 0; 12992 glGenFramebuffers(1, &fbo); 12993 if (fbo == 0) assert(0, "NanoVega: cannot create FBO"); 12994 glnvg__checkError(gl, "glnvg__allocFBO: glGenFramebuffers"); 12995 glBindFramebuffer(GL_FRAMEBUFFER, fbo); 12996 //scope(exit) glBindFramebuffer(GL_FRAMEBUFFER, 0); 12997 12998 // attach 2D texture to this FBO 12999 GLuint tidColor = 0; 13000 glGenTextures(1, &tidColor); 13001 if (tidColor == 0) assert(0, "NanoVega: cannot create RGBA texture for FBO"); 13002 glBindTexture(GL_TEXTURE_2D, tidColor); 13003 //scope(exit) glBindTexture(GL_TEXTURE_2D, 0); 13004 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 13005 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_S"); 13006 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 13007 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_T"); 13008 //FIXME: linear or nearest? 13009 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 13010 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 13011 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MIN_FILTER"); 13012 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 13013 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 13014 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MAG_FILTER"); 13015 // empty texture 13016 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 13017 // create texture with only one color channel 13018 glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, gl.fboWidth, gl.fboHeight, 0, GL_RED, GL_UNSIGNED_BYTE, null); 13019 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 13020 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (color)"); 13021 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tidColor, 0); 13022 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (color)"); 13023 13024 // attach stencil texture to this FBO 13025 GLuint tidStencil = 0; 13026 version(nanovega_shared_stencil) { 13027 if (gl.fboTex.ptr[0].ptr[0] == 0) { 13028 glGenTextures(1, &tidStencil); 13029 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO"); 13030 gl.fboTex.ptr[0].ptr[0] = tidStencil; 13031 } else { 13032 tidStencil = gl.fboTex.ptr[0].ptr[0]; 13033 } 13034 if (fidx != 0) gl.fboTex.ptr[fidx].ptr[1] = 0; // stencil texture is shared among FBOs 13035 } else { 13036 glGenTextures(1, &tidStencil); 13037 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO"); 13038 gl.fboTex.ptr[0].ptr[0] = tidStencil; 13039 } 13040 glBindTexture(GL_TEXTURE_2D, tidStencil); 13041 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, gl.fboWidth, gl.fboHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, null); 13042 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (stencil)"); 13043 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tidStencil, 0); 13044 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (stencil)"); 13045 13046 { 13047 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); 13048 if (status != GL_FRAMEBUFFER_COMPLETE) { 13049 version(all) { 13050 import core.stdc.stdio; 13051 if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) printf("fucked attachement\n"); 13052 if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) printf("fucked dimensions\n"); 13053 if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) printf("missing attachement\n"); 13054 if (status == GL_FRAMEBUFFER_UNSUPPORTED) printf("unsupported\n"); 13055 } 13056 assert(0, "NanoVega: framebuffer creation failed"); 13057 } 13058 } 13059 13060 // clear 'em all 13061 if (doclear) { 13062 glClearColor(0, 0, 0, 0); 13063 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 13064 } 13065 13066 // save texture ids 13067 gl.fbo.ptr[fidx] = fbo; 13068 gl.fboTex.ptr[fidx].ptr[0] = tidColor; 13069 version(nanovega_shared_stencil) {} else { 13070 gl.fboTex.ptr[fidx].ptr[1] = tidStencil; 13071 } 13072 13073 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture); 13074 13075 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): created with index %d\n", gl.msp-1, fidx); } 13076 13077 return true; 13078 } 13079 13080 // will not unbind buffer 13081 void glnvg__clearFBO (GLNVGcontext* gl, int fidx) nothrow @trusted @nogc { 13082 assert(fidx >= 0 && fidx < gl.fbo.length); 13083 assert(gl.fboWidth > 0); 13084 assert(gl.fboHeight > 0); 13085 assert(gl.fbo.ptr[fidx] != 0); 13086 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[fidx]); 13087 glClearColor(0, 0, 0, 0); 13088 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 13089 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cleared with index %d\n", gl.msp-1, fidx); } 13090 } 13091 13092 // will not unbind buffer 13093 void glnvg__copyFBOToFrom (GLNVGcontext* gl, int didx, int sidx) nothrow @trusted @nogc { 13094 import core.stdc.string : memset; 13095 assert(didx >= 0 && didx < gl.fbo.length); 13096 assert(sidx >= 0 && sidx < gl.fbo.length); 13097 assert(gl.fboWidth > 0); 13098 assert(gl.fboHeight > 0); 13099 assert(gl.fbo.ptr[didx] != 0); 13100 assert(gl.fbo.ptr[sidx] != 0); 13101 if (didx == sidx) return; 13102 13103 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copy FBO: %d -> %d\n", gl.msp-1, sidx, didx); } 13104 13105 glUseProgram(gl.shaderCopyFBO.prog); 13106 13107 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[didx]); 13108 glDisable(GL_CULL_FACE); 13109 glDisable(GL_BLEND); 13110 glDisable(GL_SCISSOR_TEST); 13111 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[sidx].ptr[0]); 13112 // copy texture by drawing full quad 13113 enum x = 0; 13114 enum y = 0; 13115 immutable int w = gl.fboWidth; 13116 immutable int h = gl.fboHeight; 13117 immutable(NVGVertex[4]) vertices = 13118 [NVGVertex(x, y), // top-left 13119 NVGVertex(w, y), // top-right 13120 NVGVertex(w, h), // bottom-right 13121 NVGVertex(x, h)]; // bottom-left 13122 13123 glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices); 13124 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 13125 13126 // restore state (but don't unbind FBO) 13127 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture); 13128 glEnable(GL_CULL_FACE); 13129 glEnable(GL_BLEND); 13130 glUseProgram(gl.shader.prog); 13131 } 13132 13133 void glnvg__resetFBOClipTextureCache (GLNVGcontext* gl) nothrow @trusted @nogc { 13134 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): texture cache invalidated (%d)\n", gl.msp-1, gl.lastClipFBO); } 13135 /* 13136 if (gl.lastClipFBO >= 0) { 13137 glActiveTexture(GL_TEXTURE1); 13138 glBindTexture(GL_TEXTURE_2D, 0); 13139 glActiveTexture(GL_TEXTURE0); 13140 } 13141 */ 13142 gl.lastClipFBO = -666; 13143 } 13144 13145 void glnvg__setFBOClipTexture (GLNVGcontext* gl, GLNVGfragUniforms* frag) nothrow @trusted @nogc { 13146 //assert(gl.msp > 0 && gl.msp <= gl.maskStack.length); 13147 if (gl.lastClipFBO != -666) { 13148 // cached 13149 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cached (%d)\n", gl.msp-1, gl.lastClipFBO); } 13150 frag.doclip = (gl.lastClipFBO >= 0 ? 1 : 0); 13151 return; 13152 } 13153 13154 // no cache 13155 int fboidx = -1; 13156 mainloop: foreach_reverse (immutable sp, GLMaskState mst; gl.maskStack.ptr[0..gl.msp]/*; reverse*/) { 13157 final switch (mst) { 13158 case GLMaskState.DontMask: fboidx = -1; break mainloop; 13159 case GLMaskState.Uninitialized: break; 13160 case GLMaskState.Initialized: fboidx = cast(int)sp; break mainloop; 13161 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__setFBOClipTexture()` internal error"); 13162 } 13163 } 13164 13165 if (fboidx < 0) { 13166 // don't mask 13167 gl.lastClipFBO = -1; 13168 frag.doclip = 0; 13169 } else { 13170 // do masking 13171 assert(gl.fbo.ptr[fboidx] != 0); 13172 gl.lastClipFBO = fboidx; 13173 frag.doclip = 1; 13174 } 13175 13176 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): new cache (new:%d)\n", gl.msp-1, gl.lastClipFBO); } 13177 13178 if (gl.lastClipFBO >= 0) { 13179 assert(gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 13180 glActiveTexture(GL_TEXTURE1); 13181 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 13182 glActiveTexture(GL_TEXTURE0); 13183 } 13184 } 13185 13186 // returns index in `gl.fbo`, or -1 for "don't mask" 13187 int glnvg__generateFBOClipTexture (GLNVGcontext* gl) nothrow @trusted @nogc { 13188 assert(gl.msp > 0 && gl.msp <= gl.maskStack.length); 13189 // we need initialized FBO, even for "don't mask" case 13190 // for this, look back in stack, and either copy initialized FBO, 13191 // or stop at first uninitialized one, and clear it 13192 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.Initialized) { 13193 // shortcut 13194 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); } 13195 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[gl.msp-1]); 13196 return gl.msp-1; 13197 } 13198 foreach_reverse (immutable sp; 0..gl.msp/*; reverse*/) { 13199 final switch (gl.maskStack.ptr[sp]) { 13200 case GLMaskState.DontMask: 13201 // clear it 13202 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture\n", gl.msp-1); } 13203 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1); 13204 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared; 13205 return gl.msp-1; 13206 case GLMaskState.Uninitialized: break; // do nothing 13207 case GLMaskState.Initialized: 13208 // i found her! copy to TOS 13209 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copying texture from %d\n", gl.msp-1, cast(int)sp); } 13210 glnvg__allocFBO(gl, gl.msp-1, false); 13211 glnvg__copyFBOToFrom(gl, gl.msp-1, sp); 13212 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized; 13213 return gl.msp-1; 13214 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__generateFBOClipTexture()` internal error"); 13215 } 13216 } 13217 // nothing was initialized, lol 13218 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture (first one)\n", gl.msp-1); } 13219 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1); 13220 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared; 13221 return gl.msp-1; 13222 } 13223 13224 void glnvg__renderPushClip (void* uptr) nothrow @trusted @nogc { 13225 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13226 GLNVGcall* call = glnvg__allocCall(gl); 13227 if (call is null) return; 13228 call.type = GLNVG_PUSHCLIP; 13229 } 13230 13231 void glnvg__renderPopClip (void* uptr) nothrow @trusted @nogc { 13232 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13233 GLNVGcall* call = glnvg__allocCall(gl); 13234 if (call is null) return; 13235 call.type = GLNVG_POPCLIP; 13236 } 13237 13238 void glnvg__renderResetClip (void* uptr) nothrow @trusted @nogc { 13239 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13240 GLNVGcall* call = glnvg__allocCall(gl); 13241 if (call is null) return; 13242 call.type = GLNVG_RESETCLIP; 13243 } 13244 13245 void glnvg__clipDebugDump (void* uptr, bool doit) nothrow @trusted @nogc { 13246 version(nanovega_debug_clipping) { 13247 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13248 GLNVGcall* call = glnvg__allocCall(gl); 13249 call.type = (doit ? GLNVG_CLIP_DDUMP_ON : GLNVG_CLIP_DDUMP_OFF); 13250 } 13251 } 13252 13253 bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc { 13254 import core.stdc.stdio : snprintf; 13255 13256 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13257 enum align_ = 4; 13258 13259 char[64] shaderHeader = void; 13260 //enum shaderHeader = "#define UNIFORM_ARRAY_SIZE 12\n"; 13261 snprintf(shaderHeader.ptr, shaderHeader.length, "#define UNIFORM_ARRAY_SIZE %u\n", cast(uint)GLNVGfragUniforms.UNIFORM_ARRAY_SIZE); 13262 13263 enum fillVertShader = q{ 13264 uniform vec2 viewSize; 13265 attribute vec2 vertex; 13266 attribute vec2 tcoord; 13267 varying vec2 ftcoord; 13268 varying vec2 fpos; 13269 uniform vec4 tmat; /* abcd of affine matrix: xyzw */ 13270 uniform vec2 ttr; /* tx and ty of affine matrix */ 13271 void main (void) { 13272 /* affine transformation */ 13273 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x; 13274 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y; 13275 ftcoord = tcoord; 13276 fpos = vec2(nx, ny); 13277 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1); 13278 } 13279 }; 13280 13281 enum fillFragShader = ` 13282 uniform vec4 frag[UNIFORM_ARRAY_SIZE]; 13283 uniform sampler2D tex; 13284 uniform sampler2D clipTex; 13285 uniform vec2 viewSize; 13286 varying vec2 ftcoord; 13287 varying vec2 fpos; 13288 #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz) 13289 #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz) 13290 #define innerCol frag[6] 13291 #define middleCol frag[7] 13292 #define outerCol frag[7+1] 13293 #define scissorExt frag[8+1].xy 13294 #define scissorScale frag[8+1].zw 13295 #define extent frag[9+1].xy 13296 #define radius frag[9+1].z 13297 #define feather frag[9+1].w 13298 #define strokeMult frag[10+1].x 13299 #define strokeThr frag[10+1].y 13300 #define texType int(frag[10+1].z) 13301 #define type int(frag[10+1].w) 13302 #define doclip int(frag[11+1].x) 13303 #define midp frag[11+1].y 13304 13305 float sdroundrect (in vec2 pt, in vec2 ext, in float rad) { 13306 vec2 ext2 = ext-vec2(rad, rad); 13307 vec2 d = abs(pt)-ext2; 13308 return min(max(d.x, d.y), 0.0)+length(max(d, 0.0))-rad; 13309 } 13310 13311 // Scissoring 13312 float scissorMask (in vec2 p) { 13313 vec2 sc = (abs((scissorMat*vec3(p, 1.0)).xy)-scissorExt); 13314 sc = vec2(0.5, 0.5)-sc*scissorScale; 13315 return clamp(sc.x, 0.0, 1.0)*clamp(sc.y, 0.0, 1.0); 13316 } 13317 13318 #ifdef EDGE_AA 13319 // Stroke - from [0..1] to clipped pyramid, where the slope is 1px. 13320 float strokeMask () { 13321 return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult)*min(1.0, ftcoord.y); 13322 } 13323 #endif 13324 13325 void main (void) { 13326 // clipping 13327 if (doclip != 0) { 13328 /*vec4 clr = texelFetch(clipTex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);*/ 13329 vec4 clr = texture2D(clipTex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y)); 13330 if (clr.r == 0.0) discard; 13331 } 13332 float scissor = scissorMask(fpos); 13333 if (scissor <= 0.0) discard; //k8: is it really faster? 13334 #ifdef EDGE_AA 13335 float strokeAlpha = strokeMask(); 13336 if (strokeAlpha < strokeThr) discard; 13337 #else 13338 float strokeAlpha = 1.0; 13339 #endif 13340 // rendering 13341 vec4 color; 13342 if (type == 0) { /* NSVG_SHADER_FILLCOLOR */ 13343 color = innerCol; 13344 // Combine alpha 13345 color *= strokeAlpha*scissor; 13346 } else if (type == 1) { /* NSVG_SHADER_FILLGRAD */ 13347 // Gradient 13348 // Calculate gradient color using box gradient 13349 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy; 13350 float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0); 13351 if (midp <= 0.0) { 13352 color = mix(innerCol, outerCol, d); 13353 } else { 13354 float gdst = min(midp, 1.0); 13355 if (d < gdst) { 13356 color = mix(innerCol, middleCol, d/gdst); 13357 } else { 13358 color = mix(middleCol, outerCol, (d-gdst)/gdst); 13359 } 13360 } 13361 // Combine alpha 13362 color *= strokeAlpha*scissor; 13363 } else if (type == 2) { /* NSVG_SHADER_FILLIMG */ 13364 // Image 13365 // Calculate color from texture 13366 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy/extent; 13367 color = texture2D(tex, pt); 13368 if (texType == 1) color = vec4(color.xyz*color.w, color.w); 13369 if (texType == 2) color = vec4(color.x); 13370 // Apply color tint and alpha 13371 color *= innerCol; 13372 // Combine alpha 13373 color *= strokeAlpha*scissor; 13374 } else if (type == 3) { /* NSVG_SHADER_SIMPLE */ 13375 // Stencil fill 13376 color = vec4(1, 1, 1, 1); 13377 } else if (type == 4) { /* NSVG_SHADER_IMG */ 13378 // Textured tris 13379 color = texture2D(tex, ftcoord); 13380 if (texType == 1) color = vec4(color.xyz*color.w, color.w); 13381 if (texType == 2) color = vec4(color.x); 13382 color *= scissor; 13383 color *= innerCol; // Apply color tint 13384 } 13385 gl_FragColor = color; 13386 } 13387 `; 13388 13389 enum clipVertShaderFill = q{ 13390 uniform vec2 viewSize; 13391 attribute vec2 vertex; 13392 uniform vec4 tmat; /* abcd of affine matrix: xyzw */ 13393 uniform vec2 ttr; /* tx and ty of affine matrix */ 13394 void main (void) { 13395 /* affine transformation */ 13396 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x; 13397 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y; 13398 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1); 13399 } 13400 }; 13401 13402 enum clipFragShaderFill = q{ 13403 uniform vec2 viewSize; 13404 void main (void) { 13405 gl_FragColor = vec4(1, 1, 1, 1); 13406 } 13407 }; 13408 13409 enum clipVertShaderCopy = q{ 13410 uniform vec2 viewSize; 13411 attribute vec2 vertex; 13412 void main (void) { 13413 gl_Position = vec4(2.0*vertex.x/viewSize.x-1.0, 1.0-2.0*vertex.y/viewSize.y, 0, 1); 13414 } 13415 }; 13416 13417 enum clipFragShaderCopy = q{ 13418 uniform sampler2D tex; 13419 uniform vec2 viewSize; 13420 void main (void) { 13421 //gl_FragColor = texelFetch(tex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0); 13422 gl_FragColor = texture2D(tex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y)); 13423 } 13424 }; 13425 13426 glnvg__checkError(gl, "init"); 13427 13428 string defines = (gl.flags&NVGContextFlag.Antialias ? "#define EDGE_AA 1\n" : null); 13429 if (!glnvg__createShader(&gl.shader, "shader", shaderHeader.ptr, defines.ptr, fillVertShader, fillFragShader)) return false; 13430 if (!glnvg__createShader(&gl.shaderFillFBO, "shaderFillFBO", shaderHeader.ptr, defines.ptr, clipVertShaderFill, clipFragShaderFill)) return false; 13431 if (!glnvg__createShader(&gl.shaderCopyFBO, "shaderCopyFBO", shaderHeader.ptr, defines.ptr, clipVertShaderCopy, clipFragShaderCopy)) return false; 13432 13433 glnvg__checkError(gl, "uniform locations"); 13434 glnvg__getUniforms(&gl.shader); 13435 glnvg__getUniforms(&gl.shaderFillFBO); 13436 glnvg__getUniforms(&gl.shaderCopyFBO); 13437 13438 // Create dynamic vertex array 13439 glGenBuffers(1, &gl.vertBuf); 13440 13441 gl.fragSize = GLNVGfragUniforms.sizeof+align_-GLNVGfragUniforms.sizeof%align_; 13442 13443 glnvg__checkError(gl, "create done"); 13444 13445 glFinish(); 13446 13447 return true; 13448 } 13449 13450 int glnvg__renderCreateTexture (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc { 13451 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13452 GLNVGtexture* tex = glnvg__allocTexture(gl); 13453 13454 if (tex is null) return 0; 13455 13456 glGenTextures(1, &tex.tex); 13457 tex.width = w; 13458 tex.height = h; 13459 tex.type = type; 13460 tex.flags = imageFlags; 13461 glnvg__bindTexture(gl, tex.tex); 13462 13463 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("created texture with id %d; glid=%u\n", tex.id, tex.tex); }} 13464 13465 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 13466 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width); 13467 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13468 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13469 13470 13471 13472 immutable ttype = (type == NVGtexture.RGBA ? GL_RGBA : GL_RED); 13473 glTexImage2D(GL_TEXTURE_2D, 0, ttype, w, h, 0, ttype, GL_UNSIGNED_BYTE, data); 13474 // GL 3.0 and later have support for a dedicated function for generating mipmaps 13475 // it needs to be called after the glTexImage2D call 13476 if (imageFlags & NVGImageFlag.GenerateMipmaps) 13477 glGenerateMipmap(GL_TEXTURE_2D); 13478 13479 immutable tfmin = 13480 (imageFlags & NVGImageFlag.GenerateMipmaps ? GL_LINEAR_MIPMAP_LINEAR : 13481 imageFlags & NVGImageFlag.NoFiltering ? GL_NEAREST : 13482 GL_LINEAR); 13483 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.0); 13484 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tfmin); 13485 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST : GL_LINEAR)); 13486 13487 int flag; 13488 if (imageFlags&NVGImageFlag.RepeatX) 13489 flag = GL_REPEAT; 13490 else if (imageFlags&NVGImageFlag.ClampToBorderX) 13491 flag = GL_CLAMP_TO_BORDER; 13492 else 13493 flag = GL_CLAMP_TO_EDGE; 13494 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, flag); 13495 13496 13497 if (imageFlags&NVGImageFlag.RepeatY) 13498 flag = GL_REPEAT; 13499 else if (imageFlags&NVGImageFlag.ClampToBorderY) 13500 flag = GL_CLAMP_TO_BORDER; 13501 else 13502 flag = GL_CLAMP_TO_EDGE; 13503 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, flag); 13504 13505 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 13506 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 13507 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13508 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13509 13510 glnvg__checkError(gl, "create tex"); 13511 glnvg__bindTexture(gl, 0); 13512 13513 return tex.id; 13514 } 13515 13516 bool glnvg__renderDeleteTexture (void* uptr, int image) nothrow @trusted @nogc { 13517 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13518 return glnvg__deleteTexture(gl, image); 13519 } 13520 13521 bool glnvg__renderTextureIncRef (void* uptr, int image) nothrow @trusted @nogc { 13522 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13523 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13524 if (tex is null) { 13525 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT incref texture with id %d\n", image); }} 13526 return false; 13527 } 13528 import core.atomic : atomicOp; 13529 atomicOp!"+="(tex.rc, 1); 13530 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("texture #%d: incref; newref=%d\n", image, tex.rc); }} 13531 return true; 13532 } 13533 13534 bool glnvg__renderUpdateTexture (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc { 13535 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13536 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13537 13538 if (tex is null) { 13539 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT update texture with id %d\n", image); }} 13540 return false; 13541 } 13542 13543 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("updated texture with id %d; glid=%u\n", tex.id, image, tex.tex); }} 13544 13545 glnvg__bindTexture(gl, tex.tex); 13546 13547 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 13548 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width); 13549 glPixelStorei(GL_UNPACK_SKIP_PIXELS, x); 13550 glPixelStorei(GL_UNPACK_SKIP_ROWS, y); 13551 13552 immutable ttype = (tex.type == NVGtexture.RGBA ? GL_RGBA : GL_RED); 13553 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, ttype, GL_UNSIGNED_BYTE, data); 13554 13555 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 13556 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 13557 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13558 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13559 13560 glnvg__bindTexture(gl, 0); 13561 13562 return true; 13563 } 13564 13565 bool glnvg__renderGetTextureSize (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc { 13566 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13567 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13568 if (tex is null) { 13569 if (w !is null) *w = 0; 13570 if (h !is null) *h = 0; 13571 return false; 13572 } else { 13573 if (w !is null) *w = tex.width; 13574 if (h !is null) *h = tex.height; 13575 return true; 13576 } 13577 } 13578 13579 void glnvg__xformToMat3x4 (float[] m3, const(float)[] t) nothrow @trusted @nogc { 13580 assert(t.length >= 6); 13581 assert(m3.length >= 12); 13582 m3.ptr[0] = t.ptr[0]; 13583 m3.ptr[1] = t.ptr[1]; 13584 m3.ptr[2] = 0.0f; 13585 m3.ptr[3] = 0.0f; 13586 m3.ptr[4] = t.ptr[2]; 13587 m3.ptr[5] = t.ptr[3]; 13588 m3.ptr[6] = 0.0f; 13589 m3.ptr[7] = 0.0f; 13590 m3.ptr[8] = t.ptr[4]; 13591 m3.ptr[9] = t.ptr[5]; 13592 m3.ptr[10] = 1.0f; 13593 m3.ptr[11] = 0.0f; 13594 } 13595 13596 NVGColor glnvg__premulColor() (in auto ref NVGColor c) nothrow @trusted @nogc { 13597 //pragma(inline, true); 13598 NVGColor res = void; 13599 res.r = c.r*c.a; 13600 res.g = c.g*c.a; 13601 res.b = c.b*c.a; 13602 res.a = c.a; 13603 return res; 13604 } 13605 13606 bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* paint, NVGscissor* scissor, float width, float fringe, float strokeThr) nothrow @trusted @nogc { 13607 import core.stdc.math : sqrtf; 13608 GLNVGtexture* tex = null; 13609 NVGMatrix invxform = void; 13610 13611 memset(frag, 0, (*frag).sizeof); 13612 13613 frag.innerCol = glnvg__premulColor(paint.innerColor); 13614 frag.middleCol = glnvg__premulColor(paint.middleColor); 13615 frag.outerCol = glnvg__premulColor(paint.outerColor); 13616 frag.midp = paint.midp; 13617 13618 if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) { 13619 memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof); 13620 frag.scissorExt.ptr[0] = 1.0f; 13621 frag.scissorExt.ptr[1] = 1.0f; 13622 frag.scissorScale.ptr[0] = 1.0f; 13623 frag.scissorScale.ptr[1] = 1.0f; 13624 } else { 13625 //nvgTransformInverse(invxform[], scissor.xform[]); 13626 invxform = scissor.xform.inverted; 13627 glnvg__xformToMat3x4(frag.scissorMat[], invxform.mat[]); 13628 frag.scissorExt.ptr[0] = scissor.extent.ptr[0]; 13629 frag.scissorExt.ptr[1] = scissor.extent.ptr[1]; 13630 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; 13631 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; 13632 } 13633 13634 memcpy(frag.extent.ptr, paint.extent.ptr, frag.extent.sizeof); 13635 frag.strokeMult = (width*0.5f+fringe*0.5f)/fringe; 13636 frag.strokeThr = strokeThr; 13637 13638 if (paint.image.valid) { 13639 tex = glnvg__findTexture(gl, paint.image.id); 13640 if (tex is null) return false; 13641 if ((tex.flags&NVGImageFlag.FlipY) != 0) { 13642 /* 13643 NVGMatrix flipped; 13644 nvgTransformScale(flipped[], 1.0f, -1.0f); 13645 nvgTransformMultiply(flipped[], paint.xform[]); 13646 nvgTransformInverse(invxform[], flipped[]); 13647 */ 13648 /* 13649 NVGMatrix m1 = void, m2 = void; 13650 nvgTransformTranslate(m1[], 0.0f, frag.extent.ptr[1]*0.5f); 13651 nvgTransformMultiply(m1[], paint.xform[]); 13652 nvgTransformScale(m2[], 1.0f, -1.0f); 13653 nvgTransformMultiply(m2[], m1[]); 13654 nvgTransformTranslate(m1[], 0.0f, -frag.extent.ptr[1]*0.5f); 13655 nvgTransformMultiply(m1[], m2[]); 13656 nvgTransformInverse(invxform[], m1[]); 13657 */ 13658 NVGMatrix m1 = NVGMatrix.Translated(0.0f, frag.extent.ptr[1]*0.5f); 13659 m1.mul(paint.xform); 13660 NVGMatrix m2 = NVGMatrix.Scaled(1.0f, -1.0f); 13661 m2.mul(m1); 13662 m1 = NVGMatrix.Translated(0.0f, -frag.extent.ptr[1]*0.5f); 13663 m1.mul(m2); 13664 invxform = m1.inverted; 13665 } else { 13666 //nvgTransformInverse(invxform[], paint.xform[]); 13667 invxform = paint.xform.inverted; 13668 } 13669 frag.type = NSVG_SHADER_FILLIMG; 13670 13671 if (tex.type == NVGtexture.RGBA) { 13672 frag.texType = (tex.flags&NVGImageFlag.Premultiplied ? 0 : 1); 13673 } else { 13674 frag.texType = 2; 13675 } 13676 //printf("frag.texType = %d\n", frag.texType); 13677 } else { 13678 frag.type = (paint.simpleColor ? NSVG_SHADER_FILLCOLOR : NSVG_SHADER_FILLGRAD); 13679 frag.radius = paint.radius; 13680 frag.feather = paint.feather; 13681 //nvgTransformInverse(invxform[], paint.xform[]); 13682 invxform = paint.xform.inverted; 13683 } 13684 13685 glnvg__xformToMat3x4(frag.paintMat[], invxform.mat[]); 13686 13687 return true; 13688 } 13689 13690 void glnvg__setUniforms (GLNVGcontext* gl, int uniformOffset, int image) nothrow @trusted @nogc { 13691 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); 13692 glnvg__setFBOClipTexture(gl, frag); 13693 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.Frag], frag.UNIFORM_ARRAY_SIZE, &(frag.uniformArray.ptr[0].ptr[0])); 13694 glnvg__checkError(gl, "glnvg__setUniforms"); 13695 if (image != 0) { 13696 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13697 glnvg__bindTexture(gl, (tex !is null ? tex.tex : 0)); 13698 glnvg__checkError(gl, "tex paint tex"); 13699 } else { 13700 glnvg__bindTexture(gl, 0); 13701 } 13702 } 13703 13704 void glnvg__finishClip (GLNVGcontext* gl, NVGClipMode clipmode) nothrow @trusted @nogc { 13705 assert(clipmode != NVGClipMode.None); 13706 13707 // fill FBO, clear stencil buffer 13708 //TODO: optimize with bounds? 13709 version(all) { 13710 //glnvg__resetAffine(gl); 13711 //glUseProgram(gl.shaderFillFBO.prog); 13712 glDisable(GL_CULL_FACE); 13713 glDisable(GL_BLEND); 13714 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13715 glEnable(GL_STENCIL_TEST); 13716 if (gl.doClipUnion) { 13717 // for "and" we should clear everything that is NOT stencil-masked 13718 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13719 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13720 } else { 13721 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x00, 0xff); 13722 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13723 } 13724 13725 immutable(NVGVertex[4]) vertices = 13726 [NVGVertex(0, 0, 0, 0), 13727 NVGVertex(0, gl.fboHeight, 0, 0), 13728 NVGVertex(gl.fboWidth, gl.fboHeight, 0, 0), 13729 NVGVertex(gl.fboWidth, 0, 0, 0)]; 13730 13731 glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices); 13732 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 13733 13734 //glnvg__restoreAffine(gl); 13735 } 13736 13737 glBindFramebuffer(GL_FRAMEBUFFER, gl.mainFBO); 13738 glDisable(GL_COLOR_LOGIC_OP); 13739 //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // done above 13740 glEnable(GL_BLEND); 13741 glDisable(GL_STENCIL_TEST); 13742 glEnable(GL_CULL_FACE); 13743 glUseProgram(gl.shader.prog); 13744 13745 // set current FBO as used one 13746 assert(gl.msp > 0 && gl.fbo.ptr[gl.msp-1] > 0 && gl.fboTex.ptr[gl.msp-1].ptr[0] > 0); 13747 if (gl.lastClipFBO != gl.msp-1) { 13748 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); } 13749 gl.lastClipFBO = gl.msp-1; 13750 glActiveTexture(GL_TEXTURE1); 13751 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 13752 glActiveTexture(GL_TEXTURE0); 13753 } 13754 } 13755 13756 void glnvg__setClipUniforms (GLNVGcontext* gl, int uniformOffset, NVGClipMode clipmode) nothrow @trusted @nogc { 13757 assert(clipmode != NVGClipMode.None); 13758 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); 13759 // save uniform offset for `glnvg__finishClip()` 13760 gl.lastClipUniOfs = uniformOffset; 13761 // get FBO index, bind this FBO 13762 immutable int clipTexId = glnvg__generateFBOClipTexture(gl); 13763 assert(clipTexId >= 0); 13764 glUseProgram(gl.shaderFillFBO.prog); 13765 glnvg__checkError(gl, "use"); 13766 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[clipTexId]); 13767 // set logic op for clip 13768 gl.doClipUnion = false; 13769 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.JustCleared) { 13770 // it is cleared to zero, we can just draw a path 13771 glDisable(GL_COLOR_LOGIC_OP); 13772 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized; 13773 } else { 13774 glEnable(GL_COLOR_LOGIC_OP); 13775 final switch (clipmode) { 13776 case NVGClipMode.None: assert(0, "wtf?!"); 13777 case NVGClipMode.Union: glLogicOp(GL_CLEAR); gl.doClipUnion = true; break; // use `GL_CLEAR` to avoid adding another shader mode 13778 case NVGClipMode.Or: glLogicOp(GL_COPY); break; // GL_OR 13779 case NVGClipMode.Xor: glLogicOp(GL_XOR); break; 13780 case NVGClipMode.Sub: glLogicOp(GL_CLEAR); break; 13781 case NVGClipMode.Replace: glLogicOp(GL_COPY); break; 13782 } 13783 } 13784 // set affine matrix 13785 glUniform4fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TMat], 1, gl.lastAffine.mat.ptr); 13786 glnvg__checkError(gl, "affine 0"); 13787 glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TTr], 1, gl.lastAffine.mat.ptr+4); 13788 glnvg__checkError(gl, "affine 1"); 13789 // setup common OpenGL parameters 13790 glDisable(GL_BLEND); 13791 glDisable(GL_CULL_FACE); 13792 glEnable(GL_STENCIL_TEST); 13793 glnvg__stencilMask(gl, 0xff); 13794 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13795 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 13796 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13797 } 13798 13799 void glnvg__renderViewport (void* uptr, int width, int height) nothrow @trusted @nogc { 13800 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13801 gl.inFrame = true; 13802 gl.view.ptr[0] = cast(float)width; 13803 gl.view.ptr[1] = cast(float)height; 13804 // kill FBOs if we need to create new ones (flushing will recreate 'em if necessary) 13805 if (width != gl.fboWidth || height != gl.fboHeight) { 13806 glnvg__killFBOs(gl); 13807 gl.fboWidth = width; 13808 gl.fboHeight = height; 13809 } 13810 gl.msp = 1; 13811 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13812 // texture cleanup 13813 import core.atomic : atomicLoad; 13814 if (atomicLoad(gl.mustCleanTextures)) { 13815 try { 13816 import core.thread : Thread; 13817 static if (__VERSION__ < 2076) { 13818 DGNoThrowNoGC(() { 13819 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13820 })(); 13821 } else { 13822 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13823 } 13824 synchronized(GLNVGTextureLocker.classinfo) { 13825 gl.mustCleanTextures = false; 13826 foreach (immutable tidx, ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) { 13827 // no need to use atomic ops here, as we're locked 13828 if (tex.rc == 0 && tex.tex != 0 && tex.id == 0) { 13829 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** cleaned up texture with glid=%u\n", tex.tex); }} 13830 import core.stdc.string : memset; 13831 if ((tex.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tex.tex); 13832 memset(&tex, 0, tex.sizeof); 13833 tex.nextfree = gl.freetexid; 13834 gl.freetexid = cast(int)tidx; 13835 } 13836 } 13837 } 13838 } catch (Exception e) {} 13839 } 13840 } 13841 13842 void glnvg__fill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13843 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13844 int npaths = call.pathCount; 13845 13846 if (call.clipmode == NVGClipMode.None) { 13847 // Draw shapes 13848 glEnable(GL_STENCIL_TEST); 13849 glnvg__stencilMask(gl, 0xffU); 13850 glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xffU); 13851 13852 glnvg__setUniforms(gl, call.uniformOffset, 0); 13853 glnvg__checkError(gl, "fill simple"); 13854 13855 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13856 if (call.evenOdd) { 13857 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT); 13858 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT); 13859 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); 13860 } else { 13861 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 13862 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 13863 } 13864 glDisable(GL_CULL_FACE); 13865 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13866 glEnable(GL_CULL_FACE); 13867 13868 // Draw anti-aliased pixels 13869 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13870 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image); 13871 glnvg__checkError(gl, "fill fill"); 13872 13873 if (gl.flags&NVGContextFlag.Antialias) { 13874 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xffU); 13875 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13876 // Draw fringes 13877 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13878 } 13879 13880 // Draw fill 13881 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xffU); 13882 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13883 if (call.evenOdd) { 13884 glDisable(GL_CULL_FACE); 13885 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount); 13886 //foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13887 glEnable(GL_CULL_FACE); 13888 } else { 13889 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount); 13890 } 13891 13892 glDisable(GL_STENCIL_TEST); 13893 } else { 13894 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); // this activates our FBO 13895 glnvg__checkError(gl, "fillclip simple"); 13896 glnvg__stencilFunc(gl, GL_ALWAYS, 0x00, 0xffU); 13897 if (call.evenOdd) { 13898 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT); 13899 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT); 13900 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); 13901 } else { 13902 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 13903 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 13904 } 13905 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13906 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13907 } 13908 } 13909 13910 void glnvg__convexFill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13911 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13912 int npaths = call.pathCount; 13913 13914 if (call.clipmode == NVGClipMode.None) { 13915 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13916 glnvg__checkError(gl, "convex fill"); 13917 if (call.evenOdd) glDisable(GL_CULL_FACE); 13918 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13919 if (gl.flags&NVGContextFlag.Antialias) { 13920 // Draw fringes 13921 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13922 } 13923 if (call.evenOdd) glEnable(GL_CULL_FACE); 13924 } else { 13925 glnvg__setClipUniforms(gl, call.uniformOffset, call.clipmode); // this activates our FBO 13926 glnvg__checkError(gl, "clip convex fill"); 13927 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13928 if (gl.flags&NVGContextFlag.Antialias) { 13929 // Draw fringes 13930 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13931 } 13932 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13933 } 13934 } 13935 13936 void glnvg__stroke (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13937 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13938 int npaths = call.pathCount; 13939 13940 if (call.clipmode == NVGClipMode.None) { 13941 if (gl.flags&NVGContextFlag.StencilStrokes) { 13942 glEnable(GL_STENCIL_TEST); 13943 glnvg__stencilMask(gl, 0xff); 13944 13945 // Fill the stroke base without overlap 13946 glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff); 13947 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 13948 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image); 13949 glnvg__checkError(gl, "stroke fill 0"); 13950 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13951 13952 // Draw anti-aliased pixels. 13953 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13954 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13955 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13956 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13957 13958 // Clear stencil buffer. 13959 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13960 glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff); 13961 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13962 glnvg__checkError(gl, "stroke fill 1"); 13963 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13964 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13965 13966 glDisable(GL_STENCIL_TEST); 13967 13968 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f); 13969 } else { 13970 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13971 glnvg__checkError(gl, "stroke fill"); 13972 // Draw Strokes 13973 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13974 } 13975 } else { 13976 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); 13977 glnvg__checkError(gl, "stroke fill 0"); 13978 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13979 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13980 } 13981 } 13982 13983 void glnvg__triangles (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13984 if (call.clipmode == NVGClipMode.None) { 13985 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13986 glnvg__checkError(gl, "triangles fill"); 13987 glDrawArrays(GL_TRIANGLES, call.triangleOffset, call.triangleCount); 13988 } else { 13989 //TODO(?): use texture as mask? 13990 } 13991 } 13992 13993 void glnvg__affine (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13994 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, call.affine.mat.ptr); 13995 glnvg__checkError(gl, "affine"); 13996 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, call.affine.mat.ptr+4); 13997 glnvg__checkError(gl, "affine"); 13998 //glnvg__setUniforms(gl, call.uniformOffset, call.image); 13999 } 14000 14001 void glnvg__renderCancelInternal (GLNVGcontext* gl, bool clearTextures) nothrow @trusted @nogc { 14002 scope(exit) gl.inFrame = false; 14003 if (clearTextures && gl.inFrame) { 14004 try { 14005 import core.thread : Thread; 14006 static if (__VERSION__ < 2076) { 14007 DGNoThrowNoGC(() { 14008 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 14009 })(); 14010 } else { 14011 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 14012 } 14013 } catch (Exception e) {} 14014 foreach (ref GLNVGcall c; gl.calls[0..gl.ncalls]) if (c.image > 0) glnvg__deleteTexture(gl, c.image); 14015 } 14016 gl.nverts = 0; 14017 gl.npaths = 0; 14018 gl.ncalls = 0; 14019 gl.nuniforms = 0; 14020 gl.msp = 1; 14021 gl.maskStack.ptr[0] = GLMaskState.DontMask; 14022 } 14023 14024 void glnvg__renderCancel (void* uptr) nothrow @trusted @nogc { 14025 glnvg__renderCancelInternal(cast(GLNVGcontext*)uptr, true); 14026 } 14027 14028 GLenum glnvg_convertBlendFuncFactor (NVGBlendFactor factor) pure nothrow @trusted @nogc { 14029 if (factor == NVGBlendFactor.Zero) return GL_ZERO; 14030 if (factor == NVGBlendFactor.One) return GL_ONE; 14031 if (factor == NVGBlendFactor.SrcColor) return GL_SRC_COLOR; 14032 if (factor == NVGBlendFactor.OneMinusSrcColor) return GL_ONE_MINUS_SRC_COLOR; 14033 if (factor == NVGBlendFactor.DstColor) return GL_DST_COLOR; 14034 if (factor == NVGBlendFactor.OneMinusDstColor) return GL_ONE_MINUS_DST_COLOR; 14035 if (factor == NVGBlendFactor.SrcAlpha) return GL_SRC_ALPHA; 14036 if (factor == NVGBlendFactor.OneMinusSrcAlpha) return GL_ONE_MINUS_SRC_ALPHA; 14037 if (factor == NVGBlendFactor.DstAlpha) return GL_DST_ALPHA; 14038 if (factor == NVGBlendFactor.OneMinusDstAlpha) return GL_ONE_MINUS_DST_ALPHA; 14039 if (factor == NVGBlendFactor.SrcAlphaSaturate) return GL_SRC_ALPHA_SATURATE; 14040 return GL_INVALID_ENUM; 14041 } 14042 14043 GLNVGblend glnvg__buildBlendFunc (NVGCompositeOperationState op) pure nothrow @trusted @nogc { 14044 GLNVGblend res; 14045 res.simple = op.simple; 14046 res.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB); 14047 res.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB); 14048 res.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha); 14049 res.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha); 14050 if (res.simple) { 14051 if (res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) { 14052 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM; 14053 } 14054 } else { 14055 if (res.srcRGB == GL_INVALID_ENUM || res.dstRGB == GL_INVALID_ENUM || res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) { 14056 res.simple = true; 14057 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM; 14058 } 14059 } 14060 return res; 14061 } 14062 14063 void glnvg__blendCompositeOperation() (GLNVGcontext* gl, in auto ref GLNVGblend op) nothrow @trusted @nogc { 14064 //glBlendFuncSeparate(glnvg_convertBlendFuncFactor(op.srcRGB), glnvg_convertBlendFuncFactor(op.dstRGB), glnvg_convertBlendFuncFactor(op.srcAlpha), glnvg_convertBlendFuncFactor(op.dstAlpha)); 14065 static if (NANOVG_GL_USE_STATE_FILTER) { 14066 if (gl.blendFunc.simple == op.simple) { 14067 if (op.simple) { 14068 if (gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return; 14069 } else { 14070 if (gl.blendFunc.srcRGB == op.srcRGB && gl.blendFunc.dstRGB == op.dstRGB && gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return; 14071 } 14072 } 14073 gl.blendFunc = op; 14074 } 14075 if (op.simple) { 14076 if (op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) { 14077 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 14078 } else { 14079 glBlendFunc(op.srcAlpha, op.dstAlpha); 14080 } 14081 } else { 14082 if (op.srcRGB == GL_INVALID_ENUM || op.dstRGB == GL_INVALID_ENUM || op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) { 14083 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 14084 } else { 14085 glBlendFuncSeparate(op.srcRGB, op.dstRGB, op.srcAlpha, op.dstAlpha); 14086 } 14087 } 14088 } 14089 14090 void glnvg__renderSetAffine (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc { 14091 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14092 GLNVGcall* call; 14093 // if last operation was GLNVG_AFFINE, simply replace the matrix 14094 if (gl.ncalls > 0 && gl.calls[gl.ncalls-1].type == GLNVG_AFFINE) { 14095 call = &gl.calls[gl.ncalls-1]; 14096 } else { 14097 call = glnvg__allocCall(gl); 14098 if (call is null) return; 14099 call.type = GLNVG_AFFINE; 14100 } 14101 call.affine.mat.ptr[0..6] = mat.mat.ptr[0..6]; 14102 } 14103 14104 version(nanovega_debug_clipping) public __gshared bool nanovegaClipDebugDump = false; 14105 14106 void glnvg__renderFlush (void* uptr) nothrow @trusted @nogc { 14107 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14108 if (!gl.inFrame) assert(0, "NanoVega: internal driver error"); 14109 try { 14110 import core.thread : Thread; 14111 static if (__VERSION__ < 2076) { 14112 DGNoThrowNoGC(() { 14113 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 14114 })(); 14115 } else { 14116 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 14117 } 14118 } catch (Exception e) {} 14119 scope(exit) gl.inFrame = false; 14120 14121 glnvg__resetError!true(gl); 14122 { 14123 int vv = 0; 14124 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &vv); 14125 if (glGetError() || vv < 0) vv = 0; 14126 gl.mainFBO = cast(uint)vv; 14127 } 14128 14129 enum ShaderType { None, Fill, Clip } 14130 auto lastShader = ShaderType.None; 14131 if (gl.ncalls > 0) { 14132 gl.msp = 1; 14133 gl.maskStack.ptr[0] = GLMaskState.DontMask; 14134 14135 // Setup require GL state. 14136 glUseProgram(gl.shader.prog); 14137 14138 glActiveTexture(GL_TEXTURE1); 14139 glBindTexture(GL_TEXTURE_2D, 0); 14140 glActiveTexture(GL_TEXTURE0); 14141 glnvg__resetFBOClipTextureCache(gl); 14142 14143 //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 14144 static if (NANOVG_GL_USE_STATE_FILTER) { 14145 gl.blendFunc.simple = true; 14146 gl.blendFunc.srcRGB = gl.blendFunc.dstRGB = gl.blendFunc.srcAlpha = gl.blendFunc.dstAlpha = GL_INVALID_ENUM; 14147 } 14148 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // just in case 14149 glEnable(GL_CULL_FACE); 14150 glCullFace(GL_BACK); 14151 glFrontFace(GL_CCW); 14152 glEnable(GL_BLEND); 14153 glDisable(GL_DEPTH_TEST); 14154 glDisable(GL_SCISSOR_TEST); 14155 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 14156 glStencilMask(0xffffffff); 14157 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 14158 glStencilFunc(GL_ALWAYS, 0, 0xffffffff); 14159 glActiveTexture(GL_TEXTURE0); 14160 glBindTexture(GL_TEXTURE_2D, 0); 14161 static if (NANOVG_GL_USE_STATE_FILTER) { 14162 gl.boundTexture = 0; 14163 gl.stencilMask = 0xffffffff; 14164 gl.stencilFunc = GL_ALWAYS; 14165 gl.stencilFuncRef = 0; 14166 gl.stencilFuncMask = 0xffffffff; 14167 } 14168 glnvg__checkError(gl, "OpenGL setup"); 14169 14170 // Upload vertex data 14171 glBindBuffer(GL_ARRAY_BUFFER, gl.vertBuf); 14172 // ensure that there's space for at least 4 vertices in case we need to draw a quad (e. g. framebuffer copy) 14173 glBufferData(GL_ARRAY_BUFFER, (gl.nverts < 4 ? 4 : gl.nverts) * NVGVertex.sizeof, gl.verts, GL_STREAM_DRAW); 14174 glEnableVertexAttribArray(0); 14175 glEnableVertexAttribArray(1); 14176 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)cast(usize)0); 14177 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)(0+2*float.sizeof)); 14178 glnvg__checkError(gl, "vertex data uploading"); 14179 14180 // Set view and texture just once per frame. 14181 glUniform1i(gl.shader.loc[GLNVGuniformLoc.Tex], 0); 14182 if (gl.shader.loc[GLNVGuniformLoc.ClipTex] != -1) { 14183 //{ import core.stdc.stdio; printf("%d\n", gl.shader.loc[GLNVGuniformLoc.ClipTex]); } 14184 glUniform1i(gl.shader.loc[GLNVGuniformLoc.ClipTex], 1); 14185 } 14186 if (gl.shader.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shader.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 14187 glnvg__checkError(gl, "render shader setup"); 14188 14189 // Reset affine transformations. 14190 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, NVGMatrix.IdentityMat.ptr); 14191 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, NVGMatrix.IdentityMat.ptr+4); 14192 glnvg__checkError(gl, "affine setup"); 14193 14194 // set clip shaders params 14195 // fill 14196 glUseProgram(gl.shaderFillFBO.prog); 14197 glnvg__checkError(gl, "clip shaders setup (fill 0)"); 14198 if (gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 14199 glnvg__checkError(gl, "clip shaders setup (fill 1)"); 14200 // copy 14201 glUseProgram(gl.shaderCopyFBO.prog); 14202 glnvg__checkError(gl, "clip shaders setup (copy 0)"); 14203 if (gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 14204 glnvg__checkError(gl, "clip shaders setup (copy 1)"); 14205 //glUniform1i(gl.shaderFillFBO.loc[GLNVGuniformLoc.Tex], 0); 14206 glUniform1i(gl.shaderCopyFBO.loc[GLNVGuniformLoc.Tex], 0); 14207 glnvg__checkError(gl, "clip shaders setup (copy 2)"); 14208 // restore render shader 14209 glUseProgram(gl.shader.prog); 14210 14211 //{ 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]); } 14212 14213 gl.lastAffine.identity; 14214 14215 foreach (int i; 0..gl.ncalls) { 14216 GLNVGcall* call = &gl.calls[i]; 14217 switch (call.type) { 14218 case GLNVG_FILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__fill(gl, call); break; 14219 case GLNVG_CONVEXFILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__convexFill(gl, call); break; 14220 case GLNVG_STROKE: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__stroke(gl, call); break; 14221 case GLNVG_TRIANGLES: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__triangles(gl, call); break; 14222 case GLNVG_AFFINE: gl.lastAffine = call.affine; glnvg__affine(gl, call); break; 14223 // clip region management 14224 case GLNVG_PUSHCLIP: 14225 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]); } 14226 if (gl.msp >= gl.maskStack.length) assert(0, "NanoVega: mask stack overflow in OpenGL backend"); 14227 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.DontMask) { 14228 gl.maskStack.ptr[gl.msp++] = GLMaskState.DontMask; 14229 } else { 14230 gl.maskStack.ptr[gl.msp++] = GLMaskState.Uninitialized; 14231 } 14232 // no need to reset FBO cache here, as nothing was changed 14233 break; 14234 case GLNVG_POPCLIP: 14235 if (gl.msp <= 1) assert(0, "NanoVega: mask stack underflow in OpenGL backend"); 14236 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]); } 14237 --gl.msp; 14238 assert(gl.msp > 0); 14239 //{ import core.stdc.stdio; printf("popped; new msp is %d; state is %d\n", gl.msp, gl.maskStack.ptr[gl.msp]); } 14240 // check popped item 14241 final switch (gl.maskStack.ptr[gl.msp]) { 14242 case GLMaskState.DontMask: 14243 // if last FBO was "don't mask", reset cache if current is not "don't mask" 14244 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) { 14245 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14246 glnvg__resetFBOClipTextureCache(gl); 14247 } 14248 break; 14249 case GLMaskState.Uninitialized: 14250 // if last FBO texture was uninitialized, it means that nothing was changed, 14251 // so we can keep using cached FBO 14252 break; 14253 case GLMaskState.Initialized: 14254 // if last FBO was initialized, it means that something was definitely changed 14255 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14256 glnvg__resetFBOClipTextureCache(gl); 14257 break; 14258 case GLMaskState.JustCleared: assert(0, "NanoVega: internal FBO stack error"); 14259 } 14260 break; 14261 case GLNVG_RESETCLIP: 14262 // mark current mask as "don't mask" 14263 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]); } 14264 if (gl.msp > 0) { 14265 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) { 14266 gl.maskStack.ptr[gl.msp-1] = GLMaskState.DontMask; 14267 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14268 glnvg__resetFBOClipTextureCache(gl); 14269 } 14270 } 14271 break; 14272 case GLNVG_CLIP_DDUMP_ON: 14273 version(nanovega_debug_clipping) nanovegaClipDebugDump = true; 14274 break; 14275 case GLNVG_CLIP_DDUMP_OFF: 14276 version(nanovega_debug_clipping) nanovegaClipDebugDump = false; 14277 break; 14278 case GLNVG_NONE: break; 14279 default: 14280 { 14281 import core.stdc.stdio; stderr.fprintf("NanoVega FATAL: invalid command in OpenGL backend: %d\n", call.type); 14282 } 14283 assert(0, "NanoVega: invalid command in OpenGL backend (fatal internal error)"); 14284 } 14285 // and free texture, why not 14286 glnvg__deleteTexture(gl, call.image); 14287 } 14288 14289 glDisableVertexAttribArray(0); 14290 glDisableVertexAttribArray(1); 14291 glDisable(GL_CULL_FACE); 14292 glBindBuffer(GL_ARRAY_BUFFER, 0); 14293 glUseProgram(0); 14294 glnvg__bindTexture(gl, 0); 14295 } 14296 14297 // this will do all necessary cleanup 14298 glnvg__renderCancelInternal(gl, false); // no need to clear textures 14299 } 14300 14301 int glnvg__maxVertCount (const(NVGpath)* paths, int npaths) nothrow @trusted @nogc { 14302 int count = 0; 14303 foreach (int i; 0..npaths) { 14304 count += paths[i].nfill; 14305 count += paths[i].nstroke; 14306 } 14307 return count; 14308 } 14309 14310 GLNVGcall* glnvg__allocCall (GLNVGcontext* gl) nothrow @trusted @nogc { 14311 GLNVGcall* ret = null; 14312 if (gl.ncalls+1 > gl.ccalls) { 14313 GLNVGcall* calls; 14314 int ccalls = glnvg__maxi(gl.ncalls+1, 128)+gl.ccalls/2; // 1.5x Overallocate 14315 calls = cast(GLNVGcall*)realloc(gl.calls, GLNVGcall.sizeof*ccalls); 14316 if (calls is null) return null; 14317 gl.calls = calls; 14318 gl.ccalls = ccalls; 14319 } 14320 ret = &gl.calls[gl.ncalls++]; 14321 memset(ret, 0, GLNVGcall.sizeof); 14322 return ret; 14323 } 14324 14325 int glnvg__allocPaths (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14326 int ret = 0; 14327 if (gl.npaths+n > gl.cpaths) { 14328 GLNVGpath* paths; 14329 int cpaths = glnvg__maxi(gl.npaths+n, 128)+gl.cpaths/2; // 1.5x Overallocate 14330 paths = cast(GLNVGpath*)realloc(gl.paths, GLNVGpath.sizeof*cpaths); 14331 if (paths is null) return -1; 14332 gl.paths = paths; 14333 gl.cpaths = cpaths; 14334 } 14335 ret = gl.npaths; 14336 gl.npaths += n; 14337 return ret; 14338 } 14339 14340 int glnvg__allocVerts (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14341 int ret = 0; 14342 if (gl.nverts+n > gl.cverts) { 14343 NVGVertex* verts; 14344 int cverts = glnvg__maxi(gl.nverts+n, 4096)+gl.cverts/2; // 1.5x Overallocate 14345 verts = cast(NVGVertex*)realloc(gl.verts, NVGVertex.sizeof*cverts); 14346 if (verts is null) return -1; 14347 gl.verts = verts; 14348 gl.cverts = cverts; 14349 } 14350 ret = gl.nverts; 14351 gl.nverts += n; 14352 return ret; 14353 } 14354 14355 int glnvg__allocFragUniforms (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14356 int ret = 0, structSize = gl.fragSize; 14357 if (gl.nuniforms+n > gl.cuniforms) { 14358 ubyte* uniforms; 14359 int cuniforms = glnvg__maxi(gl.nuniforms+n, 128)+gl.cuniforms/2; // 1.5x Overallocate 14360 uniforms = cast(ubyte*)realloc(gl.uniforms, structSize*cuniforms); 14361 if (uniforms is null) return -1; 14362 gl.uniforms = uniforms; 14363 gl.cuniforms = cuniforms; 14364 } 14365 ret = gl.nuniforms*structSize; 14366 gl.nuniforms += n; 14367 return ret; 14368 } 14369 14370 GLNVGfragUniforms* nvg__fragUniformPtr (GLNVGcontext* gl, int i) nothrow @trusted @nogc { 14371 return cast(GLNVGfragUniforms*)&gl.uniforms[i]; 14372 } 14373 14374 void glnvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc { 14375 vtx.x = x; 14376 vtx.y = y; 14377 vtx.u = u; 14378 vtx.v = v; 14379 } 14380 14381 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 { 14382 if (npaths < 1) return; 14383 14384 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14385 GLNVGcall* call = glnvg__allocCall(gl); 14386 NVGVertex* quad; 14387 GLNVGfragUniforms* frag; 14388 int maxverts, offset; 14389 14390 if (call is null) return; 14391 14392 call.type = GLNVG_FILL; 14393 call.evenOdd = evenOdd; 14394 call.clipmode = clipmode; 14395 //if (clipmode != NVGClipMode.None) { import core.stdc.stdio; printf("CLIP!\n"); } 14396 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14397 call.triangleCount = 4; 14398 call.pathOffset = glnvg__allocPaths(gl, npaths); 14399 if (call.pathOffset == -1) goto error; 14400 call.pathCount = npaths; 14401 call.image = paint.image.id; 14402 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14403 14404 if (npaths == 1 && paths[0].convex) { 14405 call.type = GLNVG_CONVEXFILL; 14406 call.triangleCount = 0; // Bounding box fill quad not needed for convex fill 14407 } 14408 14409 // Allocate vertices for all the paths. 14410 maxverts = glnvg__maxVertCount(paths, npaths)+call.triangleCount; 14411 offset = glnvg__allocVerts(gl, maxverts); 14412 if (offset == -1) goto error; 14413 14414 foreach (int i; 0..npaths) { 14415 GLNVGpath* copy = &gl.paths[call.pathOffset+i]; 14416 const(NVGpath)* path = &paths[i]; 14417 memset(copy, 0, GLNVGpath.sizeof); 14418 if (path.nfill > 0) { 14419 copy.fillOffset = offset; 14420 copy.fillCount = path.nfill; 14421 memcpy(&gl.verts[offset], path.fill, NVGVertex.sizeof*path.nfill); 14422 offset += path.nfill; 14423 } 14424 if (path.nstroke > 0) { 14425 copy.strokeOffset = offset; 14426 copy.strokeCount = path.nstroke; 14427 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke); 14428 offset += path.nstroke; 14429 } 14430 } 14431 14432 // Setup uniforms for draw calls 14433 if (call.type == GLNVG_FILL) { 14434 import core.stdc.string : memcpy; 14435 // Quad 14436 call.triangleOffset = offset; 14437 quad = &gl.verts[call.triangleOffset]; 14438 glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f); 14439 glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f); 14440 glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f); 14441 glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f); 14442 // Get uniform 14443 call.uniformOffset = glnvg__allocFragUniforms(gl, 2); 14444 if (call.uniformOffset == -1) goto error; 14445 // Simple shader for stencil 14446 frag = nvg__fragUniformPtr(gl, call.uniformOffset); 14447 memset(frag, 0, (*frag).sizeof); 14448 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f); 14449 memcpy(nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), frag, (*frag).sizeof); 14450 frag.strokeThr = -1.0f; 14451 frag.type = NSVG_SHADER_SIMPLE; 14452 // Fill shader 14453 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, fringe, fringe, -1.0f); 14454 } else { 14455 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14456 if (call.uniformOffset == -1) goto error; 14457 // Fill shader 14458 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f); 14459 } 14460 14461 return; 14462 14463 error: 14464 // We get here if call alloc was ok, but something else is not. 14465 // Roll back the last call to prevent drawing it. 14466 if (gl.ncalls > 0) --gl.ncalls; 14467 } 14468 14469 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 { 14470 if (npaths < 1) return; 14471 14472 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14473 GLNVGcall* call = glnvg__allocCall(gl); 14474 int maxverts, offset; 14475 14476 if (call is null) return; 14477 14478 call.type = GLNVG_STROKE; 14479 call.clipmode = clipmode; 14480 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14481 call.pathOffset = glnvg__allocPaths(gl, npaths); 14482 if (call.pathOffset == -1) goto error; 14483 call.pathCount = npaths; 14484 call.image = paint.image.id; 14485 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14486 14487 // Allocate vertices for all the paths. 14488 maxverts = glnvg__maxVertCount(paths, npaths); 14489 offset = glnvg__allocVerts(gl, maxverts); 14490 if (offset == -1) goto error; 14491 14492 foreach (int i; 0..npaths) { 14493 GLNVGpath* copy = &gl.paths[call.pathOffset+i]; 14494 const(NVGpath)* path = &paths[i]; 14495 memset(copy, 0, GLNVGpath.sizeof); 14496 if (path.nstroke) { 14497 copy.strokeOffset = offset; 14498 copy.strokeCount = path.nstroke; 14499 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke); 14500 offset += path.nstroke; 14501 } 14502 } 14503 14504 if (gl.flags&NVGContextFlag.StencilStrokes) { 14505 // Fill shader 14506 call.uniformOffset = glnvg__allocFragUniforms(gl, 2); 14507 if (call.uniformOffset == -1) goto error; 14508 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); 14509 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f); 14510 } else { 14511 // Fill shader 14512 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14513 if (call.uniformOffset == -1) goto error; 14514 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); 14515 } 14516 14517 return; 14518 14519 error: 14520 // We get here if call alloc was ok, but something else is not. 14521 // Roll back the last call to prevent drawing it. 14522 if (gl.ncalls > 0) --gl.ncalls; 14523 } 14524 14525 void glnvg__renderTriangles (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc { 14526 if (nverts < 1) return; 14527 14528 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14529 GLNVGcall* call = glnvg__allocCall(gl); 14530 GLNVGfragUniforms* frag; 14531 14532 if (call is null) return; 14533 14534 call.type = GLNVG_TRIANGLES; 14535 call.clipmode = clipmode; 14536 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14537 call.image = paint.image.id; 14538 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14539 14540 // Allocate vertices for all the paths. 14541 call.triangleOffset = glnvg__allocVerts(gl, nverts); 14542 if (call.triangleOffset == -1) goto error; 14543 call.triangleCount = nverts; 14544 14545 memcpy(&gl.verts[call.triangleOffset], verts, NVGVertex.sizeof*nverts); 14546 14547 // Fill shader 14548 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14549 if (call.uniformOffset == -1) goto error; 14550 frag = nvg__fragUniformPtr(gl, call.uniformOffset); 14551 glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f); 14552 frag.type = NSVG_SHADER_IMG; 14553 14554 return; 14555 14556 error: 14557 // We get here if call alloc was ok, but something else is not. 14558 // Roll back the last call to prevent drawing it. 14559 if (gl.ncalls > 0) --gl.ncalls; 14560 } 14561 14562 void glnvg__renderDelete (void* uptr) nothrow @trusted @nogc { 14563 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14564 if (gl is null) return; 14565 14566 glnvg__killFBOs(gl); 14567 glnvg__deleteShader(&gl.shader); 14568 glnvg__deleteShader(&gl.shaderFillFBO); 14569 glnvg__deleteShader(&gl.shaderCopyFBO); 14570 14571 if (gl.vertBuf != 0) glDeleteBuffers(1, &gl.vertBuf); 14572 14573 foreach (ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) { 14574 if (tex.id != 0 && (tex.flags&NVGImageFlag.NoDelete) == 0) { 14575 assert(tex.tex != 0); 14576 glDeleteTextures(1, &tex.tex); 14577 } 14578 } 14579 free(gl.textures); 14580 14581 free(gl.paths); 14582 free(gl.verts); 14583 free(gl.uniforms); 14584 free(gl.calls); 14585 14586 free(gl); 14587 } 14588 14589 14590 /** Creates NanoVega contexts for OpenGL2+. 14591 * 14592 * Specify creation flags as additional arguments, like this: 14593 * `nvgCreateContext(NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes);` 14594 * 14595 * If you won't specify any flags, defaults will be used: 14596 * `[NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes]`. 14597 * 14598 * Group: context_management 14599 */ 14600 public NVGContext nvgCreateContext (const(NVGContextFlag)[] flagList...) nothrow @trusted @nogc { 14601 version(aliced) { 14602 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA; 14603 } else { 14604 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes; 14605 } 14606 uint flags = 0; 14607 if (flagList.length != 0) { 14608 foreach (immutable flg; flagList) flags |= (flg != NVGContextFlag.Default ? flg : DefaultFlags); 14609 } else { 14610 flags = DefaultFlags; 14611 } 14612 NVGparams params = void; 14613 NVGContext ctx = null; 14614 version(nanovg_builtin_opengl_bindings) nanovgInitOpenGL(); // why not? 14615 version(nanovg_bindbc_opengl_bindings) nanovgInitOpenGL(); 14616 GLNVGcontext* gl = cast(GLNVGcontext*)malloc(GLNVGcontext.sizeof); 14617 if (gl is null) goto error; 14618 memset(gl, 0, GLNVGcontext.sizeof); 14619 14620 memset(¶ms, 0, params.sizeof); 14621 params.renderCreate = &glnvg__renderCreate; 14622 params.renderCreateTexture = &glnvg__renderCreateTexture; 14623 params.renderTextureIncRef = &glnvg__renderTextureIncRef; 14624 params.renderDeleteTexture = &glnvg__renderDeleteTexture; 14625 params.renderUpdateTexture = &glnvg__renderUpdateTexture; 14626 params.renderGetTextureSize = &glnvg__renderGetTextureSize; 14627 params.renderViewport = &glnvg__renderViewport; 14628 params.renderCancel = &glnvg__renderCancel; 14629 params.renderFlush = &glnvg__renderFlush; 14630 params.renderPushClip = &glnvg__renderPushClip; 14631 params.renderPopClip = &glnvg__renderPopClip; 14632 params.renderResetClip = &glnvg__renderResetClip; 14633 params.renderFill = &glnvg__renderFill; 14634 params.renderStroke = &glnvg__renderStroke; 14635 params.renderTriangles = &glnvg__renderTriangles; 14636 params.renderSetAffine = &glnvg__renderSetAffine; 14637 params.renderDelete = &glnvg__renderDelete; 14638 params.userPtr = gl; 14639 params.edgeAntiAlias = (flags&NVGContextFlag.Antialias ? true : false); 14640 if (flags&(NVGContextFlag.FontAA|NVGContextFlag.FontNoAA)) { 14641 params.fontAA = (flags&NVGContextFlag.FontNoAA ? NVG_INVERT_FONT_AA : !NVG_INVERT_FONT_AA); 14642 } else { 14643 params.fontAA = NVG_INVERT_FONT_AA; 14644 } 14645 14646 gl.flags = flags; 14647 gl.freetexid = -1; 14648 14649 ctx = createInternal(¶ms); 14650 if (ctx is null) goto error; 14651 14652 static if (__VERSION__ < 2076) { 14653 DGNoThrowNoGC(() { import core.thread; gl.mainTID = Thread.getThis.id; })(); 14654 } else { 14655 try { import core.thread; gl.mainTID = Thread.getThis.id; } catch (Exception e) {} 14656 } 14657 14658 return ctx; 14659 14660 error: 14661 // 'gl' is freed by nvgDeleteInternal. 14662 if (ctx !is null) ctx.deleteInternal(); 14663 return null; 14664 } 14665 14666 /// Create NanoVega OpenGL image from texture id. 14667 /// Group: images 14668 public int glCreateImageFromHandleGL2 (NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc { 14669 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr; 14670 GLNVGtexture* tex = glnvg__allocTexture(gl); 14671 14672 if (tex is null) return 0; 14673 14674 tex.type = NVGtexture.RGBA; 14675 tex.tex = textureId; 14676 tex.flags = imageFlags; 14677 tex.width = w; 14678 tex.height = h; 14679 14680 return tex.id; 14681 } 14682 14683 /// Returns OpenGL texture id for NanoVega image. 14684 /// Group: images 14685 public GLuint glImageHandleGL2 (NVGContext ctx, int image) nothrow @trusted @nogc { 14686 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr; 14687 GLNVGtexture* tex = glnvg__findTexture(gl, image); 14688 return tex.tex; 14689 } 14690 14691 14692 // ////////////////////////////////////////////////////////////////////////// // 14693 private: 14694 14695 static if (NanoVegaHasFontConfig) { 14696 version(nanovg_builtin_fontconfig_bindings) { 14697 pragma(lib, "fontconfig"); 14698 14699 private extern(C) nothrow @trusted @nogc { 14700 enum FC_FILE = "file"; /* String */ 14701 alias FcBool = int; 14702 alias FcChar8 = char; 14703 struct FcConfig; 14704 struct FcPattern; 14705 alias FcMatchKind = int; 14706 enum : FcMatchKind { 14707 FcMatchPattern, 14708 FcMatchFont, 14709 FcMatchScan 14710 } 14711 alias FcResult = int; 14712 enum : FcResult { 14713 FcResultMatch, 14714 FcResultNoMatch, 14715 FcResultTypeMismatch, 14716 FcResultNoId, 14717 FcResultOutOfMemory 14718 } 14719 FcBool FcInit (); 14720 FcBool FcConfigSubstituteWithPat (FcConfig* config, FcPattern* p, FcPattern* p_pat, FcMatchKind kind); 14721 void FcDefaultSubstitute (FcPattern* pattern); 14722 FcBool FcConfigSubstitute (FcConfig* config, FcPattern* p, FcMatchKind kind); 14723 FcPattern* FcFontMatch (FcConfig* config, FcPattern* p, FcResult* result); 14724 FcPattern* FcNameParse (const(FcChar8)* name); 14725 void FcPatternDestroy (FcPattern* p); 14726 FcResult FcPatternGetString (const(FcPattern)* p, const(char)* object, int n, FcChar8** s); 14727 } 14728 } 14729 14730 __gshared bool fontconfigAvailable = false; 14731 // initialize fontconfig 14732 shared static this () { 14733 if (FcInit()) { 14734 fontconfigAvailable = true; 14735 } else { 14736 import core.stdc.stdio : stderr, fprintf; 14737 stderr.fprintf("***NanoVega WARNING: cannot init fontconfig!\n"); 14738 } 14739 } 14740 } 14741 14742 14743 // ////////////////////////////////////////////////////////////////////////// // 14744 public enum BaphometDims = 512.0f; // baphomet icon is 512x512 ([0..511]) 14745 14746 private static immutable ubyte[7641] baphometPath = [ 14747 0x01,0x04,0x06,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x08,0xa0,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43, 14748 0x00,0x80,0xff,0x43,0xa2,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x08,0x00,0x80,0xff, 14749 0x43,0x7a,0x89,0xe5,0x42,0xa0,0x1d,0xc6,0x43,0x00,0x00,0x00,0x00,0x30,0x89,0x7f,0x43,0x00,0x00,0x00, 14750 0x00,0x08,0x7a,0x89,0xe5,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7a,0x89,0xe5,0x42,0x00,0x00, 14751 0x00,0x00,0x30,0x89,0x7f,0x43,0x08,0x00,0x00,0x00,0x00,0xa2,0x1d,0xc6,0x43,0x7a,0x89,0xe5,0x42,0x00, 14752 0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x09,0x06,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd, 14753 0x43,0x08,0x16,0x68,0xb3,0x43,0x72,0x87,0xdd,0x43,0x71,0x87,0xdd,0x43,0x17,0x68,0xb3,0x43,0x71,0x87, 14754 0xdd,0x43,0x30,0x89,0x7f,0x43,0x08,0x71,0x87,0xdd,0x43,0xd2,0x2f,0x18,0x43,0x16,0x68,0xb3,0x43,0x35, 14755 0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x35,0xe2,0x87,0x42,0x08,0xd1,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42, 14756 0x35,0xe2,0x87,0x42,0xd2,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x08,0x35,0xe2,0x87, 14757 0x42,0x17,0x68,0xb3,0x43,0xd1,0x2f,0x18,0x43,0x72,0x87,0xdd,0x43,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd, 14758 0x43,0x09,0x06,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x07,0xa4,0x3f,0x7f,0x43,0x0b,0x86,0xdc,0x43, 14759 0x07,0x6c,0xb9,0xb2,0x43,0xe8,0xd1,0xca,0x42,0x07,0x6e,0x4d,0xa0,0x42,0xa9,0x10,0x9c,0x43,0x07,0xb7, 14760 0x40,0xd7,0x43,0xa9,0x10,0x9c,0x43,0x07,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x09,0x06,0x98,0x42, 14761 0x74,0x43,0xb1,0x8d,0x68,0x43,0x08,0xd7,0x24,0x79,0x43,0xba,0x83,0x6e,0x43,0xa9,0x16,0x7c,0x43,0x56, 14762 0xa1,0x76,0x43,0x74,0x2a,0x7d,0x43,0x44,0x73,0x80,0x43,0x08,0x55,0xd1,0x7e,0x43,0xe3,0xea,0x76,0x43, 14763 0xbc,0x18,0x81,0x43,0x7f,0xa8,0x6e,0x43,0x8f,0x0a,0x84,0x43,0x02,0xfc,0x68,0x43,0x09,0x06,0x92,0x29, 14764 0x8d,0x43,0x73,0xc3,0x67,0x43,0x08,0xa4,0xd9,0x8e,0x43,0xf2,0xa6,0x7a,0x43,0x8f,0x22,0x88,0x43,0x75, 14765 0x2a,0x7d,0x43,0x42,0x7f,0x82,0x43,0x08,0xc8,0x88,0x43,0x09,0x06,0xc1,0x79,0x74,0x43,0x50,0x64,0x89, 14766 0x43,0x08,0x68,0x2d,0x72,0x43,0xee,0x21,0x81,0x43,0xcd,0x97,0x55,0x43,0xe6,0xf1,0x7b,0x43,0x91,0xec, 14767 0x5d,0x43,0xa8,0xc7,0x6a,0x43,0x09,0x06,0xfa,0xa5,0x52,0x43,0x60,0x97,0x7c,0x43,0x08,0x19,0xff,0x50, 14768 0x43,0xe9,0x6e,0x8a,0x43,0xb0,0xbd,0x70,0x43,0x4c,0x51,0x82,0x43,0x04,0xeb,0x69,0x43,0x66,0x0f,0x8e, 14769 0x43,0x09,0x06,0x17,0xbf,0x71,0x43,0x2c,0x58,0x94,0x43,0x08,0x1c,0x96,0x6e,0x43,0x61,0x68,0x99,0x43, 14770 0x2d,0x3a,0x6e,0x43,0xc8,0x81,0x9e,0x43,0xb7,0x9b,0x72,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0x30,0xdb, 14771 0x82,0x43,0xdb,0xe9,0x93,0x43,0x08,0x11,0x82,0x84,0x43,0x61,0x68,0x99,0x43,0xe8,0x4a,0x84,0x43,0x8e, 14772 0xa6,0x9e,0x43,0x42,0x7f,0x82,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0xc4,0x02,0x85,0x43,0xd1,0x0b,0x92, 14773 0x43,0x08,0xd6,0xb2,0x86,0x43,0x34,0x1e,0x92,0x43,0x4f,0x58,0x87,0x43,0xa4,0xf1,0x92,0x43,0x03,0xd9, 14774 0x87,0x43,0x7b,0xc6,0x94,0x43,0x09,0x06,0x87,0x3e,0x64,0x43,0x31,0x3b,0x93,0x43,0x08,0x3b,0xbf,0x64, 14775 0x43,0x6f,0xf9,0x91,0x43,0x96,0x0b,0x67,0x43,0xc5,0x4a,0x91,0x43,0xcf,0xfe,0x6a,0x43,0x31,0x2f,0x91, 14776 0x43,0x09,0x06,0x16,0x74,0xb5,0x43,0x08,0xec,0x8e,0x43,0x08,0x1b,0x4b,0xb2,0x43,0xee,0x5d,0x8b,0x43, 14777 0x48,0x4d,0xad,0x43,0x12,0xa6,0x8a,0x43,0xf3,0xd7,0xa7,0x43,0x74,0xb8,0x8a,0x43,0x08,0x8c,0xb2,0xa0, 14778 0x43,0xcd,0xf8,0x8a,0x43,0x68,0x46,0x9b,0x43,0x79,0x8f,0x87,0x43,0x49,0xc9,0x96,0x43,0xe9,0x3e,0x82, 14779 0x43,0x08,0x60,0x5c,0x97,0x43,0xa1,0xde,0x8b,0x43,0x4e,0xa0,0x93,0x43,0x31,0x3b,0x93,0x43,0x9f,0xea, 14780 0x8d,0x43,0x27,0x8d,0x99,0x43,0x08,0x07,0xe0,0x8c,0x43,0x06,0x34,0x9b,0x43,0x38,0xe9,0x8c,0x43,0x46, 14781 0x0a,0x9e,0x43,0x3d,0xcc,0x8b,0x43,0xb2,0x06,0xa2,0x43,0x08,0xf1,0x40,0x8a,0x43,0xb0,0x12,0xa4,0x43, 14782 0x39,0xd1,0x88,0x43,0x76,0x43,0xa6,0x43,0xfa,0x06,0x88,0x43,0xa4,0x75,0xa9,0x43,0x08,0x19,0x6c,0x88, 14783 0x43,0x9f,0x9e,0xac,0x43,0x66,0xeb,0x87,0x43,0x44,0x76,0xb0,0x43,0x6b,0xce,0x86,0x43,0x3b,0xbc,0xb4, 14784 0x43,0x08,0xa9,0x8c,0x85,0x43,0x06,0xd0,0xb5,0x43,0xfa,0xee,0x83,0x43,0x74,0xa3,0xb6,0x43,0x3d,0x90, 14785 0x81,0x43,0x31,0xf6,0xb6,0x43,0x08,0x9d,0x61,0x7d,0x43,0xee,0x48,0xb7,0x43,0x3b,0x1f,0x75,0x43,0xcf, 14786 0xe3,0xb6,0x43,0xee,0x6f,0x6d,0x43,0x68,0xe2,0xb5,0x43,0x08,0xd4,0xed,0x6b,0x43,0x87,0x2f,0xb2,0x43, 14787 0x0e,0xc9,0x6b,0x43,0xa7,0x7c,0xae,0x43,0x98,0xfa,0x67,0x43,0xab,0x53,0xab,0x43,0x08,0x25,0x2c,0x64, 14788 0x43,0x33,0xa2,0xa8,0x43,0x40,0x96,0x61,0x43,0xc3,0xc2,0xa5,0x43,0x64,0xde,0x60,0x43,0xfa,0xa2,0xa2, 14789 0x43,0x08,0xb0,0x5d,0x60,0x43,0x06,0x4c,0x9f,0x43,0x9a,0xca,0x5f,0x43,0x38,0x3d,0x9b,0x43,0x3b,0x8f, 14790 0x5c,0x43,0x85,0xb0,0x98,0x43,0x08,0x42,0x36,0x51,0x43,0x3d,0xf0,0x91,0x43,0xcd,0x4f,0x49,0x43,0xdb, 14791 0xb9,0x8b,0x43,0xe0,0xdb,0x44,0x43,0x42,0x8b,0x84,0x43,0x08,0x7e,0xc9,0x44,0x43,0x8a,0x57,0x8d,0x43, 14792 0xbc,0x6c,0x0f,0x43,0x23,0x62,0x8e,0x43,0xf5,0x17,0x07,0x43,0xc5,0x3e,0x8f,0x43,0x09,0x06,0xe0,0xea, 14793 0x76,0x43,0xab,0xef,0xc5,0x43,0x08,0x12,0x00,0x79,0x43,0xab,0xcb,0xbf,0x43,0x79,0xb9,0x6d,0x43,0x7e, 14794 0x8d,0xba,0x43,0xee,0x6f,0x6d,0x43,0x98,0xeb,0xb5,0x43,0x08,0xe0,0x02,0x7b,0x43,0x5f,0x1c,0xb8,0x43, 14795 0x85,0x2c,0x82,0x43,0xe9,0x65,0xb8,0x43,0xd6,0xb2,0x86,0x43,0xc6,0x05,0xb5,0x43,0x08,0x03,0xcd,0x85, 14796 0x43,0x5a,0x39,0xb9,0x43,0xe4,0x4f,0x81,0x43,0xdb,0xd4,0xbf,0x43,0xdf,0x6c,0x82,0x43,0xbc,0x93,0xc5, 14797 0x43,0x09,0x06,0xf0,0xd0,0x22,0x43,0x5d,0x19,0x08,0x43,0x08,0xbc,0xab,0x49,0x43,0x4a,0x35,0x29,0x43, 14798 0xcb,0xf7,0x65,0x43,0xce,0x37,0x45,0x43,0x0e,0x99,0x63,0x43,0x67,0xc6,0x5c,0x43,0x09,0x06,0x05,0x94, 14799 0xab,0x43,0xc2,0x13,0x04,0x43,0x08,0x9f,0x26,0x98,0x43,0x11,0x42,0x25,0x43,0x97,0x00,0x8a,0x43,0x32, 14800 0x32,0x41,0x43,0xf5,0x2f,0x8b,0x43,0xc7,0xc0,0x58,0x43,0x09,0x06,0x8f,0x85,0x48,0x43,0xe0,0xa8,0x8c, 14801 0x43,0x08,0x55,0xaa,0x48,0x43,0xe0,0xa8,0x8c,0x43,0x6b,0x3d,0x49,0x43,0xc1,0x43,0x8c,0x43,0x31,0x62, 14802 0x49,0x43,0xc1,0x43,0x8c,0x43,0x08,0x2f,0xe3,0x2f,0x43,0xad,0xe7,0x98,0x43,0xff,0x0d,0x0d,0x43,0xad, 14803 0xf3,0x9a,0x43,0xf0,0xaf,0xcc,0x42,0x74,0x00,0x97,0x43,0x08,0xbb,0xa2,0xf7,0x42,0x93,0x4d,0x93,0x43, 14804 0x5e,0x19,0x08,0x43,0x5a,0x2a,0x87,0x43,0x23,0x6e,0x10,0x43,0x42,0x97,0x86,0x43,0x08,0xca,0xe8,0x33, 14805 0x43,0x1b,0x3c,0x80,0x43,0x80,0xe8,0x4d,0x43,0xda,0xf4,0x70,0x43,0xae,0x0e,0x4f,0x43,0x2b,0x1b,0x65, 14806 0x43,0x08,0x66,0x96,0x54,0x43,0xa3,0xe1,0x3b,0x43,0x4e,0xc4,0x19,0x43,0xa0,0x1a,0x16,0x43,0x10,0xe2, 14807 0x14,0x43,0x26,0x14,0xe0,0x42,0x08,0x5c,0x91,0x1c,0x43,0xcb,0x27,0xee,0x42,0xa9,0x40,0x24,0x43,0x71, 14808 0x3b,0xfc,0x42,0xf3,0xef,0x2b,0x43,0x8b,0x27,0x05,0x43,0x08,0xe2,0x4b,0x2c,0x43,0x48,0x86,0x07,0x43, 14809 0x79,0x62,0x2f,0x43,0x05,0xe5,0x09,0x43,0x55,0x32,0x34,0x43,0xa0,0xd2,0x09,0x43,0x08,0x74,0xa3,0x36, 14810 0x43,0x3a,0xd1,0x08,0x43,0x7e,0x81,0x38,0x43,0x09,0xd4,0x0a,0x43,0x0d,0xba,0x39,0x43,0xa0,0xea,0x0d, 14811 0x43,0x08,0x6f,0xe4,0x3d,0x43,0x43,0xc7,0x0e,0x43,0xd6,0xe5,0x3e,0x43,0xc4,0x4a,0x11,0x43,0x55,0x7a, 14812 0x40,0x43,0x59,0x72,0x13,0x43,0x08,0x55,0x92,0x44,0x43,0xbf,0x73,0x14,0x43,0x23,0x95,0x46,0x43,0xa5, 14813 0x09,0x17,0x43,0xe0,0xf3,0x48,0x43,0xfe,0x55,0x19,0x43,0x08,0xcd,0x4f,0x49,0x43,0xaa,0x10,0x1c,0x43, 14814 0x61,0x77,0x4b,0x43,0xfe,0x6d,0x1d,0x43,0x80,0xe8,0x4d,0x43,0x2b,0x94,0x1e,0x43,0x08,0x58,0xc9,0x51, 14815 0x43,0x41,0x27,0x1f,0x43,0x9b,0x82,0x53,0x43,0x35,0x72,0x20,0x43,0x53,0xf2,0x54,0x43,0x88,0xcf,0x21, 14816 0x43,0x08,0x7b,0x29,0x55,0x43,0xe8,0x0a,0x25,0x43,0xb2,0x2d,0x58,0x43,0xef,0xe8,0x26,0x43,0x9b,0xb2, 14817 0x5b,0x43,0xd0,0x8f,0x28,0x43,0x08,0x5f,0xef,0x5f,0x43,0xeb,0x11,0x2a,0x43,0xfd,0xdc,0x5f,0x43,0x6e, 14818 0x95,0x2c,0x43,0x3b,0xa7,0x60,0x43,0x2b,0xf4,0x2e,0x43,0x08,0x06,0xbb,0x61,0x43,0xfd,0xe5,0x31,0x43, 14819 0xe7,0x61,0x63,0x43,0xef,0x30,0x33,0x43,0x53,0x52,0x65,0x43,0xa3,0xb1,0x33,0x43,0x08,0x12,0xa0,0x68, 14820 0x43,0x7f,0x69,0x34,0x43,0x40,0xc6,0x69,0x43,0x64,0xff,0x36,0x43,0x7e,0x90,0x6a,0x43,0x71,0xcc,0x39, 14821 0x43,0x08,0xbc,0x5a,0x6b,0x43,0x51,0x73,0x3b,0x43,0xc1,0x49,0x6c,0x43,0xa5,0xd0,0x3c,0x43,0xe0,0xba, 14822 0x6e,0x43,0xb8,0x74,0x3c,0x43,0x08,0x6b,0x1c,0x73,0x43,0x13,0xc1,0x3e,0x43,0x40,0xf6,0x71,0x43,0xce, 14823 0x1f,0x41,0x43,0x55,0x89,0x72,0x43,0x8d,0x7e,0x43,0x43,0x08,0x68,0x2d,0x72,0x43,0x89,0xae,0x4b,0x43, 14824 0xc1,0x79,0x74,0x43,0xcb,0x78,0x4c,0x43,0x55,0xa1,0x76,0x43,0x5b,0xb1,0x4d,0x43,0x08,0xa2,0x38,0x7a, 14825 0x43,0xd1,0x56,0x4e,0x43,0x85,0xb6,0x78,0x43,0xb1,0x15,0x54,0x43,0x83,0xc7,0x77,0x43,0x89,0x0e,0x5c, 14826 0x43,0x08,0xcf,0x46,0x77,0x43,0x0f,0x81,0x5f,0x43,0x1a,0xde,0x7a,0x43,0xce,0xc7,0x5d,0x43,0x42,0x73, 14827 0x80,0x43,0x99,0xc3,0x5a,0x43,0x08,0x85,0x2c,0x82,0x43,0xf6,0xe6,0x59,0x43,0x81,0x3d,0x81,0x43,0x16, 14828 0x10,0x50,0x43,0xd6,0x8e,0x80,0x43,0x5b,0x99,0x49,0x43,0x08,0xc4,0xea,0x80,0x43,0x22,0x95,0x46,0x43, 14829 0xfa,0xe2,0x81,0x43,0xda,0xec,0x43,0x43,0x78,0x77,0x83,0x43,0xe4,0xb2,0x41,0x43,0x08,0x8a,0x27,0x85, 14830 0x43,0x86,0x77,0x3e,0x43,0x0c,0x9f,0x85,0x43,0x07,0xf4,0x3b,0x43,0x8f,0x16,0x86,0x43,0xe6,0x82,0x39, 14831 0x43,0x08,0x85,0x44,0x86,0x43,0x37,0xd9,0x35,0x43,0x1e,0x4f,0x87,0x43,0xe1,0x7b,0x34,0x43,0xdf,0x90, 14832 0x88,0x43,0xb6,0x55,0x33,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0xe5,0x31,0x43,0xfa,0x12,0x8a,0x43,0xbf, 14833 0x03,0x2d,0x43,0x19,0x78,0x8a,0x43,0x45,0x5e,0x2c,0x43,0x08,0x03,0xf1,0x8b,0x43,0xac,0x47,0x29,0x43, 14834 0x2f,0x17,0x8d,0x43,0x45,0x46,0x28,0x43,0xc8,0x21,0x8e,0x43,0x30,0xb3,0x27,0x43,0x08,0xa9,0xc8,0x8f, 14835 0x43,0xef,0xe8,0x26,0x43,0xbf,0x5b,0x90,0x43,0x5b,0xc1,0x24,0x43,0x10,0xca,0x90,0x43,0xa0,0x62,0x22, 14836 0x43,0x08,0x26,0x5d,0x91,0x43,0xbb,0xcc,0x1f,0x43,0xf0,0x70,0x92,0x43,0x78,0x13,0x1e,0x43,0x77,0xd7, 14837 0x93,0x43,0x73,0x24,0x1d,0x43,0x08,0x65,0x3f,0x96,0x43,0xce,0x58,0x1b,0x43,0xbe,0x7f,0x96,0x43,0xbf, 14838 0x8b,0x18,0x43,0x60,0x5c,0x97,0x43,0xb6,0xad,0x16,0x43,0x08,0xba,0xa8,0x99,0x43,0x78,0xcb,0x11,0x43, 14839 0x49,0xe1,0x9a,0x43,0x78,0xcb,0x11,0x43,0x01,0x51,0x9c,0x43,0x73,0xdc,0x10,0x43,0x08,0x72,0x24,0x9d, 14840 0x43,0xd2,0xff,0x0f,0x43,0x1c,0xd3,0x9d,0x43,0x07,0xec,0x0e,0x43,0xeb,0xc9,0x9d,0x43,0xe8,0x7a,0x0c, 14841 0x43,0x08,0x60,0x80,0x9d,0x43,0xd7,0xbe,0x08,0x43,0x4d,0xe8,0x9f,0x43,0x86,0x50,0x08,0x43,0x25,0xbd, 14842 0xa1,0x43,0x5b,0x2a,0x07,0x43,0x08,0x99,0x7f,0xa3,0x43,0xc9,0xf1,0x05,0x43,0x48,0x1d,0xa5,0x43,0x86, 14843 0x38,0x04,0x43,0x6c,0x71,0xa6,0x43,0x18,0x59,0x01,0x43,0x08,0x32,0x96,0xa6,0x43,0x6e,0x64,0xff,0x42, 14844 0x48,0x29,0xa7,0x43,0xed,0xcf,0xfd,0x42,0x5f,0xbc,0xa7,0x43,0x71,0x3b,0xfc,0x42,0x08,0xf3,0xe3,0xa9, 14845 0x43,0xf7,0x7d,0xf7,0x42,0xd8,0x6d,0xaa,0x43,0x45,0xe5,0xf2,0x42,0x48,0x41,0xab,0x43,0xcb,0x27,0xee, 14846 0x42,0x08,0x24,0xf9,0xab,0x43,0x52,0x6a,0xe9,0x42,0xee,0x0c,0xad,0x43,0x4c,0x8c,0xe7,0x42,0x1b,0x33, 14847 0xae,0x43,0xcc,0xf7,0xe5,0x42,0x08,0xaa,0x6b,0xaf,0x43,0xe8,0x61,0xe3,0x42,0x90,0xf5,0xaf,0x43,0xc9, 14848 0xf0,0xe0,0x42,0xe0,0x63,0xb0,0x43,0xe5,0x5a,0xde,0x42,0x08,0xaa,0x83,0xb3,0x43,0x29,0x2d,0x09,0x43, 14849 0x6a,0xfe,0x8e,0x43,0xb8,0x74,0x3c,0x43,0xd5,0x06,0x95,0x43,0xe6,0x79,0x67,0x43,0x08,0x2f,0x53,0x97, 14850 0x43,0xe9,0xb0,0x74,0x43,0xa8,0x28,0xa0,0x43,0x43,0xfd,0x76,0x43,0x83,0x28,0xad,0x43,0x17,0x59,0x81, 14851 0x43,0x08,0x3d,0xe7,0xbf,0x43,0x4b,0x8d,0x8c,0x43,0xae,0x96,0xba,0x43,0x66,0x27,0x92,0x43,0x15,0xe0, 14852 0xc7,0x43,0x6f,0x11,0x96,0x43,0x08,0x7e,0x5d,0xb2,0x43,0xdb,0x01,0x98,0x43,0x9e,0x56,0xa0,0x43,0x80, 14853 0xc1,0x97,0x43,0x69,0x2e,0x97,0x43,0x31,0x17,0x8d,0x43,0x09,0x06,0xab,0xa7,0x39,0x43,0x67,0x0f,0x0e, 14854 0x43,0x08,0xdb,0xbc,0x3b,0x43,0xe8,0x92,0x10,0x43,0xb5,0x85,0x3b,0x43,0x97,0x3c,0x14,0x43,0xab,0xa7, 14855 0x39,0x43,0x0c,0x0b,0x18,0x43,0x09,0x06,0xca,0x30,0x40,0x43,0x30,0x3b,0x13,0x43,0x08,0x17,0xc8,0x43, 14856 0x43,0xa5,0x09,0x17,0x43,0x7e,0xc9,0x44,0x43,0x1a,0xd8,0x1a,0x43,0x9d,0x22,0x43,0x43,0x8d,0xa6,0x1e, 14857 0x43,0x09,0x06,0xc8,0x78,0x4c,0x43,0xed,0xc9,0x1d,0x43,0x08,0x0b,0x32,0x4e,0x43,0x22,0xce,0x20,0x43, 14858 0x23,0xc5,0x4e,0x43,0x58,0xd2,0x23,0x43,0x0b,0x32,0x4e,0x43,0x2b,0xc4,0x26,0x43,0x09,0x06,0xec,0x08, 14859 0x58,0x43,0xc7,0xb1,0x26,0x43,0x08,0x02,0x9c,0x58,0x43,0xef,0x00,0x2b,0x43,0xd9,0x64,0x58,0x43,0x02, 14860 0xbd,0x2e,0x43,0x10,0x51,0x57,0x43,0x37,0xc1,0x31,0x43,0x09,0x06,0xcb,0xdf,0x61,0x43,0x4a,0x65,0x31, 14861 0x43,0x08,0xbe,0x2a,0x63,0x43,0xbd,0x33,0x35,0x43,0x32,0xe1,0x62,0x43,0x56,0x4a,0x38,0x43,0xde,0x83, 14862 0x61,0x43,0x3c,0xe0,0x3a,0x43,0x09,0x06,0x1c,0x7e,0x6a,0x43,0x5b,0x39,0x39,0x43,0x08,0x31,0x11,0x6b, 14863 0x43,0x0c,0xd2,0x3d,0x43,0x1c,0x7e,0x6a,0x43,0x13,0xd9,0x42,0x43,0xd9,0xc4,0x68,0x43,0xcb,0x60,0x48, 14864 0x43,0x09,0x06,0xe5,0xc1,0x73,0x43,0x16,0xf8,0x4b,0x43,0x08,0xa6,0xf7,0x72,0x43,0xb1,0xfd,0x4f,0x43, 14865 0x3b,0x07,0x71,0x43,0x4a,0x14,0x53,0x43,0xa2,0xf0,0x6d,0x43,0x7c,0x29,0x55,0x43,0x09,0x06,0x00,0x8d, 14866 0xa6,0x43,0xef,0x21,0x01,0x43,0x08,0x52,0xfb,0xa6,0x43,0xce,0xc8,0x02,0x43,0xe6,0x16,0xa7,0x43,0x51, 14867 0x4c,0x05,0x43,0x3b,0x68,0xa6,0x43,0x4c,0x75,0x08,0x43,0x09,0x06,0xde,0x20,0xa1,0x43,0x86,0x50,0x08, 14868 0x43,0x08,0xd4,0x4e,0xa1,0x43,0xd3,0xe7,0x0b,0x43,0xb5,0xe9,0xa0,0x43,0x59,0x5a,0x0f,0x43,0xba,0xcc, 14869 0x9f,0x43,0x54,0x83,0x12,0x43,0x09,0x06,0x77,0xfb,0x99,0x43,0x6c,0x16,0x13,0x43,0x08,0xde,0xfc,0x9a, 14870 0x43,0x4a,0xbd,0x14,0x43,0x06,0x34,0x9b,0x43,0xfe,0x55,0x19,0x43,0x13,0xe9,0x99,0x43,0x41,0x27,0x1f, 14871 0x43,0x09,0x06,0x46,0xce,0x93,0x43,0x26,0xa5,0x1d,0x43,0x08,0xe7,0xaa,0x94,0x43,0xbb,0xcc,0x1f,0x43, 14872 0x18,0xb4,0x94,0x43,0xa8,0x40,0x24,0x43,0xe2,0xbb,0x93,0x43,0x21,0xfe,0x28,0x43,0x09,0x06,0xb1,0x8e, 14873 0x8d,0x43,0xa8,0x58,0x28,0x43,0x08,0x19,0x90,0x8e,0x43,0x54,0x13,0x2b,0x43,0xa4,0xd9,0x8e,0x43,0x84, 14874 0x40,0x31,0x43,0x46,0xaa,0x8d,0x43,0x29,0x24,0x37,0x43,0x09,0x06,0xd6,0xbe,0x88,0x43,0xef,0x30,0x33, 14875 0x43,0x08,0x0c,0xb7,0x89,0x43,0x0e,0xa2,0x35,0x43,0xc0,0x37,0x8a,0x43,0x7a,0xaa,0x3b,0x43,0xbb,0x48, 14876 0x89,0x43,0xbb,0x7b,0x41,0x43,0x09,0x06,0x3a,0xad,0x82,0x43,0xc4,0x59,0x43,0x43,0x08,0xd2,0xb7,0x83, 14877 0x43,0x2b,0x5b,0x44,0x43,0x35,0xd6,0x85,0x43,0x48,0xf5,0x49,0x43,0x42,0x97,0x86,0x43,0xc4,0xa1,0x4f, 14878 0x43,0x09,0x06,0x9c,0xb3,0x80,0x43,0x48,0x55,0x5a,0x43,0x08,0xff,0xc5,0x80,0x43,0x09,0x73,0x55,0x43, 14879 0x93,0xe1,0x80,0x43,0x0f,0x39,0x53,0x43,0xf1,0xbe,0x7e,0x43,0x18,0xe7,0x4c,0x43,0x09,0x06,0xe0,0x02, 14880 0x7b,0x43,0x92,0xec,0x5d,0x43,0x08,0x09,0x3a,0x7b,0x43,0xf0,0xf7,0x58,0x43,0x09,0x3a,0x7b,0x43,0xe6, 14881 0x31,0x5b,0x43,0xe0,0x02,0x7b,0x43,0xa8,0x4f,0x56,0x43,0x09,0x06,0x39,0x4f,0x7d,0x43,0x3e,0x8f,0x5c, 14882 0x43,0x08,0xe9,0xe0,0x7c,0x43,0x03,0x9c,0x58,0x43,0x1e,0x2b,0x81,0x43,0x7f,0x30,0x5a,0x43,0xff,0x73, 14883 0x7d,0x43,0xf6,0xb6,0x51,0x43,0x09,0x06,0x5c,0xb8,0x52,0x43,0x28,0x21,0x87,0x43,0x08,0xae,0x3e,0x57, 14884 0x43,0x12,0x9a,0x88,0x43,0x23,0xf5,0x56,0x43,0x04,0xf1,0x8b,0x43,0x25,0xfc,0x5b,0x43,0x85,0x74,0x8e, 14885 0x43,0x08,0x2f,0xf2,0x61,0x43,0x8e,0x52,0x90,0x43,0xd9,0xdc,0x6c,0x43,0x85,0x74,0x8e,0x43,0xc6,0x20, 14886 0x69,0x43,0x3d,0xd8,0x8d,0x43,0x08,0x6d,0x8c,0x5a,0x43,0xf5,0x3b,0x8d,0x43,0x3d,0x77,0x58,0x43,0xa1, 14887 0xc6,0x87,0x43,0xf8,0xed,0x5e,0x43,0x5e,0x0d,0x86,0x43,0x09,0x06,0xde,0xcc,0x92,0x43,0xf7,0x17,0x87, 14888 0x43,0x08,0xb6,0x89,0x90,0x43,0xae,0x87,0x88,0x43,0x4a,0xa5,0x90,0x43,0xa1,0xde,0x8b,0x43,0xf9,0x2a, 14889 0x8e,0x43,0x23,0x62,0x8e,0x43,0x08,0xf5,0x2f,0x8b,0x43,0x5c,0x49,0x90,0x43,0x35,0xd6,0x85,0x43,0x8e, 14890 0x46,0x8e,0x43,0x3d,0xb4,0x87,0x43,0x47,0xaa,0x8d,0x43,0x08,0x6a,0xfe,0x8e,0x43,0xff,0x0d,0x8d,0x43, 14891 0xbb,0x6c,0x8f,0x43,0xf7,0x17,0x87,0x43,0x5c,0x31,0x8c,0x43,0xb2,0x5e,0x85,0x43,0x09,0x06,0x60,0x38, 14892 0x91,0x43,0x69,0x5d,0x7a,0x43,0x08,0x34,0x1e,0x92,0x43,0x1e,0x5b,0x89,0x43,0x04,0x63,0x7e,0x43,0x5e, 14893 0x01,0x84,0x43,0x59,0x2a,0x87,0x43,0x0d,0xcf,0x8d,0x43,0x09,0x03,0x04,0x06,0x5a,0x18,0x63,0x43,0x82, 14894 0x79,0x8b,0x43,0x08,0x25,0x2c,0x64,0x43,0x82,0x79,0x8b,0x43,0x2a,0x1b,0x65,0x43,0x9d,0xef,0x8a,0x43, 14895 0x2a,0x1b,0x65,0x43,0xc1,0x37,0x8a,0x43,0x08,0x2a,0x1b,0x65,0x43,0x17,0x89,0x89,0x43,0x25,0x2c,0x64, 14896 0x43,0x31,0xff,0x88,0x43,0x5a,0x18,0x63,0x43,0x31,0xff,0x88,0x43,0x08,0xf3,0x16,0x62,0x43,0x31,0xff, 14897 0x88,0x43,0xee,0x27,0x61,0x43,0x17,0x89,0x89,0x43,0xee,0x27,0x61,0x43,0xc1,0x37,0x8a,0x43,0x08,0xee, 14898 0x27,0x61,0x43,0x9d,0xef,0x8a,0x43,0xf3,0x16,0x62,0x43,0x82,0x79,0x8b,0x43,0x5a,0x18,0x63,0x43,0x82, 14899 0x79,0x8b,0x43,0x09,0x06,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x08,0x34,0xee,0x89,0x43,0x82,0x79, 14900 0x8b,0x43,0x85,0x5c,0x8a,0x43,0x9d,0xef,0x8a,0x43,0x85,0x5c,0x8a,0x43,0xc1,0x37,0x8a,0x43,0x08,0x85, 14901 0x5c,0x8a,0x43,0x17,0x89,0x89,0x43,0x34,0xee,0x89,0x43,0x31,0xff,0x88,0x43,0x4f,0x64,0x89,0x43,0x31, 14902 0xff,0x88,0x43,0x08,0x9c,0xe3,0x88,0x43,0x31,0xff,0x88,0x43,0x19,0x6c,0x88,0x43,0x17,0x89,0x89,0x43, 14903 0x19,0x6c,0x88,0x43,0xc1,0x37,0x8a,0x43,0x08,0x19,0x6c,0x88,0x43,0x9d,0xef,0x8a,0x43,0x9c,0xe3,0x88, 14904 0x43,0x82,0x79,0x8b,0x43,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x09,0x02,0x04,0x06,0x19,0x60,0x86, 14905 0x43,0xec,0xed,0xa3,0x43,0x08,0x35,0xd6,0x85,0x43,0x76,0x43,0xa6,0x43,0x93,0xe1,0x80,0x43,0x57,0x02, 14906 0xac,0x43,0x61,0xd8,0x80,0x43,0x87,0x17,0xae,0x43,0x08,0xa5,0x85,0x80,0x43,0xc3,0xfe,0xaf,0x43,0xce, 14907 0xbc,0x80,0x43,0x83,0x40,0xb1,0x43,0xa5,0x91,0x82,0x43,0x79,0x6e,0xb1,0x43,0x08,0x23,0x26,0x84,0x43, 14908 0x40,0x93,0xb1,0x43,0x30,0xe7,0x84,0x43,0xbe,0x1b,0xb1,0x43,0x11,0x82,0x84,0x43,0xab,0x6b,0xaf,0x43, 14909 0x08,0xb7,0x41,0x84,0x43,0x3b,0x98,0xae,0x43,0xb7,0x41,0x84,0x43,0xc3,0xf2,0xad,0x43,0xa1,0xae,0x83, 14910 0x43,0x83,0x28,0xad,0x43,0x08,0xb2,0x52,0x83,0x43,0x80,0x39,0xac,0x43,0x81,0x49,0x83,0x43,0xf0,0x00, 14911 0xab,0x43,0xe4,0x67,0x85,0x43,0x76,0x4f,0xa8,0x43,0x08,0x9c,0xd7,0x86,0x43,0xd1,0x83,0xa6,0x43,0xec, 14912 0x45,0x87,0x43,0x01,0x75,0xa2,0x43,0x19,0x60,0x86,0x43,0xec,0xed,0xa3,0x43,0x09,0x06,0xd9,0xdc,0x6c, 14913 0x43,0x14,0x25,0xa4,0x43,0x08,0xa2,0xf0,0x6d,0x43,0x9f,0x7a,0xa6,0x43,0x47,0xec,0x77,0x43,0x80,0x39, 14914 0xac,0x43,0xa9,0xfe,0x77,0x43,0xb0,0x4e,0xae,0x43,0x08,0x23,0xa4,0x78,0x43,0xea,0x35,0xb0,0x43,0xd2, 14915 0x35,0x78,0x43,0xab,0x77,0xb1,0x43,0xc1,0x79,0x74,0x43,0xa2,0xa5,0xb1,0x43,0x08,0xc6,0x50,0x71,0x43, 14916 0x68,0xca,0xb1,0x43,0xab,0xce,0x6f,0x43,0xe7,0x52,0xb1,0x43,0xea,0x98,0x70,0x43,0xd4,0xa2,0xaf,0x43, 14917 0x08,0x9d,0x19,0x71,0x43,0x96,0xd8,0xae,0x43,0x9d,0x19,0x71,0x43,0xec,0x29,0xae,0x43,0xca,0x3f,0x72, 14918 0x43,0xab,0x5f,0xad,0x43,0x08,0xa6,0xf7,0x72,0x43,0xa7,0x70,0xac,0x43,0x09,0x0a,0x73,0x43,0x17,0x38, 14919 0xab,0x43,0x44,0xcd,0x6e,0x43,0x9f,0x86,0xa8,0x43,0x08,0xd4,0xed,0x6b,0x43,0xf8,0xba,0xa6,0x43,0x31, 14920 0x11,0x6b,0x43,0x2a,0xac,0xa2,0x43,0xd9,0xdc,0x6c,0x43,0x14,0x25,0xa4,0x43,0x09,0x01,0x05,0x06,0x66, 14921 0x5d,0x7a,0x43,0x74,0xeb,0xc2,0x43,0x08,0x09,0x22,0x77,0x43,0x50,0xbb,0xc7,0x43,0xe9,0xe0,0x7c,0x43, 14922 0xf5,0x86,0xc9,0x43,0x8f,0x94,0x7a,0x43,0xc5,0x95,0xcd,0x43,0x09,0x06,0x08,0x98,0x80,0x43,0x6b,0x19, 14923 0xc3,0x43,0x08,0xb7,0x35,0x82,0x43,0x79,0xf2,0xc7,0x43,0xf1,0xbe,0x7e,0x43,0x1e,0xbe,0xc9,0x43,0x73, 14924 0x7c,0x80,0x43,0xec,0xcc,0xcd,0x43,0x09,0x06,0x28,0xab,0x7d,0x43,0xae,0xde,0xc6,0x43,0x08,0x1e,0xcd, 14925 0x7b,0x43,0x8a,0xa2,0xc9,0x43,0x30,0x89,0x7f,0x43,0x5c,0x94,0xcc,0x43,0x28,0xab,0x7d,0x43,0x42,0x2a, 14926 0xcf,0x43,0x09,0x01,0x05,0x06,0x24,0x14,0xe0,0x42,0xf5,0x77,0x97,0x43,0x08,0xf7,0x1d,0xe7,0x42,0x74, 14927 0x00,0x97,0x43,0x4d,0x93,0xec,0x42,0xdb,0xf5,0x95,0x43,0x29,0x4b,0xed,0x42,0xcd,0x34,0x95,0x43,0x09, 14928 0x06,0x29,0x7b,0xf5,0x42,0x6f,0x1d,0x98,0x43,0x08,0xe4,0xf1,0xfb,0x42,0x61,0x5c,0x97,0x43,0xdb,0x7d, 14929 0x01,0x43,0xb2,0xbe,0x95,0x43,0x55,0x23,0x02,0x43,0xe7,0xaa,0x94,0x43,0x09,0x06,0x98,0xdc,0x03,0x43, 14930 0xbe,0x8b,0x98,0x43,0x08,0x66,0xdf,0x05,0x43,0x47,0xe6,0x97,0x43,0xae,0x87,0x08,0x43,0x98,0x48,0x96, 14931 0x43,0x61,0x08,0x09,0x43,0xd6,0x06,0x95,0x43,0x09,0x06,0x31,0x0b,0x0b,0x43,0x8e,0x82,0x98,0x43,0x08, 14932 0xdb,0xc5,0x0d,0x43,0x80,0xc1,0x97,0x43,0xd6,0xee,0x10,0x43,0xa9,0xec,0x95,0x43,0x79,0xcb,0x11,0x43, 14933 0x55,0x8f,0x94,0x43,0x09,0x06,0xd1,0x2f,0x18,0x43,0xdb,0x01,0x98,0x43,0x08,0xad,0xe7,0x18,0x43,0x38, 14934 0x25,0x97,0x43,0x8a,0x9f,0x19,0x43,0x80,0xb5,0x95,0x43,0xd6,0x1e,0x19,0x43,0xe0,0xd8,0x94,0x43,0x09, 14935 0x06,0x9a,0x5b,0x1d,0x43,0x58,0x8a,0x97,0x43,0x08,0x01,0x5d,0x1e,0x43,0xf1,0x88,0x96,0x43,0x2f,0x83, 14936 0x1f,0x43,0x19,0xb4,0x94,0x43,0x19,0xf0,0x1e,0x43,0x6f,0x05,0x94,0x43,0x09,0x06,0x0b,0x53,0x24,0x43, 14937 0xae,0xdb,0x96,0x43,0x08,0x25,0xd5,0x25,0x43,0x50,0xac,0x95,0x43,0x53,0xfb,0x26,0x43,0x8a,0x7b,0x93, 14938 0x43,0x76,0x43,0x26,0x43,0xb7,0x95,0x92,0x43,0x09,0x06,0x76,0x5b,0x2a,0x43,0x47,0xda,0x95,0x43,0x08, 14939 0xf3,0xef,0x2b,0x43,0x10,0xe2,0x94,0x43,0x6d,0x95,0x2c,0x43,0xae,0xc3,0x92,0x43,0x68,0xa6,0x2b,0x43, 14940 0x47,0xc2,0x91,0x43,0x09,0x06,0x36,0xc1,0x31,0x43,0x2c,0x58,0x94,0x43,0x08,0x8c,0x1e,0x33,0x43,0x31, 14941 0x3b,0x93,0x43,0x79,0x7a,0x33,0x43,0xff,0x25,0x91,0x43,0xd9,0x9d,0x32,0x43,0xc1,0x5b,0x90,0x43,0x09, 14942 0x06,0x25,0x35,0x36,0x43,0x31,0x3b,0x93,0x43,0x08,0x3f,0xb7,0x37,0x43,0xc1,0x67,0x92,0x43,0xe0,0x93, 14943 0x38,0x43,0xae,0xb7,0x90,0x43,0x7e,0x81,0x38,0x43,0x0d,0xdb,0x8f,0x43,0x09,0x06,0xb5,0x85,0x3b,0x43, 14944 0xe4,0xaf,0x91,0x43,0x08,0xcf,0x07,0x3d,0x43,0x9d,0x13,0x91,0x43,0xbc,0x63,0x3d,0x43,0x47,0xb6,0x8f, 14945 0x43,0xe5,0x9a,0x3d,0x43,0x74,0xd0,0x8e,0x43,0x09,0x06,0xae,0xc6,0x42,0x43,0xa4,0xd9,0x8e,0x43,0x08, 14946 0xca,0x48,0x44,0x43,0xfa,0x2a,0x8e,0x43,0xa2,0x11,0x44,0x43,0x9d,0xfb,0x8c,0x43,0x55,0x92,0x44,0x43, 14947 0x0d,0xc3,0x8b,0x43,0x09,0x06,0x39,0x10,0xc3,0x43,0x34,0x36,0x96,0x43,0x08,0x92,0x44,0xc1,0x43,0xe4, 14948 0xc7,0x95,0x43,0x6f,0xf0,0xbf,0x43,0x4b,0xbd,0x94,0x43,0x47,0xb9,0xbf,0x43,0x0b,0xf3,0x93,0x43,0x09, 14949 0x06,0x8f,0x49,0xbe,0x43,0xb7,0xad,0x96,0x43,0x08,0x11,0xb5,0xbc,0x43,0x77,0xe3,0x95,0x43,0x9c,0xf2, 14950 0xba,0x43,0xfa,0x4e,0x94,0x43,0xae,0x96,0xba,0x43,0x31,0x3b,0x93,0x43,0x09,0x06,0xdb,0xb0,0xb9,0x43, 14951 0x10,0xee,0x96,0x43,0x08,0x42,0xa6,0xb8,0x43,0xc8,0x51,0x96,0x43,0x50,0x5b,0xb7,0x43,0x19,0xb4,0x94, 14952 0x43,0xf7,0x1a,0xb7,0x43,0x58,0x72,0x93,0x43,0x09,0x06,0xf2,0x2b,0xb6,0x43,0x10,0xee,0x96,0x43,0x08, 14953 0x9d,0xce,0xb4,0x43,0x04,0x2d,0x96,0x43,0xed,0x30,0xb3,0x43,0x2c,0x58,0x94,0x43,0xce,0xcb,0xb2,0x43, 14954 0xd6,0xfa,0x92,0x43,0x09,0x06,0x5a,0x09,0xb1,0x43,0x19,0xc0,0x96,0x43,0x08,0x6c,0xad,0xb0,0x43,0x77, 14955 0xe3,0x95,0x43,0x7e,0x51,0xb0,0x43,0xc0,0x73,0x94,0x43,0xd8,0x91,0xb0,0x43,0x1e,0x97,0x93,0x43,0x09, 14956 0x06,0x48,0x4d,0xad,0x43,0xbe,0x7f,0x96,0x43,0x08,0x95,0xcc,0xac,0x43,0x58,0x7e,0x95,0x43,0x4d,0x30, 14957 0xac,0x43,0x80,0xa9,0x93,0x43,0xd8,0x79,0xac,0x43,0xd6,0xfa,0x92,0x43,0x09,0x06,0x90,0xd1,0xa9,0x43, 14958 0x14,0xd1,0x95,0x43,0x08,0x83,0x10,0xa9,0x43,0xb7,0xa1,0x94,0x43,0x3b,0x74,0xa8,0x43,0xf1,0x70,0x92, 14959 0x43,0x29,0xd0,0xa8,0x43,0x1e,0x8b,0x91,0x43,0x09,0x06,0x5a,0xcd,0xa6,0x43,0x8a,0x87,0x95,0x43,0x08, 14960 0x1c,0x03,0xa6,0x43,0x23,0x86,0x94,0x43,0x5f,0xb0,0xa5,0x43,0xc1,0x67,0x92,0x43,0xe1,0x27,0xa6,0x43, 14961 0x8a,0x6f,0x91,0x43,0x09,0x06,0xd4,0x5a,0xa3,0x43,0x2c,0x58,0x94,0x43,0x08,0x29,0xac,0xa2,0x43,0x31, 14962 0x3b,0x93,0x43,0x32,0x7e,0xa2,0x43,0xff,0x25,0x91,0x43,0x83,0xec,0xa2,0x43,0x8e,0x52,0x90,0x43,0x09, 14963 0x06,0xf8,0x96,0xa0,0x43,0x1e,0x97,0x93,0x43,0x08,0xeb,0xd5,0x9f,0x43,0x7b,0xba,0x92,0x43,0x99,0x67, 14964 0x9f,0x43,0x9d,0x13,0x91,0x43,0x99,0x67,0x9f,0x43,0xfa,0x36,0x90,0x43,0x09,0x06,0xeb,0xc9,0x9d,0x43, 14965 0xc8,0x39,0x92,0x43,0x08,0xde,0x08,0x9d,0x43,0xb2,0xa6,0x91,0x43,0xe6,0xda,0x9c,0x43,0x2c,0x40,0x90, 14966 0x43,0x52,0xbf,0x9c,0x43,0x5a,0x5a,0x8f,0x43,0x09,0x06,0x37,0x3d,0x9b,0x43,0x85,0x80,0x90,0x43,0x08, 14967 0x2a,0x7c,0x9a,0x43,0xdb,0xd1,0x8f,0x43,0xf0,0xa0,0x9a,0x43,0x7d,0xa2,0x8e,0x43,0x65,0x57,0x9a,0x43, 14968 0xee,0x69,0x8d,0x43,0x09,0x02,0x04,0x06,0x2a,0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x08,0x0d,0x8a,0x31, 14969 0x42,0x9f,0x0e,0x94,0x43,0xf3,0x1f,0x34,0x42,0x3d,0xfc,0x93,0x43,0x63,0xff,0x36,0x42,0xa9,0xe0,0x93, 14970 0x43,0x08,0xb5,0x34,0x5d,0x42,0x0b,0xf3,0x93,0x43,0x6d,0xa4,0x5e,0x42,0x03,0x39,0x98,0x43,0xe7,0x31, 14971 0x5b,0x42,0x93,0x89,0x9d,0x43,0x08,0x02,0x9c,0x58,0x42,0xd4,0x5a,0xa3,0x43,0x38,0x70,0x53,0x42,0x14, 14972 0x49,0xaa,0x43,0xf8,0xed,0x5e,0x42,0x83,0x28,0xad,0x43,0x08,0xea,0x68,0x68,0x42,0x20,0x22,0xaf,0x43, 14973 0x12,0xb8,0x6c,0x42,0xb5,0x49,0xb1,0x43,0x2a,0x4b,0x6d,0x42,0x0d,0x96,0xb3,0x43,0x07,0x2a,0x4b,0x6d, 14974 0x42,0xc6,0x05,0xb5,0x43,0x08,0x87,0x6e,0x6c,0x42,0x68,0xee,0xb7,0x43,0x1c,0x66,0x66,0x42,0x31,0x0e, 14975 0xbb,0x43,0x57,0x11,0x5e,0x42,0x8f,0x49,0xbe,0x43,0x08,0x66,0x96,0x54,0x42,0xb9,0x5c,0xb8,0x43,0x2c, 14976 0x2b,0x3c,0x42,0x68,0xd6,0xb3,0x43,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x07,0x2a,0xf4,0x2e,0x42, 14977 0x61,0xa4,0xa3,0x43,0x08,0x55,0x1a,0x30,0x42,0xf0,0xd0,0xa2,0x43,0xf8,0xf6,0x30,0x42,0xb2,0x06,0xa2, 14978 0x43,0x98,0xd3,0x31,0x42,0xd6,0x4e,0xa1,0x43,0x08,0x1c,0x6f,0x38,0x42,0x2a,0x94,0x9e,0x43,0xc1,0x22, 14979 0x36,0x42,0xf5,0x9b,0x9d,0x43,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x57, 14980 0xa2,0x9b,0x43,0x08,0xab,0x8f,0x35,0x42,0x8a,0xab,0x9b,0x43,0xe9,0x71,0x3a,0x42,0xb2,0xe2,0x9b,0x43, 14981 0xb7,0x74,0x3c,0x42,0x34,0x5a,0x9c,0x43,0x08,0x23,0x7d,0x42,0x42,0x0b,0x2f,0x9e,0x43,0xe5,0x9a,0x3d, 14982 0x42,0x38,0x6d,0xa3,0x43,0x36,0xd9,0x35,0x42,0xf3,0xd7,0xa7,0x43,0x08,0x12,0x61,0x2e,0x42,0xb0,0x42, 14983 0xac,0x43,0x63,0xff,0x36,0x42,0xdd,0x74,0xaf,0x43,0x1e,0xa6,0x45,0x42,0x44,0x82,0xb2,0x43,0x08,0x74, 14984 0x1b,0x4b,0x42,0x79,0x7a,0xb3,0x43,0x10,0x21,0x4f,0x42,0x2a,0x18,0xb5,0x43,0xdb,0x4c,0x54,0x42,0x91, 14985 0x19,0xb6,0x43,0x08,0xee,0x3f,0x65,0x42,0x5f,0x28,0xba,0x43,0xa7,0xaf,0x66,0x42,0xb9,0x50,0xb6,0x43, 14986 0x14,0x58,0x5c,0x42,0xca,0xdc,0xb1,0x43,0x08,0x2c,0x8b,0x4c,0x42,0x4e,0x30,0xac,0x43,0x19,0xcf,0x48, 14987 0x42,0x2a,0xd0,0xa8,0x43,0xbc,0xab,0x49,0x42,0xa9,0x4c,0xa6,0x43,0x08,0x61,0x5f,0x47,0x42,0xfa,0xa2, 14988 0xa2,0x43,0xa7,0xaf,0x66,0x42,0x85,0x98,0x94,0x43,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x07,0x2a, 14989 0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x09,0x06,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x08,0xdc,0xe3, 14990 0xf1,0x41,0xe9,0x9e,0x92,0x43,0xd2,0xe7,0x0b,0x42,0xd6,0x06,0x95,0x43,0x2a,0xf4,0x2e,0x42,0x04,0x21, 14991 0x94,0x43,0x07,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x08,0x87,0x17,0x2e,0x42,0xc3,0x62,0x95,0x43, 14992 0xe7,0x3a,0x2d,0x42,0xf5,0x6b,0x95,0x43,0x44,0x5e,0x2c,0x42,0xf5,0x6b,0x95,0x43,0x08,0xd1,0x47,0x1c, 14993 0x42,0x19,0xc0,0x96,0x43,0x66,0xdf,0x05,0x42,0x38,0x19,0x95,0x43,0x12,0x6a,0x00,0x42,0xb2,0xbe,0x95, 14994 0x43,0x08,0xbb,0x6b,0xea,0x41,0xd6,0x12,0x97,0x43,0x2d,0x82,0xfa,0x41,0x61,0x74,0x9b,0x43,0x7e,0x72, 14995 0x06,0x42,0x8a,0xab,0x9b,0x43,0x08,0xc8,0x39,0x12,0x42,0x4e,0xd0,0x9b,0x43,0x53,0xe3,0x22,0x42,0xc3, 14996 0x86,0x9b,0x43,0x2a,0xf4,0x2e,0x42,0x57,0xa2,0x9b,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43, 14997 0x08,0x01,0xa5,0x2a,0x42,0xa4,0x2d,0x9d,0x43,0x96,0x9c,0x24,0x42,0x06,0x40,0x9d,0x43,0x8a,0xb7,0x1d, 14998 0x42,0x9a,0x5b,0x9d,0x43,0x08,0x6b,0x16,0x13,0x42,0xcd,0x64,0x9d,0x43,0x42,0xc7,0x0e,0x42,0x9a,0x5b, 14999 0x9d,0x43,0x23,0x26,0x04,0x42,0xcd,0x64,0x9d,0x43,0x08,0xe6,0x91,0xeb,0x41,0x38,0x49,0x9d,0x43,0x73, 15000 0x7b,0xdb,0x41,0xf5,0x83,0x99,0x43,0x7f,0x60,0xe2,0x41,0x0b,0x0b,0x98,0x43,0x08,0x7f,0x60,0xe2,0x41, 15001 0xec,0x99,0x95,0x43,0xe3,0x5a,0xde,0x41,0xbe,0x7f,0x96,0x43,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43, 15002 0x07,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x09,0x06,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x08, 15003 0xd4,0x7e,0x29,0x42,0xab,0x6b,0xaf,0x43,0x4e,0x0c,0x26,0x42,0x44,0x6a,0xae,0x43,0x38,0x79,0x25,0x42, 15004 0xd4,0x96,0xad,0x43,0x08,0x25,0xbd,0x21,0x42,0xe2,0x4b,0xac,0x43,0x49,0x35,0x29,0x42,0x9a,0x97,0xa7, 15005 0x43,0x2a,0xf4,0x2e,0x42,0x61,0xa4,0xa3,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x09,0x06, 15006 0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x08,0x86,0x20,0x80,0x43,0x57,0x41,0xe6,0x43,0x7d,0x4e,0x80, 15007 0x43,0x25,0x38,0xe6,0x43,0xa5,0x85,0x80,0x43,0xf3,0x2e,0xe6,0x43,0x08,0x35,0xca,0x83,0x43,0xd4,0xc9, 15008 0xe5,0x43,0x9c,0xd7,0x86,0x43,0x44,0x91,0xe4,0x43,0xd5,0xca,0x8a,0x43,0x91,0x1c,0xe6,0x43,0x08,0x53, 15009 0x5f,0x8c,0x43,0xf8,0x1d,0xe7,0x43,0x2f,0x17,0x8d,0x43,0x4e,0x7b,0xe8,0x43,0x92,0x29,0x8d,0x43,0x2f, 15010 0x22,0xea,0x43,0x07,0x92,0x29,0x8d,0x43,0x44,0xb5,0xea,0x43,0x08,0xfe,0x0d,0x8d,0x43,0x2a,0x4b,0xed, 15011 0x43,0xe3,0x8b,0x8b,0x43,0x55,0x7d,0xf0,0x43,0xec,0x51,0x89,0x43,0x72,0x0b,0xf4,0x43,0x08,0xcd,0xd4, 15012 0x84,0x43,0x9d,0x55,0xfb,0x43,0xc9,0xe5,0x83,0x43,0x74,0x1e,0xfb,0x43,0x73,0x94,0x84,0x43,0x5a,0x90, 15013 0xf7,0x43,0x08,0xe8,0x62,0x88,0x43,0xfd,0x30,0xee,0x43,0x39,0xc5,0x86,0x43,0xdd,0xbf,0xeb,0x43,0x35, 15014 0xbe,0x81,0x43,0x40,0xde,0xed,0x43,0x08,0x4f,0x34,0x81,0x43,0x36,0x0c,0xee,0x43,0x08,0x98,0x80,0x43, 15015 0xfd,0x30,0xee,0x43,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x40,0xec, 15016 0x43,0x08,0x35,0xbe,0x81,0x43,0x06,0xf7,0xeb,0x43,0x15,0x65,0x83,0x43,0x49,0xa4,0xeb,0x43,0x1e,0x43, 15017 0x85,0x43,0xbe,0x5a,0xeb,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0x18,0xea,0x43,0x42,0x97,0x86,0x43,0x5f, 15018 0x67,0xf4,0x43,0xa9,0x98,0x87,0x43,0xd4,0x1d,0xf4,0x43,0x08,0x5c,0x25,0x8a,0x43,0xcf,0x16,0xef,0x43, 15019 0x46,0xaa,0x8d,0x43,0x5a,0x3c,0xe9,0x43,0x19,0x6c,0x88,0x43,0x53,0x5e,0xe7,0x43,0x08,0xc4,0x02,0x85, 15020 0x43,0x96,0x0b,0xe7,0x43,0x85,0x2c,0x82,0x43,0x83,0x67,0xe7,0x43,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7, 15021 0x43,0x07,0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x09,0x06,0xfd,0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43, 15022 0x08,0xfa,0x6c,0x78,0x43,0xd1,0xc2,0xe0,0x43,0x25,0x5c,0x6c,0x43,0x25,0x44,0xe8,0x43,0x1d,0xe5,0x7f, 15023 0x43,0x87,0x4a,0xe6,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,0x43,0x08,0xa6,0x27,0x7b,0x43,0x91, 15024 0x28,0xe8,0x43,0xbc,0xa2,0x77,0x43,0xb0,0x8d,0xe8,0x43,0xc6,0x68,0x75,0x43,0x57,0x4d,0xe8,0x43,0x08, 15025 0xe0,0xd2,0x72,0x43,0xab,0x9e,0xe7,0x43,0x50,0x9a,0x71,0x43,0x2a,0x27,0xe7,0x43,0xea,0x98,0x70,0x43, 15026 0x57,0x35,0xe4,0x43,0x08,0x94,0x3b,0x6f,0x43,0x14,0x7c,0xe2,0x43,0xff,0x13,0x6d,0x43,0x06,0xbb,0xe1, 15027 0x43,0xcf,0xfe,0x6a,0x43,0x06,0xbb,0xe1,0x43,0x08,0x44,0x9d,0x66,0x43,0x77,0x8e,0xe2,0x43,0x3b,0xef, 15028 0x6c,0x43,0x91,0x10,0xe4,0x43,0xfd,0x24,0x6c,0x43,0xb0,0x81,0xe6,0x43,0x08,0x96,0x23,0x6b,0x43,0xee, 15029 0x57,0xe9,0x43,0xca,0x0f,0x6a,0x43,0x5f,0x37,0xec,0x43,0x55,0x71,0x6e,0x43,0x9f,0x01,0xed,0x43,0x08, 15030 0xdb,0xfb,0x75,0x43,0x3b,0xef,0xec,0x43,0x09,0x3a,0x7b,0x43,0xb0,0xa5,0xec,0x43,0x1d,0xe5,0x7f,0x43, 15031 0x91,0x40,0xec,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x08,0xa9,0x16,0x7c,0x43,0xb0,0xb1, 15032 0xee,0x43,0x47,0xec,0x77,0x43,0xd9,0xe8,0xee,0x43,0x1e,0x9d,0x73,0x43,0xcf,0x16,0xef,0x43,0x08,0x0e, 15033 0xc9,0x6b,0x43,0xee,0x7b,0xef,0x43,0x7e,0x90,0x6a,0x43,0xfd,0x30,0xee,0x43,0x01,0xfc,0x68,0x43,0x4e, 15034 0x93,0xec,0x43,0x08,0x31,0xf9,0x66,0x43,0x4e,0x87,0xea,0x43,0x31,0x11,0x6b,0x43,0xd4,0xd5,0xe7,0x43, 15035 0xd9,0xc4,0x68,0x43,0xd4,0xc9,0xe5,0x43,0x08,0xe5,0x79,0x67,0x43,0x77,0x9a,0xe4,0x43,0x44,0x9d,0x66, 15036 0x43,0xab,0x86,0xe3,0x43,0x7e,0x78,0x66,0x43,0x0b,0xaa,0xe2,0x43,0x07,0x7e,0x78,0x66,0x43,0x57,0x29, 15037 0xe2,0x43,0x08,0xa7,0xaf,0x66,0x43,0xbe,0x1e,0xe1,0x43,0x87,0x56,0x68,0x43,0x77,0x82,0xe0,0x43,0xfd, 15038 0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,0x09,0x06,0xc4,0x41,0xbf,0x43,0x85,0xc0,0x72,0x42,0x08,0x73,0xdf, 15039 0xc0,0x43,0xf4,0x76,0x72,0x42,0x97,0x33,0xc2,0x43,0x85,0xc0,0x72,0x42,0xb2,0xb5,0xc3,0x43,0x64,0x56, 15040 0x75,0x42,0x08,0x03,0x24,0xc4,0x43,0x5e,0x7f,0x78,0x42,0xfa,0x51,0xc4,0x43,0x01,0x85,0x7c,0x42,0x5c, 15041 0x64,0xc4,0x43,0xa0,0xb3,0x80,0x42,0x07,0x5c,0x64,0xc4,0x43,0x10,0x93,0x83,0x42,0x08,0xc8,0x48,0xc4, 15042 0x43,0x1c,0x78,0x8a,0x42,0x27,0x6c,0xc3,0x43,0xaf,0xcf,0x94,0x42,0x23,0x7d,0xc2,0x43,0x99,0x9c,0xa4, 15043 0x42,0x08,0x3d,0xe7,0xbf,0x43,0xfb,0xfd,0xb5,0x42,0xb3,0x9d,0xbf,0x43,0x88,0x17,0xae,0x42,0xc4,0x41, 15044 0xbf,0x43,0x69,0x76,0xa3,0x42,0x07,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x08,0x4f,0x8b,0xbf,0x43, 15045 0xed,0x81,0x91,0x42,0xe4,0xa6,0xbf,0x43,0x5d,0x61,0x94,0x42,0xfa,0x39,0xc0,0x43,0x3b,0x49,0x9d,0x42, 15046 0x08,0x2b,0x43,0xc0,0x43,0x28,0xed,0xa9,0x42,0x61,0x3b,0xc1,0x43,0x00,0x9e,0xa5,0x42,0xe4,0xb2,0xc1, 15047 0x43,0x5d,0x91,0x9c,0x42,0x08,0x78,0xce,0xc1,0x43,0xfd,0x36,0x90,0x42,0x22,0x89,0xc4,0x43,0x81,0x72, 15048 0x86,0x42,0xae,0xc6,0xc2,0x43,0xa0,0xb3,0x80,0x42,0x08,0x54,0x86,0xc2,0x43,0x58,0xd1,0x7e,0x42,0x30, 15049 0x32,0xc1,0x43,0xce,0x5e,0x7b,0x42,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x07,0xc4,0x41,0xbf,0x43, 15050 0x85,0xc0,0x72,0x42,0x09,0x06,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x08,0x35,0xfd,0xbb,0x43,0xa4, 15051 0xa1,0x5c,0x42,0x5e,0x34,0xbc,0x43,0x9d,0x2a,0x70,0x42,0x5e,0x40,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08, 15052 0x4c,0x9c,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,0xef,0xbe,0x43,0x0e,0x0a,0x73,0x42,0xc4,0x41,0xbf,0x43, 15053 0x85,0xc0,0x72,0x42,0x07,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x08,0xcd,0x13,0xbf,0x43,0xe8,0xf1, 15054 0x7b,0x42,0xd6,0xe5,0xbe,0x43,0x71,0x3b,0x7c,0x42,0xdf,0xb7,0xbe,0x43,0x71,0x3b,0x7c,0x42,0x08,0x08, 15055 0xe3,0xbc,0x43,0xa4,0x61,0x7d,0x42,0x28,0x3c,0xbb,0x43,0x91,0x45,0x69,0x42,0x28,0x3c,0xbb,0x43,0x58, 15056 0x71,0x6e,0x42,0x08,0xce,0xfb,0xba,0x43,0xd5,0x35,0x78,0x42,0x59,0x45,0xbb,0x43,0x58,0x23,0x82,0x42, 15057 0xa1,0xe1,0xbb,0x43,0xd7,0xbe,0x88,0x42,0x08,0xc9,0x18,0xbc,0x43,0xaf,0x9f,0x8c,0x42,0x1e,0x76,0xbd, 15058 0x43,0x51,0x7c,0x8d,0x42,0xd6,0xe5,0xbe,0x43,0xf4,0x58,0x8e,0x42,0x08,0x9c,0x0a,0xbf,0x43,0x45,0xc7, 15059 0x8e,0x42,0x30,0x26,0xbf,0x43,0x96,0x35,0x8f,0x42,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x07,0xc4, 15060 0x41,0xbf,0x43,0x69,0x76,0xa3,0x42,0x08,0x08,0xef,0xbe,0x43,0xb1,0xd6,0x99,0x42,0xe8,0x89,0xbe,0x43, 15061 0xde,0xc5,0x8d,0x42,0xc0,0x46,0xbc,0x43,0xc2,0x5b,0x90,0x42,0x08,0x9c,0xf2,0xba,0x43,0x86,0x80,0x90, 15062 0x42,0xf2,0x43,0xba,0x43,0xe8,0x73,0x87,0x42,0x8f,0x31,0xba,0x43,0xb6,0xf4,0x7d,0x42,0x07,0x8f,0x31, 15063 0xba,0x43,0x21,0xc6,0x76,0x42,0x08,0xc0,0x3a,0xba,0x43,0x5f,0x48,0x6b,0x42,0xae,0x96,0xba,0x43,0xe3, 15064 0x83,0x61,0x42,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x09,0x06,0xea,0x74,0xea,0x43,0x61,0x44,0x93, 15065 0x43,0x08,0x24,0x5c,0xec,0x43,0x31,0x3b,0x93,0x43,0xfb,0x30,0xee,0x43,0x93,0x4d,0x93,0x43,0x0d,0xe1, 15066 0xef,0x43,0x80,0xa9,0x93,0x43,0x08,0x8f,0x58,0xf0,0x43,0xd1,0x17,0x94,0x43,0xb7,0x8f,0xf0,0x43,0x10, 15067 0xe2,0x94,0x43,0xea,0x98,0xf0,0x43,0xa9,0xec,0x95,0x43,0x07,0xea,0x98,0xf0,0x43,0x38,0x25,0x97,0x43, 15068 0x08,0x23,0x74,0xf0,0x43,0x9f,0x32,0x9a,0x43,0x5a,0x60,0xef,0x43,0x53,0xcb,0x9e,0x43,0x2d,0x3a,0xee, 15069 0x43,0xfd,0x91,0xa3,0x43,0x08,0xa2,0xf0,0xed,0x43,0xdd,0x38,0xa5,0x43,0x17,0xa7,0xed,0x43,0xbe,0xdf, 15070 0xa6,0x43,0x5a,0x54,0xed,0x43,0x9f,0x86,0xa8,0x43,0x08,0xfc,0x24,0xec,0x43,0xca,0xc4,0xad,0x43,0x48, 15071 0xa4,0xeb,0x43,0x40,0x6f,0xab,0x43,0x28,0x3f,0xeb,0x43,0x1c,0x0f,0xa8,0x43,0x08,0x1f,0x6d,0xeb,0x43, 15072 0x72,0x48,0xa3,0x43,0x67,0x09,0xec,0x43,0xd1,0x53,0x9e,0x43,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,0x43, 15073 0x07,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x08,0x7e,0x90,0xea,0x43,0x8a,0x9f,0x99,0x43,0x12,0xac, 15074 0xea,0x43,0xbc,0xa8,0x99,0x43,0xa7,0xc7,0xea,0x43,0xbc,0xa8,0x99,0x43,0x08,0x51,0x76,0xeb,0x43,0x9f, 15075 0x32,0x9a,0x43,0x5e,0x37,0xec,0x43,0x49,0xed,0x9c,0x43,0xb0,0xa5,0xec,0x43,0x2a,0xa0,0xa0,0x43,0x08, 15076 0x09,0xe6,0xec,0x43,0xd1,0x77,0xa4,0x43,0x28,0x4b,0xed,0x43,0x61,0xa4,0xa3,0x43,0xab,0xc2,0xed,0x43, 15077 0x8e,0xb2,0xa0,0x43,0x08,0x70,0xe7,0xed,0x43,0xde,0x08,0x9d,0x43,0x87,0x86,0xf0,0x43,0x2f,0x53,0x97, 15078 0x43,0x87,0x7a,0xee,0x43,0xec,0x99,0x95,0x43,0x08,0xca,0x27,0xee,0x43,0xff,0x3d,0x95,0x43,0x74,0xca, 15079 0xec,0x43,0x55,0x8f,0x94,0x43,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x07,0xea,0x74,0xea,0x43,0x61, 15080 0x44,0x93,0x43,0x09,0x06,0x05,0xd3,0xe5,0x43,0x19,0x9c,0x90,0x43,0x08,0x09,0xc2,0xe6,0x43,0xd1,0xff, 15081 0x8f,0x43,0x4d,0x6f,0xe6,0x43,0x74,0xe8,0x92,0x43,0x3b,0xd7,0xe8,0x43,0xc3,0x56,0x93,0x43,0x08,0x1f, 15082 0x61,0xe9,0x43,0x93,0x4d,0x93,0x43,0x05,0xeb,0xe9,0x43,0x93,0x4d,0x93,0x43,0xea,0x74,0xea,0x43,0x61, 15083 0x44,0x93,0x43,0x07,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x08,0x24,0x50,0xea,0x43,0xe7,0xaa,0x94, 15084 0x43,0x2d,0x22,0xea,0x43,0xe7,0xaa,0x94,0x43,0x36,0xf4,0xe9,0x43,0xe7,0xaa,0x94,0x43,0x08,0xa2,0xcc, 15085 0xe7,0x43,0xe0,0xd8,0x94,0x43,0xd4,0xc9,0xe5,0x43,0x19,0xa8,0x92,0x43,0xd4,0xc9,0xe5,0x43,0x27,0x69, 15086 0x93,0x43,0x08,0x17,0x77,0xe5,0x43,0xe0,0xd8,0x94,0x43,0x67,0xe5,0xe5,0x43,0x47,0xda,0x95,0x43,0x43, 15087 0x9d,0xe6,0x43,0xe2,0xd3,0x97,0x43,0x08,0x9d,0xdd,0xe6,0x43,0xad,0xe7,0x98,0x43,0x09,0xce,0xe8,0x43, 15088 0xff,0x55,0x99,0x43,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x07,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b, 15089 0x43,0x08,0x71,0xcf,0xe9,0x43,0x53,0xb3,0x9a,0x43,0xa7,0xbb,0xe8,0x43,0xdb,0x0d,0x9a,0x43,0xc6,0x14, 15090 0xe7,0x43,0xdb,0x0d,0x9a,0x43,0x08,0x48,0x80,0xe5,0x43,0xdb,0x0d,0x9a,0x43,0x0a,0xb6,0xe4,0x43,0xc3, 15091 0x6e,0x97,0x43,0x76,0x9a,0xe4,0x43,0x74,0xf4,0x94,0x43,0x07,0x76,0x9a,0xe4,0x43,0x79,0xd7,0x93,0x43, 15092 0x08,0xd8,0xac,0xe4,0x43,0x66,0x27,0x92,0x43,0x29,0x1b,0xe5,0x43,0xe0,0xc0,0x90,0x43,0x05,0xd3,0xe5, 15093 0x43,0x19,0x9c,0x90,0x43,0x09,0x06,0x1b,0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x08,0x71,0x0b,0xf4,0x42, 15094 0x00,0x0e,0x8d,0x42,0x8c,0x0f,0x01,0x43,0x3e,0xc0,0x89,0x42,0xf3,0x28,0x06,0x43,0x48,0x9e,0x8b,0x42, 15095 0x08,0x15,0x89,0x09,0x43,0x00,0x0e,0x8d,0x42,0xe0,0x9c,0x0a,0x43,0xc1,0x8b,0x98,0x42,0xa6,0xc1,0x0a, 15096 0x43,0x02,0xa5,0xaa,0x42,0x07,0xa6,0xc1,0x0a,0x43,0xf9,0xf6,0xb0,0x42,0x08,0xa6,0xc1,0x0a,0x43,0x47, 15097 0x8e,0xb4,0x42,0x42,0xaf,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0xe0,0x9c,0x0a,0x43,0xba,0x74,0xbc,0x42,0x08, 15098 0xa1,0xd2,0x09,0x43,0x40,0x47,0xd0,0x42,0x0d,0xab,0x07,0x43,0x91,0xb5,0xd0,0x42,0x3b,0xb9,0x04,0x43, 15099 0xec,0x71,0xba,0x42,0x08,0xe5,0x5b,0x03,0x43,0xe3,0x33,0xa8,0x42,0x63,0xd8,0x00,0x43,0xce,0x70,0x9f, 15100 0x42,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x07,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,0x42,0x08,0xed, 15101 0x6f,0xed,0x42,0x73,0x24,0x9d,0x42,0xd8,0x0c,0xf5,0x42,0x99,0x6c,0x9c,0x42,0x27,0xab,0xfd,0x42,0xea, 15102 0xda,0x9c,0x42,0x08,0x36,0xca,0x03,0x43,0x2b,0x94,0x9e,0x42,0x68,0xc7,0x01,0x43,0x8f,0xbe,0xa2,0x42, 15103 0xfa,0x06,0x08,0x43,0x73,0xb4,0xb5,0x42,0x08,0x8e,0x2e,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0x9d,0xe3,0x08, 15104 0x43,0xd7,0x1e,0x99,0x42,0x28,0x15,0x05,0x43,0x32,0x3b,0x93,0x42,0x08,0x63,0xf0,0x04,0x43,0x70,0xed, 15105 0x8f,0x42,0x71,0x0b,0xf4,0x42,0x32,0x3b,0x93,0x42,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x07,0x1b, 15106 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x09,0x06,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,0x08,0x8e,0x55, 15107 0xc0,0x42,0xb8,0x4d,0x86,0x42,0x60,0xbf,0xd7,0x42,0x3e,0xf0,0x91,0x42,0x63,0xf6,0xe4,0x42,0x70,0xed, 15108 0x8f,0x42,0x08,0x7a,0x89,0xe5,0x42,0xac,0xc8,0x8f,0x42,0xcc,0xf7,0xe5,0x42,0xac,0xc8,0x8f,0x42,0x1b, 15109 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x07,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x08,0x63,0xf6,0xe4, 15110 0x42,0x3b,0x19,0x95,0x42,0xe6,0x61,0xe3,0x42,0x00,0x3e,0x95,0x42,0xf4,0x16,0xe2,0x42,0xc4,0x62,0x95, 15111 0x42,0x08,0x6e,0x74,0xd6,0x42,0x15,0xd1,0x95,0x42,0x97,0x63,0xca,0x42,0xaf,0xcf,0x94,0x42,0xfb,0x2d, 15112 0xbe,0x42,0x86,0x80,0x90,0x42,0x08,0x97,0x03,0xba,0x42,0xce,0x10,0x8f,0x42,0x5e,0x28,0xba,0x42,0x3e, 15113 0xf0,0x91,0x42,0xf2,0x4f,0xbc,0x42,0x45,0xf7,0x96,0x42,0x08,0x27,0x54,0xbf,0x42,0x73,0x24,0x9d,0x42, 15114 0xa5,0xe8,0xc0,0x42,0x86,0xe0,0xa0,0x42,0xe4,0xca,0xc5,0x42,0xed,0x11,0xaa,0x42,0x08,0x54,0xaa,0xc8, 15115 0x42,0x86,0x40,0xb1,0x42,0x59,0x81,0xc5,0x42,0xa1,0x11,0xc4,0x42,0x3e,0xe7,0xbf,0x42,0xfb,0x8d,0xce, 15116 0x42,0x08,0xb4,0x6d,0xb7,0x42,0x30,0xc2,0xd9,0x42,0x46,0xf5,0xc9,0x42,0xdf,0x53,0xd9,0x42,0x38,0x40, 15117 0xcb,0x42,0x62,0x8f,0xcf,0x42,0x08,0x7d,0xf9,0xcc,0x42,0xec,0xa1,0xc2,0x42,0x07,0x43,0xcd,0x42,0x6c, 15118 0xdd,0xb8,0x42,0x2b,0x8b,0xcc,0x42,0x92,0xf5,0xaf,0x42,0x08,0xf9,0x8d,0xce,0x42,0x41,0x57,0xa7,0x42, 15119 0x5b,0xb8,0xd2,0x42,0xae,0x2f,0xa5,0x42,0x18,0x2f,0xd9,0x42,0x13,0x2a,0xa1,0x42,0x08,0x41,0x7e,0xdd, 15120 0x42,0xe3,0x03,0xa0,0x42,0x2e,0xf2,0xe1,0x42,0x7c,0x02,0x9f,0x42,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e, 15121 0x42,0x07,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x08,0x4d,0x63,0xe4,0x42,0x00,0x9e,0xa5,0x42,0xf4, 15122 0x16,0xe2,0x42,0x15,0x31,0xa6,0x42,0x99,0xca,0xdf,0x42,0x2b,0xc4,0xa6,0x42,0x08,0xc0,0x82,0xc6,0x42, 15123 0xc4,0xc2,0xa5,0x42,0x57,0xe1,0xd5,0x42,0x91,0xb5,0xd0,0x42,0x54,0xda,0xd0,0x42,0x97,0x93,0xd2,0x42, 15124 0x08,0x9c,0x3a,0xc7,0x42,0x17,0x58,0xdc,0x42,0x9c,0x0a,0xbf,0x42,0x6e,0xa4,0xde,0x42,0x90,0x25,0xb8, 15125 0x42,0xdf,0x53,0xd9,0x42,0x08,0x59,0x21,0xb5,0x42,0xf2,0xdf,0xd4,0x42,0x51,0x43,0xb3,0x42,0x91,0xb5, 15126 0xd0,0x42,0xc5,0x29,0xbb,0x42,0x0e,0x1a,0xca,0x42,0x08,0x65,0x36,0xc4,0x42,0xd0,0x07,0xbd,0x42,0x3e, 15127 0xe7,0xbf,0x42,0x37,0x09,0xbe,0x42,0x0c,0xea,0xc1,0x42,0xcd,0xd0,0xaf,0x42,0x08,0x2b,0x5b,0xc4,0x42, 15128 0x18,0x08,0xa3,0x42,0x67,0xa6,0xab,0x42,0x99,0x3c,0x94,0x42,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42, 15129 0x09,]; 15130 15131 private struct ThePath { 15132 public: 15133 enum Command { 15134 Bounds, // always first, has 4 args (x0, y0, x1, y1) 15135 StrokeMode, 15136 FillMode, 15137 StrokeFillMode, 15138 NormalStroke, 15139 ThinStroke, 15140 MoveTo, 15141 LineTo, 15142 CubicTo, // cubic bezier 15143 EndPath, 15144 } 15145 15146 public: 15147 const(ubyte)[] path; 15148 uint ppos; 15149 15150 public: 15151 this (const(void)[] apath) pure nothrow @trusted @nogc { 15152 path = cast(const(ubyte)[])apath; 15153 } 15154 15155 @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (ppos >= path.length); } 15156 15157 Command getCommand () nothrow @trusted @nogc { 15158 pragma(inline, true); 15159 if (ppos >= cast(uint)path.length) assert(0, "invalid path"); 15160 return cast(Command)(path.ptr[ppos++]); 15161 } 15162 15163 // number of (x,y) pairs for this command 15164 static int argCount (in Command cmd) nothrow @safe @nogc { 15165 version(aliced) pragma(inline, true); 15166 if (cmd == Command.Bounds) return 2; 15167 else if (cmd == Command.MoveTo || cmd == Command.LineTo) return 1; 15168 else if (cmd == Command.CubicTo) return 3; 15169 else return 0; 15170 } 15171 15172 void skipArgs (int argc) nothrow @trusted @nogc { 15173 pragma(inline, true); 15174 ppos += cast(uint)(float.sizeof*2*argc); 15175 } 15176 15177 float getFloat () nothrow @trusted @nogc { 15178 pragma(inline, true); 15179 if (ppos >= cast(uint)path.length || cast(uint)path.length-ppos < float.sizeof) assert(0, "invalid path"); 15180 version(LittleEndian) { 15181 float res = *cast(const(float)*)(&path.ptr[ppos]); 15182 ppos += cast(uint)float.sizeof; 15183 return res; 15184 } else { 15185 static assert(float.sizeof == 4); 15186 uint xp = path.ptr[ppos]|(path.ptr[ppos+1]<<8)|(path.ptr[ppos+2]<<16)|(path.ptr[ppos+3]<<24); 15187 ppos += cast(uint)float.sizeof; 15188 return *cast(const(float)*)(&xp); 15189 } 15190 } 15191 } 15192 15193 // this will add baphomet's background path to the current NanoVega path, so you can fill it. 15194 public void addBaphometBack (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc { 15195 if (nvg is null) return; 15196 15197 auto path = ThePath(baphometPath); 15198 15199 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15200 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15201 15202 bool inPath = false; 15203 while (!path.empty) { 15204 auto cmd = path.getCommand(); 15205 switch (cmd) { 15206 case ThePath.Command.MoveTo: 15207 inPath = true; 15208 immutable float ex = getScaledX(); 15209 immutable float ey = getScaledY(); 15210 nvg.moveTo(ex, ey); 15211 break; 15212 case ThePath.Command.LineTo: 15213 inPath = true; 15214 immutable float ex = getScaledX(); 15215 immutable float ey = getScaledY(); 15216 nvg.lineTo(ex, ey); 15217 break; 15218 case ThePath.Command.CubicTo: // cubic bezier 15219 inPath = true; 15220 immutable float x1 = getScaledX(); 15221 immutable float y1 = getScaledY(); 15222 immutable float x2 = getScaledX(); 15223 immutable float y2 = getScaledY(); 15224 immutable float ex = getScaledX(); 15225 immutable float ey = getScaledY(); 15226 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15227 break; 15228 case ThePath.Command.EndPath: 15229 if (inPath) return; 15230 break; 15231 default: 15232 path.skipArgs(path.argCount(cmd)); 15233 break; 15234 } 15235 } 15236 } 15237 15238 // this will add baphomet's pupil paths to the current NanoVega path, so you can fill it. 15239 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 { 15240 // pupils starts with "fill-and-stroke" mode 15241 if (nvg is null) return; 15242 15243 auto path = ThePath(baphometPath); 15244 15245 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15246 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15247 15248 bool inPath = false; 15249 bool pupLeft = true; 15250 while (!path.empty) { 15251 auto cmd = path.getCommand(); 15252 switch (cmd) { 15253 case ThePath.Command.StrokeFillMode: inPath = true; break; 15254 case ThePath.Command.MoveTo: 15255 if (!inPath) goto default; 15256 static if (!left) { if (pupLeft) goto default; } 15257 static if (!right) { if (!pupLeft) goto default; } 15258 immutable float ex = getScaledX(); 15259 immutable float ey = getScaledY(); 15260 nvg.moveTo(ex, ey); 15261 break; 15262 case ThePath.Command.LineTo: 15263 if (!inPath) goto default; 15264 static if (!left) { if (pupLeft) goto default; } 15265 static if (!right) { if (!pupLeft) goto default; } 15266 immutable float ex = getScaledX(); 15267 immutable float ey = getScaledY(); 15268 nvg.lineTo(ex, ey); 15269 break; 15270 case ThePath.Command.CubicTo: // cubic bezier 15271 if (!inPath) goto default; 15272 static if (!left) { if (pupLeft) goto default; } 15273 static if (!right) { if (!pupLeft) goto default; } 15274 immutable float x1 = getScaledX(); 15275 immutable float y1 = getScaledY(); 15276 immutable float x2 = getScaledX(); 15277 immutable float y2 = getScaledY(); 15278 immutable float ex = getScaledX(); 15279 immutable float ey = getScaledY(); 15280 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15281 break; 15282 case ThePath.Command.EndPath: 15283 if (inPath) { 15284 if (pupLeft) pupLeft = false; else return; 15285 } 15286 break; 15287 default: 15288 path.skipArgs(path.argCount(cmd)); 15289 break; 15290 } 15291 } 15292 } 15293 15294 // mode: 'f' to allow fills; 's' to allow strokes; 'w' to allow stroke widths; 'c' to replace fills with strokes 15295 public void renderBaphomet(string mode="fs") (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc { 15296 template hasChar(char ch, string s) { 15297 static if (s.length == 0) enum hasChar = false; 15298 else static if (s[0] == ch) enum hasChar = true; 15299 else enum hasChar = hasChar!(ch, s[1..$]); 15300 } 15301 enum AllowStroke = hasChar!('s', mode); 15302 enum AllowFill = hasChar!('f', mode); 15303 enum AllowWidth = hasChar!('w', mode); 15304 enum Contour = hasChar!('c', mode); 15305 //static assert(AllowWidth || AllowFill); 15306 15307 if (nvg is null) return; 15308 15309 auto path = ThePath(baphometPath); 15310 15311 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15312 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15313 15314 int mode = 0; 15315 int sw = ThePath.Command.NormalStroke; 15316 nvg.beginPath(); 15317 while (!path.empty) { 15318 auto cmd = path.getCommand(); 15319 switch (cmd) { 15320 case ThePath.Command.StrokeMode: mode = ThePath.Command.StrokeMode; break; 15321 case ThePath.Command.FillMode: mode = ThePath.Command.FillMode; break; 15322 case ThePath.Command.StrokeFillMode: mode = ThePath.Command.StrokeFillMode; break; 15323 case ThePath.Command.NormalStroke: sw = ThePath.Command.NormalStroke; break; 15324 case ThePath.Command.ThinStroke: sw = ThePath.Command.ThinStroke; break; 15325 case ThePath.Command.MoveTo: 15326 immutable float ex = getScaledX(); 15327 immutable float ey = getScaledY(); 15328 nvg.moveTo(ex, ey); 15329 break; 15330 case ThePath.Command.LineTo: 15331 immutable float ex = getScaledX(); 15332 immutable float ey = getScaledY(); 15333 nvg.lineTo(ex, ey); 15334 break; 15335 case ThePath.Command.CubicTo: // cubic bezier 15336 immutable float x1 = getScaledX(); 15337 immutable float y1 = getScaledY(); 15338 immutable float x2 = getScaledX(); 15339 immutable float y2 = getScaledY(); 15340 immutable float ex = getScaledX(); 15341 immutable float ey = getScaledY(); 15342 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15343 break; 15344 case ThePath.Command.EndPath: 15345 if (mode == ThePath.Command.FillMode || mode == ThePath.Command.StrokeFillMode) { 15346 static if (AllowFill || Contour) { 15347 static if (Contour) { 15348 if (mode == ThePath.Command.FillMode) { nvg.strokeWidth = 1; nvg.stroke(); } 15349 } else { 15350 nvg.fill(); 15351 } 15352 } 15353 } 15354 if (mode == ThePath.Command.StrokeMode || mode == ThePath.Command.StrokeFillMode) { 15355 static if (AllowStroke || Contour) { 15356 static if (AllowWidth) { 15357 if (sw == ThePath.Command.NormalStroke) nvg.strokeWidth = 1; 15358 else if (sw == ThePath.Command.ThinStroke) nvg.strokeWidth = 0.5; 15359 else assert(0, "wtf?!"); 15360 } 15361 nvg.stroke(); 15362 } 15363 } 15364 nvg.newPath(); 15365 break; 15366 default: 15367 path.skipArgs(path.argCount(cmd)); 15368 break; 15369 } 15370 } 15371 nvg.newPath(); 15372 }