1 // 2 // Would be nice: way to take output of the canvas to an image file (raster and/or svg) 3 // 4 // 5 // Copyright (c) 2013 Mikko Mononen memon@inside.org 6 // 7 // This software is provided 'as-is', without any express or implied 8 // warranty. In no event will the authors be held liable for any damages 9 // arising from the use of this software. 10 // Permission is granted to anyone to use this software for any purpose, 11 // including commercial applications, and to alter it and redistribute it 12 // freely, subject to the following restrictions: 13 // 1. The origin of this software must not be misrepresented; you must not 14 // claim that you wrote the original software. If you use this software 15 // in a product, an acknowledgment in the product documentation would be 16 // appreciated but is not required. 17 // 2. Altered source versions must be plainly marked as such, and must not be 18 // misrepresented as being the original software. 19 // 3. This notice may not be removed or altered from any source distribution. 20 // 21 // Fork developement, feature integration and new bugs: 22 // Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org> 23 // Contains code from various contributors. 24 /** 25 The NanoVega API is modeled loosely on HTML5 canvas API. 26 If you know canvas, you're up to speed with NanoVega in no time. 27 28 $(SIDE_BY_SIDE 29 30 $(COLUMN 31 D code with nanovega: 32 33 --- 34 import arsd.nanovega; 35 import arsd.simpledisplay; 36 37 void main () { 38 // The NVGWindow class creates a window and sets up the nvg context for you 39 // you can also do these steps yourself, see the other examples in these docs 40 auto window = new NVGWindow(800, 600, "NanoVega Simple Sample"); 41 42 window.redrawNVGScene = delegate (nvg) { 43 nvg.beginPath(); // start new path 44 nvg.roundedRect(20.5, 30.5, window.width-40, window.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation) 45 // now set filling mode for our rectangle 46 // you can create colors using HTML syntax, or with convenient constants 47 nvg.fillPaint = nvg.linearGradient(20.5, 30.5, window.width-40, window.height-60, 48 NVGColor("#f70"), NVGColor.green); 49 // now fill our rect 50 nvg.fill(); 51 // and draw a nice outline 52 nvg.strokeColor = NVGColor.white; 53 nvg.strokeWidth = 2; 54 nvg.stroke(); 55 // that's all, folks! 56 }; 57 58 window.eventLoop(0, 59 delegate (KeyEvent event) { 60 if (event == "*-Q" || event == "Escape") { window.close(); return; } // quit on Q, Ctrl+Q, and so on 61 }, 62 ); 63 } 64 --- 65 ) 66 67 $(COLUMN 68 Javascript code with HTML5 Canvas 69 70 ```html 71 <!DOCTYPE html> 72 <html> 73 <head> 74 <title>NanoVega Simple Sample (HTML5 Translation)</title> 75 <style> 76 body { background-color: black; } 77 </style> 78 </head> 79 <body> 80 <canvas id="my-canvas" width="800" height="600"></canvas> 81 <script> 82 var canvas = document.getElementById("my-canvas"); 83 var context = canvas.getContext("2d"); 84 85 context.beginPath(); 86 87 context.rect(20.5, 30.5, canvas.width - 40, canvas.height - 60); 88 89 var gradient = context.createLinearGradient(20.5, 30.5, canvas.width - 40, canvas.height - 60); 90 gradient.addColorStop(0, "#f70"); 91 gradient.addColorStop(1, "green"); 92 93 context.fillStyle = gradient; 94 context.fill(); 95 context.closePath(); 96 context.strokeStyle = "white"; 97 context.lineWidth = 2; 98 context.stroke(); 99 </script> 100 </body> 101 </html> 102 ``` 103 ) 104 ) 105 106 $(TIP 107 This library can use either inbuilt or BindBC (external dependency) provided bindings for OpenGL and FreeType. 108 Former are used by default, latter can be activated by passing the `bindbc` version specifier to the compiler. 109 ) 110 111 112 Creating drawing context 113 ======================== 114 115 The drawing context is created using platform specific constructor function. 116 117 --- 118 NVGContext vg = nvgCreateContext(); 119 --- 120 121 $(WARNING You must use created context ONLY in that thread where you created it. 122 There is no way to "transfer" context between threads. Trying to do so 123 will lead to UB.) 124 125 $(WARNING Never issue any commands outside of [beginFrame]/[endFrame]. Trying to 126 do so will lead to UB.) 127 128 129 Drawing shapes with NanoVega 130 ============================ 131 132 Drawing a simple shape using NanoVega consists of four steps: 133 $(LIST 134 * begin a new shape, 135 * define the path to draw, 136 * set fill or stroke, 137 * and finally fill or stroke the path. 138 ) 139 140 --- 141 vg.beginPath(); 142 vg.rect(100, 100, 120, 30); 143 vg.fillColor(nvgRGBA(255, 192, 0, 255)); 144 vg.fill(); 145 --- 146 147 Calling [beginPath] will clear any existing paths and start drawing from blank slate. 148 There are number of number of functions to define the path to draw, such as rectangle, 149 rounded rectangle and ellipse, or you can use the common moveTo, lineTo, bezierTo and 150 arcTo API to compose the paths step by step. 151 152 153 Understanding Composite Paths 154 ============================= 155 156 Because of the way the rendering backend is built in NanoVega, drawing a composite path, 157 that is path consisting from multiple paths defining holes and fills, is a bit more 158 involved. NanoVega uses non-zero filling rule and by default, and paths are wound in counter 159 clockwise order. Keep that in mind when drawing using the low level draw API. In order to 160 wind one of the predefined shapes as a hole, you should call `pathWinding(NVGSolidity.Hole)`, 161 or `pathWinding(NVGSolidity.Solid)` $(B after) defining the path. 162 163 --- 164 vg.beginPath(); 165 vg.rect(100, 100, 120, 30); 166 vg.circle(120, 120, 5); 167 vg.pathWinding(NVGSolidity.Hole); // mark circle as a hole 168 vg.fillColor(nvgRGBA(255, 192, 0, 255)); 169 vg.fill(); 170 --- 171 172 173 Rendering is wrong, what to do? 174 =============================== 175 176 $(LIST 177 * make sure you have created NanoVega context using [nvgCreateContext] call 178 * make sure you have initialised OpenGL with $(B stencil buffer) 179 * make sure you have cleared stencil buffer 180 * make sure all rendering calls happen between [beginFrame] and [endFrame] 181 * to enable more checks for OpenGL errors, add `NVGContextFlag.Debug` flag to [nvgCreateContext] 182 ) 183 184 185 OpenGL state touched by the backend 186 =================================== 187 188 The OpenGL back-end touches following states: 189 190 When textures are uploaded or updated, the following pixel store is set to defaults: 191 `GL_UNPACK_ALIGNMENT`, `GL_UNPACK_ROW_LENGTH`, `GL_UNPACK_SKIP_PIXELS`, `GL_UNPACK_SKIP_ROWS`. 192 Texture binding is also affected. Texture updates can happen when the user loads images, 193 or when new font glyphs are added. Glyphs are added as needed between calls to [beginFrame] 194 and [endFrame]. 195 196 The data for the whole frame is buffered and flushed in [endFrame]. 197 The following code illustrates the OpenGL state touched by the rendering code: 198 199 --- 200 glUseProgram(prog); 201 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 202 glEnable(GL_CULL_FACE); 203 glCullFace(GL_BACK); 204 glFrontFace(GL_CCW); 205 glEnable(GL_BLEND); 206 glDisable(GL_DEPTH_TEST); 207 glDisable(GL_SCISSOR_TEST); 208 glDisable(GL_COLOR_LOGIC_OP); 209 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 210 glStencilMask(0xffffffff); 211 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 212 glStencilFunc(GL_ALWAYS, 0, 0xffffffff); 213 glActiveTexture(GL_TEXTURE1); 214 glActiveTexture(GL_TEXTURE0); 215 glBindBuffer(GL_UNIFORM_BUFFER, buf); 216 glBindVertexArray(arr); 217 glBindBuffer(GL_ARRAY_BUFFER, buf); 218 glBindTexture(GL_TEXTURE_2D, tex); 219 glUniformBlockBinding(... , GLNVG_FRAG_BINDING); 220 --- 221 222 Symbol_groups: 223 224 context_management = 225 ## Context Management 226 227 Functions to create and destory NanoVega context. 228 229 frame_management = 230 ## Frame Management 231 232 To start drawing with NanoVega context, you have to "begin frame", and then 233 "end frame" to flush your rendering commands to GPU. 234 235 composite_operation = 236 ## Composite Operation 237 238 The composite operations in NanoVega are modeled after HTML Canvas API, and 239 the blend func is based on OpenGL (see corresponding manuals for more info). 240 The colors in the blending state have premultiplied alpha. 241 242 color_utils = 243 ## Color Utils 244 245 Colors in NanoVega are stored as ARGB. Zero alpha means "transparent color". 246 247 matrices = 248 ## Matrices and Transformations 249 250 The paths, gradients, patterns and scissor region are transformed by an transformation 251 matrix at the time when they are passed to the API. 252 The current transformation matrix is an affine matrix: 253 254 ---------------------- 255 [sx kx tx] 256 [ky sy ty] 257 [ 0 0 1] 258 ---------------------- 259 260 Where: (sx, sy) define scaling, (kx, ky) skewing, and (tx, ty) translation. 261 The last row is assumed to be (0, 0, 1) and is not stored. 262 263 Apart from [resetTransform], each transformation function first creates 264 specific transformation matrix and pre-multiplies the current transformation by it. 265 266 Current coordinate system (transformation) can be saved and restored using [save] and [restore]. 267 268 The following functions can be used to make calculations on 2x3 transformation matrices. 269 A 2x3 matrix is represented as float[6]. 270 271 state_handling = 272 ## State Handling 273 274 NanoVega contains state which represents how paths will be rendered. 275 The state contains transform, fill and stroke styles, text and font styles, 276 and scissor clipping. 277 278 render_styles = 279 ## Render Styles 280 281 Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern. 282 Solid color is simply defined as a color value, different kinds of paints can be created 283 using [linearGradient], [boxGradient], [radialGradient] and [imagePattern]. 284 285 Current render style can be saved and restored using [save] and [restore]. 286 287 Note that if you want "almost perfect" pixel rendering, you should set aspect ratio to 1, 288 and use `integerCoord+0.5f` as pixel coordinates. 289 290 render_transformations = 291 ## Render Transformations 292 293 Transformation matrix management for the current rendering style. Transformations are applied in 294 backwards order. I.e. if you first translate, and then rotate, your path will be rotated around 295 it's origin, and then translated to the destination point. 296 297 scissoring = 298 ## Scissoring 299 300 Scissoring allows you to clip the rendering into a rectangle. This is useful for various 301 user interface cases like rendering a text edit or a timeline. 302 303 images = 304 ## Images 305 306 NanoVega allows you to load image files in various formats (if arsd loaders are in place) to be used for rendering. 307 In addition you can upload your own image. 308 The parameter imageFlagsList is a list of flags defined in [NVGImageFlag]. 309 310 If you will use your image as fill pattern, it will be scaled by default. To make it repeat, pass 311 [NVGImageFlag.RepeatX] and [NVGImageFlag.RepeatY] flags to image creation function respectively. 312 313 paints = 314 ## Paints 315 316 NanoVega supports four types of paints: linear gradient, box gradient, radial gradient and image pattern. 317 These can be used as paints for strokes and fills. 318 319 gpu_affine = 320 ## Render-Time Affine Transformations 321 322 It is possible to set affine transformation matrix for GPU. That matrix will 323 be applied by the shader code. This can be used to quickly translate and rotate 324 saved paths. Call this $(B only) between [beginFrame] and [endFrame]. 325 326 Note that [beginFrame] resets this matrix to identity one. 327 328 $(WARNING Don't use this for scaling or skewing, or your image will be heavily distorted!) 329 330 paths = 331 ## Paths 332 333 Drawing a new shape starts with [beginPath], it clears all the currently defined paths. 334 Then you define one or more paths and sub-paths which describe the shape. The are functions 335 to draw common shapes like rectangles and circles, and lower level step-by-step functions, 336 which allow to define a path curve by curve. 337 338 NanoVega uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise 339 winding and holes should have counter clockwise order. To specify winding of a path you can 340 call [pathWinding]. This is useful especially for the common shapes, which are drawn CCW. 341 342 Finally you can fill the path using current fill style by calling [fill], and stroke it 343 with current stroke style by calling [stroke]. 344 345 The curve segments and sub-paths are transformed by the current transform. 346 347 picking_api = 348 ## Picking API 349 350 This is picking API that works directly on paths, without rasterizing them first. 351 352 [beginFrame] resets picking state. Then you can create paths as usual, but 353 there is a possibility to perform hit checks $(B before) rasterizing a path. 354 Call either id assigning functions ([currFillHitId]/[currStrokeHitId]), or 355 immediate hit test functions ([hitTestCurrFill]/[hitTestCurrStroke]) 356 before rasterizing (i.e. calling [fill] or [stroke]) to perform hover 357 effects, for example. 358 359 Also note that picking API is ignoring GPU affine transformation matrix. 360 You can "untransform" picking coordinates before checking with [gpuUntransformPoint]. 361 362 $(WARNING Picking API completely ignores clipping. If you want to check for 363 clip regions, you have to manually register them as fill/stroke paths, 364 and perform the necessary logic. See [hitTestForId] function.) 365 366 clipping = 367 ## Clipping with paths 368 369 If scissoring is not enough for you, you can clip rendering with arbitrary path, 370 or with combination of paths. Clip region is saved by [save] and restored by 371 [restore] NanoVega functions. You can combine clip paths with various logic 372 operations, see [NVGClipMode]. 373 374 Note that both [clip] and [clipStroke] are ignoring scissoring (i.e. clip mask 375 is created as if there was no scissor set). Actual rendering is affected by 376 scissors, though. 377 378 text_api = 379 ## Text 380 381 NanoVega allows you to load .ttf files and use the font to render text. 382 You have to load some font, and set proper font size before doing anything 383 with text, as there is no "default" font provided by NanoVega. Also, don't 384 forget to check return value of `createFont()`, 'cause NanoVega won't fail 385 if it cannot load font, it will silently try to render nothing. 386 387 The appearance of the text can be defined by setting the current text style 388 and by specifying the fill color. Common text and font settings such as 389 font size, letter spacing and text align are supported. Font blur allows you 390 to create simple text effects such as drop shadows. 391 392 At render time the font face can be set based on the font handles or name. 393 394 Font measure functions return values in local space, the calculations are 395 carried in the same resolution as the final rendering. This is done because 396 the text glyph positions are snapped to the nearest pixels sharp rendering. 397 398 The local space means that values are not rotated or scale as per the current 399 transformation. For example if you set font size to 12, which would mean that 400 line height is 16, then regardless of the current scaling and rotation, the 401 returned line height is always 16. Some measures may vary because of the scaling 402 since aforementioned pixel snapping. 403 404 While this may sound a little odd, the setup allows you to always render the 405 same way regardless of scaling. I.e. following works regardless of scaling: 406 407 ---------------------- 408 string txt = "Text me up."; 409 vg.textBounds(x, y, txt, bounds); 410 vg.beginPath(); 411 vg.roundedRect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1], 6); 412 vg.fill(); 413 ---------------------- 414 415 Note: currently only solid color fill is supported for text. 416 417 font_stash = 418 ## Low-Level Font Engine (FontStash) 419 420 FontStash is used to load fonts, to manage font atlases, and to get various text metrics. 421 You don't need any graphics context to use FontStash, so you can do things like text 422 layouting outside of your rendering code. Loaded fonts are refcounted, so it is cheap 423 to create new FontStash, copy fonts from NanoVega context into it, and use that new 424 FontStash to do some UI layouting, for example. Also note that you can get text metrics 425 without creating glyph bitmaps (by using [FONSTextBoundsIterator], for example); this way 426 you don't need to waste CPU and memory resources to render unneeded images into font atlas, 427 and you can layout alot of text very fast. 428 429 Note that "FontStash" is abbrevated as "FONS". So when you see some API that contains 430 word "fons" in it, this is not a typo, and it should not read "font" intead. 431 432 TODO for Ketmar: write some nice example code here, and finish documenting FontStash API. 433 */ 434 module arsd.nanovega; 435 436 /// This example shows how to do the NanoVega sample without the [NVGWindow] helper class. 437 unittest { 438 import arsd.simpledisplay; 439 440 import arsd.nanovega; 441 442 void main () { 443 NVGContext nvg; // our NanoVega context 444 445 // we need at least OpenGL3 with GLSL to use NanoVega, 446 // so let's tell simpledisplay about that 447 setOpenGLContextVersion(3, 0); 448 449 // now create OpenGL window 450 auto sdmain = new SimpleWindow(800, 600, "NanoVega Simple Sample", OpenGlOptions.yes, Resizability.allowResizing); 451 452 // we need to destroy NanoVega context on window close 453 // stricly speaking, it is not necessary, as nothing fatal 454 // will happen if you'll forget it, but let's be polite. 455 // note that we cannot do that *after* our window was closed, 456 // as we need alive OpenGL context to do proper cleanup. 457 sdmain.onClosing = delegate () { 458 nvg.kill(); 459 }; 460 461 // this is called just before our window will be shown for the first time. 462 // we must create NanoVega context here, as it needs to initialize 463 // internal OpenGL subsystem with valid OpenGL context. 464 sdmain.visibleForTheFirstTime = delegate () { 465 // yes, that's all 466 nvg = nvgCreateContext(); 467 if (nvg is null) assert(0, "cannot initialize NanoVega"); 468 }; 469 470 // this callback will be called when we will need to repaint our window 471 sdmain.redrawOpenGlScene = delegate () { 472 // fix viewport (we can do this in resize event, or here, it doesn't matter) 473 glViewport(0, 0, sdmain.width, sdmain.height); 474 475 // clear window 476 glClearColor(0, 0, 0, 0); 477 glClear(glNVGClearFlags); // use NanoVega API to get flags for OpenGL call 478 479 { 480 nvg.beginFrame(sdmain.width, sdmain.height); // begin rendering 481 scope(exit) nvg.endFrame(); // and flush render queue on exit 482 483 nvg.beginPath(); // start new path 484 nvg.roundedRect(20.5, 30.5, sdmain.width-40, sdmain.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation) 485 // now set filling mode for our rectangle 486 // you can create colors using HTML syntax, or with convenient constants 487 nvg.fillPaint = nvg.linearGradient(20.5, 30.5, sdmain.width-40, sdmain.height-60, NVGColor("#f70"), NVGColor.green); 488 // now fill our rect 489 nvg.fill(); 490 // and draw a nice outline 491 nvg.strokeColor = NVGColor.white; 492 nvg.strokeWidth = 2; 493 nvg.stroke(); 494 // that's all, folks! 495 } 496 }; 497 498 sdmain.eventLoop(0, // no pulse timer required 499 delegate (KeyEvent event) { 500 if (event == "*-Q" || event == "Escape") { sdmain.close(); return; } // quit on Q, Ctrl+Q, and so on 501 }, 502 ); 503 504 flushGui(); // let OS do it's cleanup 505 } 506 } 507 508 private: 509 510 version(aliced) { 511 import iv.meta; 512 import iv.vfs; 513 } else { 514 private alias usize = size_t; 515 // i fear phobos! 516 private template Unqual(T) { 517 static if (is(T U == immutable U)) alias Unqual = U; 518 else static if (is(T U == shared inout const U)) alias Unqual = U; 519 else static if (is(T U == shared inout U)) alias Unqual = U; 520 else static if (is(T U == shared const U)) alias Unqual = U; 521 else static if (is(T U == shared U)) alias Unqual = U; 522 else static if (is(T U == inout const U)) alias Unqual = U; 523 else static if (is(T U == inout U)) alias Unqual = U; 524 else static if (is(T U == const U)) alias Unqual = U; 525 else alias Unqual = T; 526 } 527 private template isAnyCharType(T, bool unqual=false) { 528 static if (unqual) private alias UT = Unqual!T; else private alias UT = T; 529 enum isAnyCharType = is(UT == char) || is(UT == wchar) || is(UT == dchar); 530 } 531 private template isWideCharType(T, bool unqual=false) { 532 static if (unqual) private alias UT = Unqual!T; else private alias UT = T; 533 enum isWideCharType = is(UT == wchar) || is(UT == dchar); 534 } 535 } 536 version(nanovg_disable_vfs) { 537 enum NanoVegaHasIVVFS = false; 538 } else { 539 static if (is(typeof((){import iv.vfs;}))) { 540 enum NanoVegaHasIVVFS = true; 541 import iv.vfs; 542 } else { 543 enum NanoVegaHasIVVFS = false; 544 } 545 } 546 547 // ////////////////////////////////////////////////////////////////////////// // 548 // engine 549 // ////////////////////////////////////////////////////////////////////////// // 550 import core.stdc.stdlib : malloc, realloc, free; 551 import core.stdc.string : memset, memcpy, strlen; 552 import std.math : PI; 553 554 //version = nanovg_force_stb_ttf; 555 556 version(Posix) { 557 version = nanovg_use_freetype; 558 } else { 559 version = nanovg_disable_fontconfig; 560 } 561 562 version (bindbc) { 563 version = nanovg_builtin_fontconfig_bindings; 564 version = nanovg_bindbc_opengl_bindings; 565 version = nanovg_bindbc_freetype_bindings; 566 version(BindFT_Dynamic) 567 static assert(0, "AsumFace was too lazy to write the code for the dynamic bindbc freetype bindings"); 568 else { 569 version(BindFT_Static) {} 570 else 571 static assert(0, "well, duh. you got to pass the BindFT_Static version identifier to the compiler"); 572 } 573 } else version(aliced) { 574 version = nanovg_default_no_font_aa; 575 version = nanovg_builtin_fontconfig_bindings; 576 version = nanovg_builtin_freetype_bindings; 577 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings 578 } else { 579 version = nanovg_builtin_fontconfig_bindings; 580 version = nanovg_builtin_freetype_bindings; 581 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings 582 } 583 584 version(nanovg_disable_fontconfig) { 585 public enum NanoVegaHasFontConfig = false; 586 } else { 587 public enum NanoVegaHasFontConfig = true; 588 version(nanovg_builtin_fontconfig_bindings) {} else import iv.fontconfig; 589 } 590 591 //version = nanovg_bench_flatten; 592 593 /++ 594 Annotation to indicate the marked function is compatible with [arsd.script]. 595 596 597 Any function that takes a [Color] argument will be passed a string instead. 598 599 Scriptable Functions 600 ==================== 601 602 $(UDA_USES) 603 604 $(ALWAYS_DOCUMENT) 605 +/ 606 private enum scriptable = "arsd_jsvar_compatible"; 607 608 public: 609 alias NVG_PI = PI; 610 611 enum NanoVegaHasArsdColor = (is(typeof((){ import arsd.color; }))); 612 enum NanoVegaHasArsdImage = (is(typeof((){ import arsd.color; import arsd.image; }))); 613 614 static if (NanoVegaHasArsdColor) private import arsd.color; 615 static if (NanoVegaHasArsdImage) { 616 private import arsd.image; 617 } else { 618 void stbi_set_unpremultiply_on_load (int flag_true_if_should_unpremultiply) {} 619 void stbi_convert_iphone_png_to_rgb (int flag_true_if_should_convert) {} 620 ubyte* stbi_load (const(char)* filename, int* x, int* y, int* comp, int req_comp) { return null; } 621 ubyte* stbi_load_from_memory (const(void)* buffer, int len, int* x, int* y, int* comp, int req_comp) { return null; } 622 void stbi_image_free (void* retval_from_stbi_load) {} 623 } 624 625 version(nanovg_default_no_font_aa) { 626 __gshared bool NVG_INVERT_FONT_AA = false; 627 } else { 628 __gshared bool NVG_INVERT_FONT_AA = true; 629 } 630 631 632 /// this is branchless for ints on x86, and even for longs on x86_64 633 public ubyte nvgClampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) { 634 static if (__VERSION__ > 2067) pragma(inline, true); 635 static if (T.sizeof == 2 || T.sizeof == 4) { 636 static if (__traits(isUnsigned, T)) { 637 return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24))); 638 } else { 639 n &= -cast(int)(n >= 0); 640 return cast(ubyte)(n|((255-cast(int)n)>>31)); 641 } 642 } else static if (T.sizeof == 1) { 643 static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?"); 644 return cast(ubyte)n; 645 } else static if (T.sizeof == 8) { 646 static if (__traits(isUnsigned, T)) { 647 return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56))); 648 } else { 649 n &= -cast(long)(n >= 0); 650 return cast(ubyte)(n|((255-cast(long)n)>>63)); 651 } 652 } else { 653 static assert(false, "clampToByte: integer too big"); 654 } 655 } 656 657 658 /// NanoVega RGBA color 659 /// Group: color_utils 660 public align(1) struct NVGColor { 661 align(1): 662 public: 663 float[4] rgba = 0; /// default color is transparent (a=1 is opaque) 664 665 public: 666 @property string toString () const @safe { import std.string : format; return "NVGColor(%s,%s,%s,%s)".format(r, g, b, a); } 667 668 public: 669 enum transparent = NVGColor(0.0f, 0.0f, 0.0f, 0.0f); 670 enum k8orange = NVGColor(1.0f, 0.5f, 0.0f, 1.0f); 671 672 enum aliceblue = NVGColor(240, 248, 255); 673 enum antiquewhite = NVGColor(250, 235, 215); 674 enum aqua = NVGColor(0, 255, 255); 675 enum aquamarine = NVGColor(127, 255, 212); 676 enum azure = NVGColor(240, 255, 255); 677 enum beige = NVGColor(245, 245, 220); 678 enum bisque = NVGColor(255, 228, 196); 679 enum black = NVGColor(0, 0, 0); // basic color 680 enum blanchedalmond = NVGColor(255, 235, 205); 681 enum blue = NVGColor(0, 0, 255); // basic color 682 enum blueviolet = NVGColor(138, 43, 226); 683 enum brown = NVGColor(165, 42, 42); 684 enum burlywood = NVGColor(222, 184, 135); 685 enum cadetblue = NVGColor(95, 158, 160); 686 enum chartreuse = NVGColor(127, 255, 0); 687 enum chocolate = NVGColor(210, 105, 30); 688 enum coral = NVGColor(255, 127, 80); 689 enum cornflowerblue = NVGColor(100, 149, 237); 690 enum cornsilk = NVGColor(255, 248, 220); 691 enum crimson = NVGColor(220, 20, 60); 692 enum cyan = NVGColor(0, 255, 255); // basic color 693 enum darkblue = NVGColor(0, 0, 139); 694 enum darkcyan = NVGColor(0, 139, 139); 695 enum darkgoldenrod = NVGColor(184, 134, 11); 696 enum darkgray = NVGColor(169, 169, 169); 697 enum darkgreen = NVGColor(0, 100, 0); 698 enum darkgrey = NVGColor(169, 169, 169); 699 enum darkkhaki = NVGColor(189, 183, 107); 700 enum darkmagenta = NVGColor(139, 0, 139); 701 enum darkolivegreen = NVGColor(85, 107, 47); 702 enum darkorange = NVGColor(255, 140, 0); 703 enum darkorchid = NVGColor(153, 50, 204); 704 enum darkred = NVGColor(139, 0, 0); 705 enum darksalmon = NVGColor(233, 150, 122); 706 enum darkseagreen = NVGColor(143, 188, 143); 707 enum darkslateblue = NVGColor(72, 61, 139); 708 enum darkslategray = NVGColor(47, 79, 79); 709 enum darkslategrey = NVGColor(47, 79, 79); 710 enum darkturquoise = NVGColor(0, 206, 209); 711 enum darkviolet = NVGColor(148, 0, 211); 712 enum deeppink = NVGColor(255, 20, 147); 713 enum deepskyblue = NVGColor(0, 191, 255); 714 enum dimgray = NVGColor(105, 105, 105); 715 enum dimgrey = NVGColor(105, 105, 105); 716 enum dodgerblue = NVGColor(30, 144, 255); 717 enum firebrick = NVGColor(178, 34, 34); 718 enum floralwhite = NVGColor(255, 250, 240); 719 enum forestgreen = NVGColor(34, 139, 34); 720 enum fuchsia = NVGColor(255, 0, 255); 721 enum gainsboro = NVGColor(220, 220, 220); 722 enum ghostwhite = NVGColor(248, 248, 255); 723 enum gold = NVGColor(255, 215, 0); 724 enum goldenrod = NVGColor(218, 165, 32); 725 enum gray = NVGColor(128, 128, 128); // basic color 726 enum green = NVGColor(0, 128, 0); // basic color 727 enum greenyellow = NVGColor(173, 255, 47); 728 enum grey = NVGColor(128, 128, 128); // basic color 729 enum honeydew = NVGColor(240, 255, 240); 730 enum hotpink = NVGColor(255, 105, 180); 731 enum indianred = NVGColor(205, 92, 92); 732 enum indigo = NVGColor(75, 0, 130); 733 enum ivory = NVGColor(255, 255, 240); 734 enum khaki = NVGColor(240, 230, 140); 735 enum lavender = NVGColor(230, 230, 250); 736 enum lavenderblush = NVGColor(255, 240, 245); 737 enum lawngreen = NVGColor(124, 252, 0); 738 enum lemonchiffon = NVGColor(255, 250, 205); 739 enum lightblue = NVGColor(173, 216, 230); 740 enum lightcoral = NVGColor(240, 128, 128); 741 enum lightcyan = NVGColor(224, 255, 255); 742 enum lightgoldenrodyellow = NVGColor(250, 250, 210); 743 enum lightgray = NVGColor(211, 211, 211); 744 enum lightgreen = NVGColor(144, 238, 144); 745 enum lightgrey = NVGColor(211, 211, 211); 746 enum lightpink = NVGColor(255, 182, 193); 747 enum lightsalmon = NVGColor(255, 160, 122); 748 enum lightseagreen = NVGColor(32, 178, 170); 749 enum lightskyblue = NVGColor(135, 206, 250); 750 enum lightslategray = NVGColor(119, 136, 153); 751 enum lightslategrey = NVGColor(119, 136, 153); 752 enum lightsteelblue = NVGColor(176, 196, 222); 753 enum lightyellow = NVGColor(255, 255, 224); 754 enum lime = NVGColor(0, 255, 0); 755 enum limegreen = NVGColor(50, 205, 50); 756 enum linen = NVGColor(250, 240, 230); 757 enum magenta = NVGColor(255, 0, 255); // basic color 758 enum maroon = NVGColor(128, 0, 0); 759 enum mediumaquamarine = NVGColor(102, 205, 170); 760 enum mediumblue = NVGColor(0, 0, 205); 761 enum mediumorchid = NVGColor(186, 85, 211); 762 enum mediumpurple = NVGColor(147, 112, 219); 763 enum mediumseagreen = NVGColor(60, 179, 113); 764 enum mediumslateblue = NVGColor(123, 104, 238); 765 enum mediumspringgreen = NVGColor(0, 250, 154); 766 enum mediumturquoise = NVGColor(72, 209, 204); 767 enum mediumvioletred = NVGColor(199, 21, 133); 768 enum midnightblue = NVGColor(25, 25, 112); 769 enum mintcream = NVGColor(245, 255, 250); 770 enum mistyrose = NVGColor(255, 228, 225); 771 enum moccasin = NVGColor(255, 228, 181); 772 enum navajowhite = NVGColor(255, 222, 173); 773 enum navy = NVGColor(0, 0, 128); 774 enum oldlace = NVGColor(253, 245, 230); 775 enum olive = NVGColor(128, 128, 0); 776 enum olivedrab = NVGColor(107, 142, 35); 777 enum orange = NVGColor(255, 165, 0); 778 enum orangered = NVGColor(255, 69, 0); 779 enum orchid = NVGColor(218, 112, 214); 780 enum palegoldenrod = NVGColor(238, 232, 170); 781 enum palegreen = NVGColor(152, 251, 152); 782 enum paleturquoise = NVGColor(175, 238, 238); 783 enum palevioletred = NVGColor(219, 112, 147); 784 enum papayawhip = NVGColor(255, 239, 213); 785 enum peachpuff = NVGColor(255, 218, 185); 786 enum peru = NVGColor(205, 133, 63); 787 enum pink = NVGColor(255, 192, 203); 788 enum plum = NVGColor(221, 160, 221); 789 enum powderblue = NVGColor(176, 224, 230); 790 enum purple = NVGColor(128, 0, 128); 791 enum red = NVGColor(255, 0, 0); // basic color 792 enum rosybrown = NVGColor(188, 143, 143); 793 enum royalblue = NVGColor(65, 105, 225); 794 enum saddlebrown = NVGColor(139, 69, 19); 795 enum salmon = NVGColor(250, 128, 114); 796 enum sandybrown = NVGColor(244, 164, 96); 797 enum seagreen = NVGColor(46, 139, 87); 798 enum seashell = NVGColor(255, 245, 238); 799 enum sienna = NVGColor(160, 82, 45); 800 enum silver = NVGColor(192, 192, 192); 801 enum skyblue = NVGColor(135, 206, 235); 802 enum slateblue = NVGColor(106, 90, 205); 803 enum slategray = NVGColor(112, 128, 144); 804 enum slategrey = NVGColor(112, 128, 144); 805 enum snow = NVGColor(255, 250, 250); 806 enum springgreen = NVGColor(0, 255, 127); 807 enum steelblue = NVGColor(70, 130, 180); 808 enum tan = NVGColor(210, 180, 140); 809 enum teal = NVGColor(0, 128, 128); 810 enum thistle = NVGColor(216, 191, 216); 811 enum tomato = NVGColor(255, 99, 71); 812 enum turquoise = NVGColor(64, 224, 208); 813 enum violet = NVGColor(238, 130, 238); 814 enum wheat = NVGColor(245, 222, 179); 815 enum white = NVGColor(255, 255, 255); // basic color 816 enum whitesmoke = NVGColor(245, 245, 245); 817 enum yellow = NVGColor(255, 255, 0); // basic color 818 enum yellowgreen = NVGColor(154, 205, 50); 819 820 nothrow @safe @nogc: 821 public: 822 /// 823 this (ubyte ar, ubyte ag, ubyte ab, ubyte aa=255) pure { 824 pragma(inline, true); 825 r = ar/255.0f; 826 g = ag/255.0f; 827 b = ab/255.0f; 828 a = aa/255.0f; 829 } 830 831 /// 832 this (float ar, float ag, float ab, float aa=1.0f) pure { 833 pragma(inline, true); 834 r = ar; 835 g = ag; 836 b = ab; 837 a = aa; 838 } 839 840 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 841 this (uint c) pure { 842 pragma(inline, true); 843 r = (c&0xff)/255.0f; 844 g = ((c>>8)&0xff)/255.0f; 845 b = ((c>>16)&0xff)/255.0f; 846 a = ((c>>24)&0xff)/255.0f; 847 } 848 849 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb" 850 this (const(char)[] srgb) { 851 static int c2d (char ch) pure nothrow @safe @nogc { 852 pragma(inline, true); 853 return 854 ch >= '0' && ch <= '9' ? ch-'0' : 855 ch >= 'A' && ch <= 'F' ? ch-'A'+10 : 856 ch >= 'a' && ch <= 'f' ? ch-'a'+10 : 857 -1; 858 } 859 int[8] digs; 860 int dc = -1; 861 foreach (immutable char ch; srgb) { 862 if (ch <= ' ') continue; 863 if (ch == '#') { 864 if (dc != -1) { dc = -1; break; } 865 dc = 0; 866 } else { 867 if (dc >= digs.length) { dc = -1; break; } 868 if ((digs[dc++] = c2d(ch)) < 0) { dc = -1; break; } 869 } 870 } 871 switch (dc) { 872 case 3: // rgb 873 a = 1.0f; 874 r = digs[0]/15.0f; 875 g = digs[1]/15.0f; 876 b = digs[2]/15.0f; 877 break; 878 case 4: // argb 879 a = digs[0]/15.0f; 880 r = digs[1]/15.0f; 881 g = digs[2]/15.0f; 882 b = digs[3]/15.0f; 883 break; 884 case 6: // rrggbb 885 a = 1.0f; 886 r = (digs[0]*16+digs[1])/255.0f; 887 g = (digs[2]*16+digs[3])/255.0f; 888 b = (digs[4]*16+digs[5])/255.0f; 889 break; 890 case 8: // aarrggbb 891 a = (digs[0]*16+digs[1])/255.0f; 892 r = (digs[2]*16+digs[3])/255.0f; 893 g = (digs[4]*16+digs[5])/255.0f; 894 b = (digs[6]*16+digs[7])/255.0f; 895 break; 896 default: 897 break; 898 } 899 } 900 901 /// Is this color completely opaque? 902 @property bool isOpaque () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] >= 1.0f); } 903 /// Is this color completely transparent? 904 @property bool isTransparent () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] <= 0.0f); } 905 906 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 907 @property uint asUint () const pure { 908 pragma(inline, true); 909 return 910 cast(uint)(r*255)| 911 (cast(uint)(g*255)<<8)| 912 (cast(uint)(b*255)<<16)| 913 (cast(uint)(a*255)<<24); 914 } 915 916 alias asUintABGR = asUint; /// Ditto. 917 918 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 919 static NVGColor fromUint (uint c) pure { pragma(inline, true); return NVGColor(c); } 920 921 alias fromUintABGR = fromUint; /// Ditto. 922 923 /// AARRGGBB 924 @property uint asUintARGB () const pure { 925 pragma(inline, true); 926 return 927 cast(uint)(b*255)| 928 (cast(uint)(g*255)<<8)| 929 (cast(uint)(r*255)<<16)| 930 (cast(uint)(a*255)<<24); 931 } 932 933 /// AARRGGBB 934 static NVGColor fromUintARGB (uint c) pure { pragma(inline, true); return NVGColor((c>>16)&0xff, (c>>8)&0xff, c&0xff, (c>>24)&0xff); } 935 936 @property ref inout(float) r () inout pure @trusted { pragma(inline, true); return rgba.ptr[0]; } /// 937 @property ref inout(float) g () inout pure @trusted { pragma(inline, true); return rgba.ptr[1]; } /// 938 @property ref inout(float) b () inout pure @trusted { pragma(inline, true); return rgba.ptr[2]; } /// 939 @property ref inout(float) a () inout pure @trusted { pragma(inline, true); return rgba.ptr[3]; } /// 940 941 ref NVGColor applyTint() (in auto ref NVGColor tint) nothrow @trusted @nogc { 942 if (tint.a == 0) return this; 943 foreach (immutable idx, ref float v; rgba[0..4]) { 944 v = nvg__clamp(v*tint.rgba.ptr[idx], 0.0f, 1.0f); 945 } 946 return this; 947 } 948 949 NVGHSL asHSL() (bool useWeightedLightness=false) const { pragma(inline, true); return NVGHSL.fromColor(this, useWeightedLightness); } /// 950 static fromHSL() (in auto ref NVGHSL hsl) { pragma(inline, true); return hsl.asColor; } /// 951 952 static if (NanoVegaHasArsdColor) { 953 Color toArsd () const { pragma(inline, true); return Color(cast(int)(r*255), cast(int)(g*255), cast(int)(b*255), cast(int)(a*255)); } /// 954 static NVGColor fromArsd (in Color c) { pragma(inline, true); return NVGColor(c.r, c.g, c.b, c.a); } /// 955 /// 956 this (in Color c) { 957 version(aliced) pragma(inline, true); 958 r = c.r/255.0f; 959 g = c.g/255.0f; 960 b = c.b/255.0f; 961 a = c.a/255.0f; 962 } 963 } 964 } 965 966 967 /// NanoVega A-HSL color 968 /// Group: color_utils 969 public align(1) struct NVGHSL { 970 align(1): 971 float h=0, s=0, l=1, a=1; /// 972 973 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)); } 974 975 nothrow @safe @nogc: 976 public: 977 /// 978 this (float ah, float as, float al, float aa=1) pure { pragma(inline, true); h = ah; s = as; l = al; a = aa; } 979 980 NVGColor asColor () const { pragma(inline, true); return nvgHSLA(h, s, l, a); } /// 981 982 // taken from Adam's arsd.color 983 /** Converts an RGB color into an HSL triplet. 984 * [useWeightedLightness] will try to get a better value for luminosity for the human eye, 985 * which is more sensitive to green than red and more to red than blue. 986 * If it is false, it just does average of the rgb. */ 987 static NVGHSL fromColor() (in auto ref NVGColor c, bool useWeightedLightness=false) pure { 988 NVGHSL res; 989 res.a = c.a; 990 float r1 = c.r; 991 float g1 = c.g; 992 float b1 = c.b; 993 994 float maxColor = r1; 995 if (g1 > maxColor) maxColor = g1; 996 if (b1 > maxColor) maxColor = b1; 997 float minColor = r1; 998 if (g1 < minColor) minColor = g1; 999 if (b1 < minColor) minColor = b1; 1000 1001 res.l = (maxColor+minColor)/2; 1002 if (useWeightedLightness) { 1003 // the colors don't affect the eye equally 1004 // this is a little more accurate than plain HSL numbers 1005 res.l = 0.2126*r1+0.7152*g1+0.0722*b1; 1006 } 1007 if (maxColor != minColor) { 1008 if (res.l < 0.5) { 1009 res.s = (maxColor-minColor)/(maxColor+minColor); 1010 } else { 1011 res.s = (maxColor-minColor)/(2.0-maxColor-minColor); 1012 } 1013 if (r1 == maxColor) { 1014 res.h = (g1-b1)/(maxColor-minColor); 1015 } else if(g1 == maxColor) { 1016 res.h = 2.0+(b1-r1)/(maxColor-minColor); 1017 } else { 1018 res.h = 4.0+(r1-g1)/(maxColor-minColor); 1019 } 1020 } 1021 1022 res.h = res.h*60; 1023 if (res.h < 0) res.h += 360; 1024 res.h /= 360; 1025 1026 return res; 1027 } 1028 } 1029 1030 1031 //version = nanovega_debug_image_manager; 1032 //version = nanovega_debug_image_manager_rc; 1033 1034 /** NanoVega image handle. 1035 * 1036 * This is refcounted struct, so you don't need to do anything special to free it once it is allocated. 1037 * 1038 * Group: images 1039 */ 1040 struct NVGImage { 1041 enum isOpaqueStruct = true; 1042 private: 1043 NVGContext ctx; 1044 int id; // backend image id 1045 1046 public: 1047 /// 1048 this() (in auto ref NVGImage src) nothrow @trusted @nogc { 1049 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); } 1050 if (src.id > 0 && src.ctx !is null) { 1051 ctx = cast(NVGContext)src.ctx; 1052 id = src.id; 1053 ctx.nvg__imageIncRef(id); 1054 } 1055 } 1056 1057 /// 1058 ~this () nothrow @trusted @nogc { version(aliced) pragma(inline, true); clear(); } 1059 1060 /// 1061 this (this) nothrow @trusted @nogc { 1062 version(aliced) pragma(inline, true); 1063 if (id > 0 && ctx !is null) { 1064 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p postblit (imgid=%d)\n", &this, id); } 1065 ctx.nvg__imageIncRef(id); 1066 } 1067 } 1068 1069 /// 1070 void opAssign() (in auto ref NVGImage src) nothrow @trusted @nogc { 1071 if (src.id <= 0 || src.ctx is null) { 1072 clear(); 1073 } else { 1074 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); } 1075 if (src.id > 0 && src.ctx !is null) (cast(NVGContext)src.ctx).nvg__imageIncRef(src.id); 1076 if (id > 0 && ctx !is null) ctx.nvg__imageDecRef(id); 1077 ctx = cast(NVGContext)src.ctx; 1078 id = src.id; 1079 } 1080 } 1081 1082 /// Free this image. 1083 void clear () nothrow @trusted @nogc { 1084 if (id > 0 && ctx !is null) { 1085 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p cleared (imgid=%d)\n", &this, id); } 1086 ctx.nvg__imageDecRef(id); 1087 } 1088 id = 0; 1089 ctx = null; 1090 } 1091 1092 /// Is this image valid? 1093 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (id > 0 && ctx.valid); } 1094 1095 /// Is the given image valid and comes from the same context? 1096 @property bool isSameContext (const(NVGContext) actx) const pure nothrow @safe @nogc { pragma(inline, true); return (actx !is null && ctx is actx); } 1097 1098 /// Returns image width, or zero for invalid image. 1099 int width () const nothrow @trusted @nogc { 1100 int w = 0; 1101 if (valid) { 1102 int h = void; 1103 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h); 1104 } 1105 return w; 1106 } 1107 1108 /// Returns image height, or zero for invalid image. 1109 int height () const nothrow @trusted @nogc { 1110 int h = 0; 1111 if (valid) { 1112 int w = void; 1113 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h); 1114 } 1115 return h; 1116 } 1117 } 1118 1119 1120 /// Paint parameters for various fills. Don't change anything here! 1121 /// Group: render_styles 1122 public struct NVGPaint { 1123 enum isOpaqueStruct = true; 1124 1125 NVGMatrix xform; 1126 float[2] extent = 0.0f; 1127 float radius = 0.0f; 1128 float feather = 0.0f; 1129 NVGColor innerColor; /// this can be used to modulate images (fill/font) 1130 NVGColor middleColor; 1131 NVGColor outerColor; 1132 float midp = -1; // middle stop for 3-color gradient 1133 NVGImage image; 1134 bool simpleColor; /// if `true`, only innerColor is used, and this is solid-color paint 1135 1136 this() (in auto ref NVGPaint p) nothrow @trusted @nogc { 1137 xform = p.xform; 1138 extent[] = p.extent[]; 1139 radius = p.radius; 1140 feather = p.feather; 1141 innerColor = p.innerColor; 1142 middleColor = p.middleColor; 1143 midp = p.midp; 1144 outerColor = p.outerColor; 1145 image = p.image; 1146 simpleColor = p.simpleColor; 1147 } 1148 1149 void opAssign() (in auto ref NVGPaint p) nothrow @trusted @nogc { 1150 xform = p.xform; 1151 extent[] = p.extent[]; 1152 radius = p.radius; 1153 feather = p.feather; 1154 innerColor = p.innerColor; 1155 middleColor = p.middleColor; 1156 midp = p.midp; 1157 outerColor = p.outerColor; 1158 image = p.image; 1159 simpleColor = p.simpleColor; 1160 } 1161 1162 void clear () nothrow @trusted @nogc { 1163 version(aliced) pragma(inline, true); 1164 import core.stdc.string : memset; 1165 image.clear(); 1166 memset(&this, 0, this.sizeof); 1167 simpleColor = true; 1168 } 1169 } 1170 1171 /// Path winding. 1172 /// Group: paths 1173 public enum NVGWinding { 1174 CCW = 1, /// Winding for solid shapes 1175 CW = 2, /// Winding for holes 1176 } 1177 1178 /// Path solidity. 1179 /// Group: paths 1180 public enum NVGSolidity { 1181 Solid = 1, /// Solid shape (CCW winding). 1182 Hole = 2, /// Hole (CW winding). 1183 } 1184 1185 /// Line cap style. 1186 /// Group: render_styles 1187 public enum NVGLineCap { 1188 Butt, /// 1189 Round, /// 1190 Square, /// 1191 Bevel, /// 1192 Miter, /// 1193 } 1194 1195 /// Text align. 1196 /// Group: text_api 1197 public align(1) struct NVGTextAlign { 1198 align(1): 1199 /// Horizontal align. 1200 enum H : ubyte { 1201 Left = 0, /// Default, align text horizontally to left. 1202 Center = 1, /// Align text horizontally to center. 1203 Right = 2, /// Align text horizontally to right. 1204 } 1205 1206 /// Vertical align. 1207 enum V : ubyte { 1208 Baseline = 0, /// Default, align text vertically to baseline. 1209 Top = 1, /// Align text vertically to top. 1210 Middle = 2, /// Align text vertically to middle. 1211 Bottom = 3, /// Align text vertically to bottom. 1212 } 1213 1214 pure nothrow @safe @nogc: 1215 public: 1216 this (H h) { pragma(inline, true); value = h; } /// 1217 this (V v) { pragma(inline, true); value = cast(ubyte)(v<<4); } /// 1218 this (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1219 this (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1220 void reset () { pragma(inline, true); value = 0; } /// 1221 void reset (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1222 void reset (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1223 @property: 1224 bool left () const { pragma(inline, true); return ((value&0x0f) == H.Left); } /// 1225 void left (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Left : 0)); } /// 1226 bool center () const { pragma(inline, true); return ((value&0x0f) == H.Center); } /// 1227 void center (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Center : 0)); } /// 1228 bool right () const { pragma(inline, true); return ((value&0x0f) == H.Right); } /// 1229 void right (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Right : 0)); } /// 1230 // 1231 bool baseline () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Baseline); } /// 1232 void baseline (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Baseline<<4 : 0)); } /// 1233 bool top () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Top); } /// 1234 void top (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Top<<4 : 0)); } /// 1235 bool middle () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Middle); } /// 1236 void middle (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Middle<<4 : 0)); } /// 1237 bool bottom () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Bottom); } /// 1238 void bottom (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Bottom<<4 : 0)); } /// 1239 // 1240 H horizontal () const { pragma(inline, true); return cast(H)(value&0x0f); } /// 1241 void horizontal (H v) { pragma(inline, true); value = (value&0xf0)|v; } /// 1242 // 1243 V vertical () const { pragma(inline, true); return cast(V)((value>>4)&0x0f); } /// 1244 void vertical (V v) { pragma(inline, true); value = (value&0x0f)|cast(ubyte)(v<<4); } /// 1245 // 1246 private: 1247 ubyte value = 0; // low nibble: horizontal; high nibble: vertical 1248 } 1249 1250 /// Blending type. 1251 /// Group: composite_operation 1252 public enum NVGBlendFactor { 1253 Zero = 1<<0, /// 1254 One = 1<<1, /// 1255 SrcColor = 1<<2, /// 1256 OneMinusSrcColor = 1<<3, /// 1257 DstColor = 1<<4, /// 1258 OneMinusDstColor = 1<<5, /// 1259 SrcAlpha = 1<<6, /// 1260 OneMinusSrcAlpha = 1<<7, /// 1261 DstAlpha = 1<<8, /// 1262 OneMinusDstAlpha = 1<<9, /// 1263 SrcAlphaSaturate = 1<<10, /// 1264 } 1265 1266 /// Composite operation (HTML5-alike). 1267 /// Group: composite_operation 1268 public enum NVGCompositeOperation { 1269 SourceOver, /// 1270 SourceIn, /// 1271 SourceOut, /// 1272 SourceAtop, /// 1273 DestinationOver, /// 1274 DestinationIn, /// 1275 DestinationOut, /// 1276 DestinationAtop, /// 1277 Lighter, /// 1278 Copy, /// 1279 Xor, /// 1280 } 1281 1282 /// Composite operation state. 1283 /// Group: composite_operation 1284 public struct NVGCompositeOperationState { 1285 bool simple; /// `true`: use `glBlendFunc()` instead of `glBlendFuncSeparate()` 1286 NVGBlendFactor srcRGB; /// 1287 NVGBlendFactor dstRGB; /// 1288 NVGBlendFactor srcAlpha; /// 1289 NVGBlendFactor dstAlpha; /// 1290 } 1291 1292 /// Mask combining more 1293 /// Group: clipping 1294 public enum NVGClipMode { 1295 None, /// normal rendering (i.e. render path instead of modifying clip region) 1296 Union, /// old mask will be masked with the current one; this is the default mode for [clip] 1297 Or, /// new mask will be added to the current one (logical `OR` operation); 1298 Xor, /// new mask will be logically `XOR`ed with the current one 1299 Sub, /// "subtract" current path from mask 1300 Replace, /// replace current mask 1301 Add = Or, /// Synonym 1302 } 1303 1304 /// Glyph position info. 1305 /// Group: text_api 1306 public struct NVGGlyphPosition { 1307 usize strpos; /// Position of the glyph in the input string. 1308 float x; /// The x-coordinate of the logical glyph position. 1309 float minx, maxx; /// The bounds of the glyph shape. 1310 } 1311 1312 /// Text row storage. 1313 /// Group: text_api 1314 public struct NVGTextRow(CT) if (isAnyCharType!CT) { 1315 alias CharType = CT; 1316 const(CT)[] s; 1317 int start; /// Index in the input text where the row starts. 1318 int end; /// Index in the input text where the row ends (one past the last character). 1319 float width; /// Logical width of the row. 1320 float minx, maxx; /// Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending. 1321 /// Get rest of the string. 1322 @property const(CT)[] rest () const pure nothrow @trusted @nogc { pragma(inline, true); return (end <= s.length ? s[end..$] : null); } 1323 /// Get current row. 1324 @property const(CT)[] row () const pure nothrow @trusted @nogc { pragma(inline, true); return s[start..end]; } 1325 @property const(CT)[] string () const pure nothrow @trusted @nogc { pragma(inline, true); return s; } 1326 @property void string(CT) (const(CT)[] v) pure nothrow @trusted @nogc { pragma(inline, true); s = v; } 1327 } 1328 1329 /// Image creation flags. 1330 /// Group: images 1331 public enum NVGImageFlag : uint { 1332 None = 0, /// Nothing special. 1333 GenerateMipmaps = 1<<0, /// Generate mipmaps during creation of the image. 1334 RepeatX = 1<<1, /// Repeat image in X direction. 1335 RepeatY = 1<<2, /// Repeat image in Y direction. 1336 FlipY = 1<<3, /// Flips (inverses) image in Y direction when rendered. 1337 Premultiplied = 1<<4, /// Image data has premultiplied alpha. 1338 ClampToBorderX = 1<<5, /// Clamp image to border (instead of clamping to edge by default) 1339 ClampToBorderY = 1<<6, /// Clamp image to border (instead of clamping to edge by default) 1340 NoFiltering = 1<<8, /// use GL_NEAREST instead of GL_LINEAR. Only affects upscaling if GenerateMipmaps is active. 1341 Nearest = NoFiltering, /// compatibility with original NanoVG 1342 NoDelete = 1<<16,/// Do not delete GL texture handle. 1343 } 1344 1345 alias NVGImageFlags = NVGImageFlag; /// Backwards compatibility for [NVGImageFlag]. 1346 1347 1348 // ////////////////////////////////////////////////////////////////////////// // 1349 private: 1350 1351 static T* xdup(T) (const(T)* ptr, int count) nothrow @trusted @nogc { 1352 import core.stdc.stdlib : malloc; 1353 import core.stdc.string : memcpy; 1354 if (count == 0) return null; 1355 T* res = cast(T*)malloc(T.sizeof*count); 1356 if (res is null) assert(0, "NanoVega: out of memory"); 1357 memcpy(res, ptr, T.sizeof*count); 1358 return res; 1359 } 1360 1361 // Internal Render API 1362 enum NVGtexture { 1363 Alpha = 0x01, 1364 RGBA = 0x02, 1365 } 1366 1367 struct NVGscissor { 1368 NVGMatrix xform; 1369 float[2] extent = -1.0f; 1370 } 1371 1372 /// General NanoVega vertex struct. Contains geometry coordinates, and (sometimes unused) texture coordinates. 1373 public struct NVGVertex { 1374 float x, y, u, v; 1375 } 1376 1377 struct NVGpath { 1378 int first; 1379 int count; 1380 bool closed; 1381 int nbevel; 1382 NVGVertex* fill; 1383 int nfill; 1384 NVGVertex* stroke; 1385 int nstroke; 1386 NVGWinding mWinding; 1387 bool convex; 1388 bool cloned; 1389 1390 @disable this (this); // no copies 1391 void opAssign() (in auto ref NVGpath a) { static assert(0, "no copies!"); } 1392 1393 void clear () nothrow @trusted @nogc { 1394 import core.stdc.stdlib : free; 1395 import core.stdc.string : memset; 1396 if (cloned) { 1397 if (stroke !is null && stroke !is fill) free(stroke); 1398 if (fill !is null) free(fill); 1399 } 1400 memset(&this, 0, this.sizeof); 1401 } 1402 1403 // won't clear current path 1404 void copyFrom (const NVGpath* src) nothrow @trusted @nogc { 1405 import core.stdc.string : memcpy; 1406 assert(src !is null); 1407 memcpy(&this, src, NVGpath.sizeof); 1408 this.fill = xdup(src.fill, src.nfill); 1409 if (src.stroke is src.fill) { 1410 this.stroke = this.fill; 1411 } else { 1412 this.stroke = xdup(src.stroke, src.nstroke); 1413 } 1414 this.cloned = true; 1415 } 1416 1417 public @property const(NVGVertex)[] fillVertices () const pure nothrow @trusted @nogc { 1418 pragma(inline, true); 1419 return (nfill > 0 ? fill[0..nfill] : null); 1420 } 1421 1422 public @property const(NVGVertex)[] strokeVertices () const pure nothrow @trusted @nogc { 1423 pragma(inline, true); 1424 return (nstroke > 0 ? stroke[0..nstroke] : null); 1425 } 1426 1427 public @property NVGWinding winding () const pure nothrow @trusted @nogc { pragma(inline, true); return mWinding; } 1428 public @property bool complex () const pure nothrow @trusted @nogc { pragma(inline, true); return !convex; } 1429 } 1430 1431 1432 struct NVGparams { 1433 void* userPtr; 1434 bool edgeAntiAlias; 1435 bool fontAA; 1436 bool function (void* uptr) nothrow @trusted @nogc renderCreate; 1437 int function (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc renderCreateTexture; 1438 bool function (void* uptr, int image) nothrow @trusted @nogc renderTextureIncRef; 1439 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 1440 bool function (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc renderUpdateTexture; 1441 bool function (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc renderGetTextureSize; 1442 void function (void* uptr, int width, int height) nothrow @trusted @nogc renderViewport; // called in [beginFrame] 1443 void function (void* uptr) nothrow @trusted @nogc renderCancel; 1444 void function (void* uptr) nothrow @trusted @nogc renderFlush; 1445 void function (void* uptr) nothrow @trusted @nogc renderPushClip; // backend should support stack of at least [NVG_MAX_STATES] elements 1446 void function (void* uptr) nothrow @trusted @nogc renderPopClip; // backend should support stack of at least [NVG_MAX_STATES] elements 1447 void function (void* uptr) nothrow @trusted @nogc renderResetClip; // reset current clip region to `non-clipped` 1448 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; 1449 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; 1450 void function (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc renderTriangles; 1451 void function (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc renderSetAffine; 1452 void function (void* uptr) nothrow @trusted @nogc renderDelete; 1453 } 1454 1455 // ////////////////////////////////////////////////////////////////////////// // 1456 private: 1457 1458 enum NVG_INIT_FONTIMAGE_SIZE = 512; 1459 enum NVG_MAX_FONTIMAGE_SIZE = 2048; 1460 enum NVG_MAX_FONTIMAGES = 4; 1461 1462 enum NVG_INIT_COMMANDS_SIZE = 256; 1463 enum NVG_INIT_POINTS_SIZE = 128; 1464 enum NVG_INIT_PATHS_SIZE = 16; 1465 enum NVG_INIT_VERTS_SIZE = 256; 1466 enum NVG_MAX_STATES = 32; 1467 1468 public enum NVG_KAPPA90 = 0.5522847493f; /// Length proportional to radius of a cubic bezier handle for 90deg arcs. 1469 enum NVG_MIN_FEATHER = 0.001f; // it should be greater than zero, 'cause it is used in shader for divisions 1470 1471 enum Command { 1472 MoveTo = 0, 1473 LineTo = 1, 1474 BezierTo = 2, 1475 Close = 3, 1476 Winding = 4, 1477 } 1478 1479 enum PointFlag : int { 1480 Corner = 0x01, 1481 Left = 0x02, 1482 Bevel = 0x04, 1483 InnerBevelPR = 0x08, 1484 } 1485 1486 struct NVGstate { 1487 NVGCompositeOperationState compositeOperation; 1488 bool shapeAntiAlias = true; 1489 NVGPaint fill; 1490 NVGPaint stroke; 1491 float strokeWidth = 1.0f; 1492 float miterLimit = 10.0f; 1493 NVGLineCap lineJoin = NVGLineCap.Miter; 1494 NVGLineCap lineCap = NVGLineCap.Butt; 1495 float alpha = 1.0f; 1496 NVGMatrix xform; 1497 NVGscissor scissor; 1498 float fontSize = 16.0f; 1499 float letterSpacing = 0.0f; 1500 float lineHeight = 1.0f; 1501 float fontBlur = 0.0f; 1502 NVGTextAlign textAlign; 1503 int fontId = 0; 1504 bool evenOddMode = false; // use even-odd filling rule (required for some svgs); otherwise use non-zero fill 1505 // dashing 1506 enum MaxDashes = 32; // max 16 dashes 1507 float[MaxDashes] dashes; 1508 uint dashCount = 0; 1509 uint lastFlattenDashCount = 0; 1510 float dashStart = 0; 1511 float totalDashLen; 1512 bool firstDashIsGap = false; 1513 // dasher state for flattener 1514 bool dasherActive = false; 1515 1516 void clearPaint () nothrow @trusted @nogc { 1517 fill.clear(); 1518 stroke.clear(); 1519 } 1520 } 1521 1522 struct NVGpoint { 1523 float x, y; 1524 float dx, dy; 1525 float len; 1526 float dmx, dmy; 1527 ubyte flags; 1528 } 1529 1530 struct NVGpathCache { 1531 NVGpoint* points; 1532 int npoints; 1533 int cpoints; 1534 NVGpath* paths; 1535 int npaths; 1536 int cpaths; 1537 NVGVertex* verts; 1538 int nverts; 1539 int cverts; 1540 float[4] bounds; 1541 // this is required for saved paths 1542 bool strokeReady; 1543 bool fillReady; 1544 float strokeAlphaMul; 1545 float strokeWidth; 1546 float fringeWidth; 1547 bool evenOddMode; 1548 NVGClipMode clipmode; 1549 // non-saved path will not have this 1550 float* commands; 1551 int ncommands; 1552 1553 @disable this (this); // no copies 1554 void opAssign() (in auto ref NVGpathCache a) { static assert(0, "no copies!"); } 1555 1556 // won't clear current path 1557 void copyFrom (const NVGpathCache* src) nothrow @trusted @nogc { 1558 import core.stdc.stdlib : malloc; 1559 import core.stdc.string : memcpy, memset; 1560 assert(src !is null); 1561 memcpy(&this, src, NVGpathCache.sizeof); 1562 this.points = xdup(src.points, src.npoints); 1563 this.cpoints = src.npoints; 1564 this.verts = xdup(src.verts, src.nverts); 1565 this.cverts = src.nverts; 1566 this.commands = xdup(src.commands, src.ncommands); 1567 if (src.npaths > 0) { 1568 this.paths = cast(NVGpath*)malloc(src.npaths*NVGpath.sizeof); 1569 memset(this.paths, 0, npaths*NVGpath.sizeof); 1570 foreach (immutable pidx; 0..npaths) this.paths[pidx].copyFrom(&src.paths[pidx]); 1571 this.cpaths = src.npaths; 1572 } else { 1573 this.npaths = this.cpaths = 0; 1574 } 1575 } 1576 1577 void clear () nothrow @trusted @nogc { 1578 import core.stdc.stdlib : free; 1579 import core.stdc.string : memset; 1580 if (paths !is null) { 1581 foreach (ref p; paths[0..npaths]) p.clear(); 1582 free(paths); 1583 } 1584 if (points !is null) free(points); 1585 if (verts !is null) free(verts); 1586 if (commands !is null) free(commands); 1587 memset(&this, 0, this.sizeof); 1588 } 1589 } 1590 1591 /// Pointer to opaque NanoVega context structure. 1592 /// Group: context_management 1593 public alias NVGContext = NVGcontextinternal*; 1594 1595 /// FontStash context 1596 /// Group: font_stash 1597 public alias FONSContext = FONScontextInternal*; 1598 1599 /// Returns FontStash context of the given NanoVega context. 1600 /// Group: font_stash 1601 public FONSContext fonsContext (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.fs : null); } 1602 1603 /// Returns scale that should be applied to FontStash parameters due to matrix transformations on the context (or 1) 1604 /// Group: font_stash 1605 public float fonsScale (NVGContext ctx) /*pure*/ nothrow @trusted @nogc { 1606 pragma(inline, true); 1607 return (ctx !is null && ctx.contextAlive && ctx.nstates > 0 ? nvg__getFontScale(&ctx.states.ptr[ctx.nstates-1])*ctx.devicePxRatio : 1); 1608 } 1609 1610 /// Setup FontStash from the given NanoVega context font parameters. Note that this will apply transformation scale too. 1611 /// Returns `false` if FontStash or NanoVega context is not active. 1612 /// Group: font_stash 1613 public bool setupFonsFrom (FONSContext stash, NVGContext ctx) nothrow @trusted @nogc { 1614 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false; 1615 NVGstate* state = nvg__getState(ctx); 1616 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 1617 stash.size = state.fontSize*scale; 1618 stash.spacing = state.letterSpacing*scale; 1619 stash.blur = state.fontBlur*scale; 1620 stash.textAlign = state.textAlign; 1621 stash.fontId = state.fontId; 1622 return true; 1623 } 1624 1625 /// Setup NanoVega context font parameters from the given FontStash. Note that NanoVega can apply transformation scale later. 1626 /// Returns `false` if FontStash or NanoVega context is not active. 1627 /// Group: font_stash 1628 public bool setupCtxFrom (NVGContext ctx, FONSContext stash) nothrow @trusted @nogc { 1629 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false; 1630 NVGstate* state = nvg__getState(ctx); 1631 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 1632 state.fontSize = stash.size; 1633 state.letterSpacing = stash.spacing; 1634 state.fontBlur = stash.blur; 1635 state.textAlign = stash.textAlign; 1636 state.fontId = stash.fontId; 1637 return true; 1638 } 1639 1640 /** Bezier curve rasterizer. 1641 * 1642 * De Casteljau Bezier rasterizer is faster, but currently rasterizing curves with cusps sligtly wrong. 1643 * It doesn't really matter in practice. 1644 * 1645 * AFD tesselator is somewhat slower, but does cusps better. 1646 * 1647 * McSeem rasterizer should have the best quality, bit it is the slowest method. Basically, you will 1648 * never notice any visial difference (and this code is not really debugged), so you probably should 1649 * not use it. It is there for further experiments. 1650 */ 1651 public enum NVGTesselation { 1652 DeCasteljau, /// default: standard well-known tesselation algorithm 1653 AFD, /// adaptive forward differencing 1654 DeCasteljauMcSeem, /// standard well-known tesselation algorithm, with improvements from Maxim Shemanarev; slowest one, but should give best results 1655 } 1656 1657 /// Default tesselator for Bezier curves. 1658 public __gshared NVGTesselation NVG_DEFAULT_TESSELATOR = NVGTesselation.DeCasteljau; 1659 1660 1661 // some public info 1662 1663 /// valid only inside [beginFrame]/[endFrame] 1664 /// Group: context_management 1665 public int width (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mWidth : 0); } 1666 1667 /// valid only inside [beginFrame]/[endFrame] 1668 /// Group: context_management 1669 public int height (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mHeight : 0); } 1670 1671 /// valid only inside [beginFrame]/[endFrame] 1672 /// Group: context_management 1673 public float devicePixelRatio (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.devicePxRatio : float.nan); } 1674 1675 /// Returns `true` if [beginFrame] was called, and neither [endFrame], nor [cancelFrame] were. 1676 /// Group: context_management 1677 public bool inFrame (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.nstates > 0 : false); } 1678 1679 // path autoregistration 1680 1681 /// [pickid] to stop autoregistration. 1682 /// Group: context_management 1683 public enum NVGNoPick = -1; 1684 1685 /// >=0: this pickid will be assigned to all filled/stroked paths 1686 /// Group: context_management 1687 public int pickid (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickId : NVGNoPick); } 1688 1689 /// >=0: this pickid will be assigned to all filled/stroked paths 1690 /// Group: context_management 1691 public void pickid (NVGContext ctx, int v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.pathPickId = v; } 1692 1693 /// pick autoregistration mode; see [NVGPickKind] 1694 /// Group: context_management 1695 public uint pickmode (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickRegistered&NVGPickKind.All : 0); } 1696 1697 /// pick autoregistration mode; see [NVGPickKind] 1698 /// Group: context_management 1699 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); } 1700 1701 // tesselator options 1702 1703 /// Get current Bezier tesselation mode. See [NVGTesselation]. 1704 /// Group: context_management 1705 public NVGTesselation tesselation (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.tesselatortype : NVGTesselation.DeCasteljau); } 1706 1707 /// Set current Bezier tesselation mode. See [NVGTesselation]. 1708 /// Group: context_management 1709 public void tesselation (NVGContext ctx, NVGTesselation v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.tesselatortype = v; } 1710 1711 1712 private struct NVGcontextinternal { 1713 private: 1714 NVGparams params; 1715 float* commands; 1716 int ccommands; 1717 int ncommands; 1718 float commandx, commandy; 1719 NVGstate[NVG_MAX_STATES] states; 1720 int nstates; 1721 NVGpathCache* cache; 1722 public float tessTol; 1723 public float angleTol; // 0.0f -- angle tolerance for McSeem Bezier rasterizer 1724 public float cuspLimit; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps) 1725 float distTol; 1726 public float fringeWidth; 1727 float devicePxRatio; 1728 FONSContext fs; 1729 NVGImage[NVG_MAX_FONTIMAGES] fontImages; 1730 int fontImageIdx; 1731 int drawCallCount; 1732 int fillTriCount; 1733 int strokeTriCount; 1734 int textTriCount; 1735 NVGTesselation tesselatortype; 1736 // picking API 1737 NVGpickScene* pickScene; 1738 int pathPickId; // >=0: register all paths for picking using this id 1739 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 1740 // path recording 1741 NVGPathSet recset; 1742 int recstart; // used to cancel recording 1743 bool recblockdraw; 1744 // internals 1745 NVGMatrix gpuAffine; 1746 int mWidth, mHeight; 1747 // image manager 1748 shared int imageCount; // number of alive images in this context 1749 bool contextAlive; // context can be dead, but still contain some images 1750 1751 @disable this (this); // no copies 1752 void opAssign() (in auto ref NVGcontextinternal a) { static assert(0, "no copies!"); } 1753 1754 // debug feature 1755 public @property int getImageCount () nothrow @trusted @nogc { 1756 import core.atomic; 1757 return atomicLoad(imageCount); 1758 } 1759 } 1760 1761 /** Returns number of tesselated pathes in context. 1762 * 1763 * Tesselated pathes are either triangle strips (for strokes), or 1764 * triangle fans (for fills). Note that NanoVega can generate some 1765 * surprising pathes (like fringe stroke for antialiasing, for example). 1766 * 1767 * One render path can contain vertices both for fill, and for stroke triangles. 1768 */ 1769 public int renderPathCount (NVGContext ctx) pure nothrow @trusted @nogc { 1770 pragma(inline, true); 1771 return (ctx !is null && ctx.contextAlive ? ctx.cache.npaths : 0); 1772 } 1773 1774 /** Get vertices of "fill" triangle fan for the given render path. Can return empty slice. 1775 * 1776 * $(WARNING Returned slice can be invalidated by any other NanoVega API call 1777 * (except the calls to render path accessors), and using it in such 1778 * case is UB. So copy vertices to freshly allocated array if you want 1779 * to keep them for further processing.) 1780 */ 1781 public const(NVGVertex)[] renderPathFillVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1782 pragma(inline, true); 1783 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].fillVertices : null); 1784 } 1785 1786 /** Get vertices of "stroke" triangle strip for the given render path. Can return empty slice. 1787 * 1788 * $(WARNING Returned slice can be invalidated by any other NanoVega API call 1789 * (except the calls to render path accessors), and using it in such 1790 * case is UB. So copy vertices to freshly allocated array if you want 1791 * to keep them for further processing.) 1792 */ 1793 public const(NVGVertex)[] renderPathStrokeVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1794 pragma(inline, true); 1795 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].strokeVertices : null); 1796 1797 } 1798 1799 /// Returns winding for the given render path. 1800 public NVGWinding renderPathWinding (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1801 pragma(inline, true); 1802 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].winding : NVGWinding.CCW); 1803 1804 } 1805 1806 /// Returns "complex path" flag for the given render path. 1807 public bool renderPathComplex (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1808 pragma(inline, true); 1809 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].complex : false); 1810 1811 } 1812 1813 void nvg__imageIncRef (NVGContext ctx, int imgid, bool increfInGL=true) nothrow @trusted @nogc { 1814 if (ctx !is null && imgid > 0) { 1815 import core.atomic : atomicOp; 1816 atomicOp!"+="(ctx.imageCount, 1); 1817 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[++]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); } 1818 if (ctx.contextAlive && increfInGL) ctx.params.renderTextureIncRef(ctx.params.userPtr, imgid); 1819 } 1820 } 1821 1822 void nvg__imageDecRef (NVGContext ctx, int imgid) nothrow @trusted @nogc { 1823 if (ctx !is null && imgid > 0) { 1824 import core.atomic : atomicOp; 1825 int icnt = atomicOp!"-="(ctx.imageCount, 1); 1826 if (icnt < 0) assert(0, "NanoVega: internal image refcounting error"); 1827 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[--]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); } 1828 if (ctx.contextAlive) ctx.params.renderDeleteTexture(ctx.params.userPtr, imgid); 1829 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); } 1830 if (!ctx.contextAlive && icnt == 0) { 1831 // it is finally safe to free context memory 1832 import core.stdc.stdlib : free; 1833 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("killed zombie context %p\n", ctx); } 1834 free(ctx); 1835 } 1836 } 1837 } 1838 1839 1840 public import core.stdc.math : 1841 nvg__sqrtf = sqrtf, 1842 nvg__modf = fmodf, 1843 nvg__sinf = sinf, 1844 nvg__cosf = cosf, 1845 nvg__tanf = tanf, 1846 nvg__atan2f = atan2f, 1847 nvg__acosf = acosf, 1848 nvg__ceilf = ceilf; 1849 1850 version(Windows) { 1851 public int nvg__lrintf (float f) nothrow @trusted @nogc { pragma(inline, true); return cast(int)(f+0.5); } 1852 } else { 1853 public import core.stdc.math : nvg__lrintf = lrintf; 1854 } 1855 1856 public auto nvg__min(T) (T a, T b) { pragma(inline, true); return (a < b ? a : b); } 1857 public auto nvg__max(T) (T a, T b) { pragma(inline, true); return (a > b ? a : b); } 1858 public auto nvg__clamp(T) (T a, T mn, T mx) { pragma(inline, true); return (a < mn ? mn : (a > mx ? mx : a)); } 1859 //float nvg__absf() (float a) { pragma(inline, true); return (a >= 0.0f ? a : -a); } 1860 public auto nvg__sign(T) (T a) { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); } 1861 public float nvg__cross() (float dx0, float dy0, float dx1, float dy1) { pragma(inline, true); return (dx1*dy0-dx0*dy1); } 1862 1863 //public import core.stdc.math : nvg__absf = fabsf; 1864 public import core.math : nvg__absf = fabs; 1865 1866 1867 float nvg__normalize (float* x, float* y) nothrow @safe @nogc { 1868 float d = nvg__sqrtf((*x)*(*x)+(*y)*(*y)); 1869 if (d > 1e-6f) { 1870 immutable float id = 1.0f/d; 1871 *x *= id; 1872 *y *= id; 1873 } 1874 return d; 1875 } 1876 1877 void nvg__deletePathCache (ref NVGpathCache* c) nothrow @trusted @nogc { 1878 if (c !is null) { 1879 c.clear(); 1880 free(c); 1881 } 1882 } 1883 1884 NVGpathCache* nvg__allocPathCache () nothrow @trusted @nogc { 1885 NVGpathCache* c = cast(NVGpathCache*)malloc(NVGpathCache.sizeof); 1886 if (c is null) goto error; 1887 memset(c, 0, NVGpathCache.sizeof); 1888 1889 c.points = cast(NVGpoint*)malloc(NVGpoint.sizeof*NVG_INIT_POINTS_SIZE); 1890 if (c.points is null) goto error; 1891 assert(c.npoints == 0); 1892 c.cpoints = NVG_INIT_POINTS_SIZE; 1893 1894 c.paths = cast(NVGpath*)malloc(NVGpath.sizeof*NVG_INIT_PATHS_SIZE); 1895 if (c.paths is null) goto error; 1896 assert(c.npaths == 0); 1897 c.cpaths = NVG_INIT_PATHS_SIZE; 1898 1899 c.verts = cast(NVGVertex*)malloc(NVGVertex.sizeof*NVG_INIT_VERTS_SIZE); 1900 if (c.verts is null) goto error; 1901 assert(c.nverts == 0); 1902 c.cverts = NVG_INIT_VERTS_SIZE; 1903 1904 return c; 1905 1906 error: 1907 nvg__deletePathCache(c); 1908 return null; 1909 } 1910 1911 void nvg__setDevicePixelRatio (NVGContext ctx, float ratio) pure nothrow @safe @nogc { 1912 ctx.tessTol = 0.25f/ratio; 1913 ctx.distTol = 0.01f/ratio; 1914 ctx.fringeWidth = 1.0f/ratio; 1915 ctx.devicePxRatio = ratio; 1916 } 1917 1918 NVGCompositeOperationState nvg__compositeOperationState (NVGCompositeOperation op) pure nothrow @safe @nogc { 1919 NVGCompositeOperationState state; 1920 NVGBlendFactor sfactor, dfactor; 1921 1922 if (op == NVGCompositeOperation.SourceOver) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha;} 1923 else if (op == NVGCompositeOperation.SourceIn) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.Zero; } 1924 else if (op == NVGCompositeOperation.SourceOut) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.Zero; } 1925 else if (op == NVGCompositeOperation.SourceAtop) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } 1926 else if (op == NVGCompositeOperation.DestinationOver) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.One; } 1927 else if (op == NVGCompositeOperation.DestinationIn) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.SrcAlpha; } 1928 else if (op == NVGCompositeOperation.DestinationOut) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } 1929 else if (op == NVGCompositeOperation.DestinationAtop) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.SrcAlpha; } 1930 else if (op == NVGCompositeOperation.Lighter) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.One; } 1931 else if (op == NVGCompositeOperation.Copy) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.Zero; } 1932 else if (op == NVGCompositeOperation.Xor) { 1933 state.simple = false; 1934 state.srcRGB = NVGBlendFactor.OneMinusDstColor; 1935 state.srcAlpha = NVGBlendFactor.OneMinusDstAlpha; 1936 state.dstRGB = NVGBlendFactor.OneMinusSrcColor; 1937 state.dstAlpha = NVGBlendFactor.OneMinusSrcAlpha; 1938 return state; 1939 } 1940 else { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } // default value for invalid op: SourceOver 1941 1942 state.simple = true; 1943 state.srcAlpha = sfactor; 1944 state.dstAlpha = dfactor; 1945 return state; 1946 } 1947 1948 NVGstate* nvg__getState (NVGContext ctx) pure nothrow @trusted @nogc { 1949 pragma(inline, true); 1950 if (ctx is null || !ctx.contextAlive || ctx.nstates == 0) assert(0, "NanoVega: cannot perform commands on inactive context"); 1951 return &ctx.states.ptr[ctx.nstates-1]; 1952 } 1953 1954 // Constructor called by the render back-end. 1955 NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc { 1956 FONSParams fontParams; 1957 NVGContext ctx = cast(NVGContext)malloc(NVGcontextinternal.sizeof); 1958 if (ctx is null) goto error; 1959 memset(ctx, 0, NVGcontextinternal.sizeof); 1960 1961 ctx.angleTol = 0; // angle tolerance for McSeem Bezier rasterizer 1962 ctx.cuspLimit = 0; // cusp limit for McSeem Bezier rasterizer (0: real cusps) 1963 1964 ctx.contextAlive = true; 1965 1966 ctx.params = *params; 1967 //ctx.fontImages[0..NVG_MAX_FONTIMAGES] = 0; 1968 1969 ctx.commands = cast(float*)malloc(float.sizeof*NVG_INIT_COMMANDS_SIZE); 1970 if (ctx.commands is null) goto error; 1971 ctx.ncommands = 0; 1972 ctx.ccommands = NVG_INIT_COMMANDS_SIZE; 1973 1974 ctx.cache = nvg__allocPathCache(); 1975 if (ctx.cache is null) goto error; 1976 1977 ctx.save(); 1978 ctx.reset(); 1979 1980 nvg__setDevicePixelRatio(ctx, 1.0f); 1981 ctx.mWidth = ctx.mHeight = 0; 1982 1983 if (!ctx.params.renderCreate(ctx.params.userPtr)) goto error; 1984 1985 // init font rendering 1986 memset(&fontParams, 0, fontParams.sizeof); 1987 fontParams.width = NVG_INIT_FONTIMAGE_SIZE; 1988 fontParams.height = NVG_INIT_FONTIMAGE_SIZE; 1989 fontParams.flags = FONSParams.Flag.ZeroTopLeft; 1990 fontParams.renderCreate = null; 1991 fontParams.renderUpdate = null; 1992 fontParams.renderDelete = null; 1993 fontParams.userPtr = null; 1994 ctx.fs = FONSContext.create(fontParams); 1995 if (ctx.fs is null) goto error; 1996 1997 // create font texture 1998 ctx.fontImages[0].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, fontParams.width, fontParams.height, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); 1999 if (ctx.fontImages[0].id == 0) goto error; 2000 ctx.fontImages[0].ctx = ctx; 2001 ctx.nvg__imageIncRef(ctx.fontImages[0].id, false); // don't increment driver refcount 2002 ctx.fontImageIdx = 0; 2003 2004 ctx.pathPickId = -1; 2005 ctx.tesselatortype = NVG_DEFAULT_TESSELATOR; 2006 2007 return ctx; 2008 2009 error: 2010 ctx.deleteInternal(); 2011 return null; 2012 } 2013 2014 // Called by render backend. 2015 NVGparams* internalParams (NVGContext ctx) nothrow @trusted @nogc { 2016 return &ctx.params; 2017 } 2018 2019 // Destructor called by the render back-end. 2020 void deleteInternal (ref NVGContext ctx) nothrow @trusted @nogc { 2021 if (ctx is null) return; 2022 if (ctx.contextAlive) { 2023 if (ctx.commands !is null) free(ctx.commands); 2024 nvg__deletePathCache(ctx.cache); 2025 2026 if (ctx.fs) ctx.fs.kill(); 2027 2028 foreach (uint i; 0..NVG_MAX_FONTIMAGES) ctx.fontImages[i].clear(); 2029 2030 if (ctx.params.renderDelete !is null) ctx.params.renderDelete(ctx.params.userPtr); 2031 2032 if (ctx.pickScene !is null) nvg__deletePickScene(ctx.pickScene); 2033 2034 ctx.contextAlive = false; 2035 2036 import core.atomic : atomicLoad; 2037 if (atomicLoad(ctx.imageCount) == 0) { 2038 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("destroyed context %p\n", ctx); } 2039 free(ctx); 2040 } else { 2041 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("context %p is zombie now (%d image refs)\n", ctx, ctx.imageCount); } 2042 } 2043 } 2044 } 2045 2046 /// Delete NanoVega context. 2047 /// Group: context_management 2048 public void kill (ref NVGContext ctx) nothrow @trusted @nogc { 2049 if (ctx !is null) { 2050 ctx.deleteInternal(); 2051 ctx = null; 2052 } 2053 } 2054 2055 /// Returns `true` if the given context is not `null` and can be used for painting. 2056 /// Group: context_management 2057 public bool valid (in NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive); } 2058 2059 2060 // ////////////////////////////////////////////////////////////////////////// // 2061 // Frame Management 2062 2063 /** Begin drawing a new frame. 2064 * 2065 * Calls to NanoVega drawing API should be wrapped in [beginFrame] and [endFrame] 2066 * 2067 * [beginFrame] defines the size of the window to render to in relation currently 2068 * set viewport (i.e. glViewport on GL backends). Device pixel ration allows to 2069 * control the rendering on Hi-DPI devices. 2070 * 2071 * For example, GLFW returns two dimension for an opened window: window size and 2072 * frame buffer size. In that case you would set windowWidth/windowHeight to the window size, 2073 * devicePixelRatio to: `windowWidth/windowHeight`. 2074 * 2075 * Default ratio is `1`. 2076 * 2077 * Note that fractional ratio can (and will) distort your fonts and images. 2078 * 2079 * This call also resets pick marks (see picking API for non-rasterized paths), 2080 * path recording, and GPU affine transformatin matrix. 2081 * 2082 * see also [glNVGClearFlags], which returns necessary flags for [glClear]. 2083 * 2084 * Group: frame_management 2085 */ 2086 public void beginFrame (NVGContext ctx, int windowWidth, int windowHeight, float devicePixelRatio=1.0f) nothrow @trusted @nogc { 2087 import std.math : isNaN; 2088 /* 2089 printf("Tris: draws:%d fill:%d stroke:%d text:%d TOT:%d\n", 2090 ctx.drawCallCount, ctx.fillTriCount, ctx.strokeTriCount, ctx.textTriCount, 2091 ctx.fillTriCount+ctx.strokeTriCount+ctx.textTriCount); 2092 */ 2093 if (ctx.nstates > 0) ctx.cancelFrame(); 2094 2095 if (windowWidth < 1) windowWidth = 1; 2096 if (windowHeight < 1) windowHeight = 1; 2097 2098 if (isNaN(devicePixelRatio)) devicePixelRatio = (windowHeight > 0 ? cast(float)windowWidth/cast(float)windowHeight : 1024.0/768.0); 2099 2100 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2101 ctx.nstates = 0; 2102 ctx.save(); 2103 ctx.reset(); 2104 2105 nvg__setDevicePixelRatio(ctx, devicePixelRatio); 2106 2107 ctx.params.renderViewport(ctx.params.userPtr, windowWidth, windowHeight); 2108 ctx.mWidth = windowWidth; 2109 ctx.mHeight = windowHeight; 2110 2111 ctx.recset = null; 2112 ctx.recstart = -1; 2113 2114 ctx.pathPickId = NVGNoPick; 2115 ctx.pathPickRegistered = 0; 2116 2117 ctx.drawCallCount = 0; 2118 ctx.fillTriCount = 0; 2119 ctx.strokeTriCount = 0; 2120 ctx.textTriCount = 0; 2121 2122 ctx.ncommands = 0; 2123 ctx.pathPickRegistered = 0; 2124 nvg__clearPathCache(ctx); 2125 2126 ctx.gpuAffine = NVGMatrix.Identity; 2127 2128 nvg__pickBeginFrame(ctx, windowWidth, windowHeight); 2129 } 2130 2131 /// Cancels drawing the current frame. Cancels path recording. 2132 /// Group: frame_management 2133 public void cancelFrame (NVGContext ctx) nothrow @trusted @nogc { 2134 ctx.cancelRecording(); 2135 //ctx.mWidth = 0; 2136 //ctx.mHeight = 0; 2137 // cancel render queue 2138 ctx.params.renderCancel(ctx.params.userPtr); 2139 // clear saved states (this may free some textures) 2140 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2141 ctx.nstates = 0; 2142 } 2143 2144 /// Ends drawing the current frame (flushing remaining render state). Commits recorded paths. 2145 /// Group: frame_management 2146 public void endFrame (NVGContext ctx) nothrow @trusted @nogc { 2147 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx); 2148 ctx.stopRecording(); 2149 //ctx.mWidth = 0; 2150 //ctx.mHeight = 0; 2151 // flush render queue 2152 NVGstate* state = nvg__getState(ctx); 2153 ctx.params.renderFlush(ctx.params.userPtr); 2154 if (ctx.fontImageIdx != 0) { 2155 auto fontImage = ctx.fontImages[ctx.fontImageIdx]; 2156 int j = 0, iw, ih; 2157 // delete images that smaller than current one 2158 if (!fontImage.valid) return; 2159 ctx.imageSize(fontImage, iw, ih); 2160 foreach (int i; 0..ctx.fontImageIdx) { 2161 if (ctx.fontImages[i].valid) { 2162 int nw, nh; 2163 ctx.imageSize(ctx.fontImages[i], nw, nh); 2164 if (nw < iw || nh < ih) { 2165 ctx.deleteImage(ctx.fontImages[i]); 2166 } else { 2167 ctx.fontImages[j++] = ctx.fontImages[i]; 2168 } 2169 } 2170 } 2171 // make current font image to first 2172 ctx.fontImages[j++] = ctx.fontImages[0]; 2173 ctx.fontImages[0] = fontImage; 2174 ctx.fontImageIdx = 0; 2175 // clear all images after j 2176 ctx.fontImages[j..NVG_MAX_FONTIMAGES] = NVGImage.init; 2177 } 2178 // clear saved states (this may free some textures) 2179 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2180 ctx.nstates = 0; 2181 } 2182 2183 2184 // ////////////////////////////////////////////////////////////////////////// // 2185 // Recording and Replaying Pathes 2186 2187 // Saved path set. 2188 // Group: path_recording 2189 public alias NVGPathSet = NVGPathSetS*; 2190 2191 2192 //TODO: save scissor info? 2193 struct NVGPathSetS { 2194 private: 2195 // either path cache, or text item 2196 static struct Node { 2197 NVGPaint paint; 2198 NVGpathCache* path; 2199 } 2200 2201 private: 2202 Node* nodes; 2203 int nnodes, cnodes; 2204 NVGpickScene* pickscene; 2205 //int npickscenes, cpickscenes; 2206 NVGContext svctx; // used to do some sanity checks, and to free resources 2207 2208 private: 2209 Node* allocNode () nothrow @trusted @nogc { 2210 import core.stdc.string : memset; 2211 // grow buffer if necessary 2212 if (nnodes+1 > cnodes) { 2213 import core.stdc.stdlib : realloc; 2214 int newsz = (cnodes == 0 ? 8 : cnodes <= 1024 ? cnodes*2 : cnodes+1024); 2215 nodes = cast(Node*)realloc(nodes, newsz*Node.sizeof); 2216 if (nodes is null) assert(0, "NanoVega: out of memory"); 2217 //memset(svp.caches+svp.ccaches, 0, (newsz-svp.ccaches)*NVGpathCache.sizeof); 2218 cnodes = newsz; 2219 } 2220 assert(nnodes < cnodes); 2221 memset(nodes+nnodes, 0, Node.sizeof); 2222 return &nodes[nnodes++]; 2223 } 2224 2225 Node* allocPathNode () nothrow @trusted @nogc { 2226 import core.stdc.stdlib : malloc; 2227 import core.stdc.string : memset; 2228 auto node = allocNode(); 2229 // allocate path cache 2230 auto pc = cast(NVGpathCache*)malloc(NVGpathCache.sizeof); 2231 if (pc is null) assert(0, "NanoVega: out of memory"); 2232 node.path = pc; 2233 return node; 2234 } 2235 2236 void clearNode (int idx) nothrow @trusted @nogc { 2237 if (idx < 0 || idx >= nnodes) return; 2238 Node* node = &nodes[idx]; 2239 if (svctx !is null && node.paint.image.valid) node.paint.image.clear(); 2240 if (node.path !is null) node.path.clear(); 2241 } 2242 2243 private: 2244 void takeCurrentPickScene (NVGContext ctx) nothrow @trusted @nogc { 2245 NVGpickScene* ps = ctx.pickScene; 2246 if (ps is null) return; // nothing to do 2247 if (ps.npaths == 0) return; // pick scene is empty 2248 ctx.pickScene = null; 2249 pickscene = ps; 2250 } 2251 2252 void replay (NVGContext ctx, in ref NVGColor fillTint, in ref NVGColor strokeTint) nothrow @trusted @nogc { 2253 NVGstate* state = nvg__getState(ctx); 2254 foreach (ref node; nodes[0..nnodes]) { 2255 if (auto cc = node.path) { 2256 if (cc.npaths <= 0) continue; 2257 2258 if (cc.fillReady) { 2259 NVGPaint fillPaint = node.paint; 2260 2261 // apply global alpha 2262 fillPaint.innerColor.a *= state.alpha; 2263 fillPaint.middleColor.a *= state.alpha; 2264 fillPaint.outerColor.a *= state.alpha; 2265 2266 fillPaint.innerColor.applyTint(fillTint); 2267 fillPaint.middleColor.applyTint(fillTint); 2268 fillPaint.outerColor.applyTint(fillTint); 2269 2270 ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &fillPaint, &state.scissor, cc.fringeWidth, cc.bounds.ptr, cc.paths, cc.npaths, cc.evenOddMode); 2271 2272 // count triangles 2273 foreach (int i; 0..cc.npaths) { 2274 NVGpath* path = &cc.paths[i]; 2275 ctx.fillTriCount += path.nfill-2; 2276 ctx.fillTriCount += path.nstroke-2; 2277 ctx.drawCallCount += 2; 2278 } 2279 } 2280 2281 if (cc.strokeReady) { 2282 NVGPaint strokePaint = node.paint; 2283 2284 strokePaint.innerColor.a *= cc.strokeAlphaMul; 2285 strokePaint.middleColor.a *= cc.strokeAlphaMul; 2286 strokePaint.outerColor.a *= cc.strokeAlphaMul; 2287 2288 // apply global alpha 2289 strokePaint.innerColor.a *= state.alpha; 2290 strokePaint.middleColor.a *= state.alpha; 2291 strokePaint.outerColor.a *= state.alpha; 2292 2293 strokePaint.innerColor.applyTint(strokeTint); 2294 strokePaint.middleColor.applyTint(strokeTint); 2295 strokePaint.outerColor.applyTint(strokeTint); 2296 2297 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &strokePaint, &state.scissor, cc.fringeWidth, cc.strokeWidth, cc.paths, cc.npaths); 2298 2299 // count triangles 2300 foreach (int i; 0..cc.npaths) { 2301 NVGpath* path = &cc.paths[i]; 2302 ctx.strokeTriCount += path.nstroke-2; 2303 ++ctx.drawCallCount; 2304 } 2305 } 2306 } 2307 } 2308 } 2309 2310 public: 2311 @disable this (this); // no copies 2312 void opAssign() (in auto ref NVGPathSetS a) { static assert(0, "no copies!"); } 2313 2314 // pick test 2315 // Call delegate [dg] for each path under the specified position (in no particular order). 2316 // Returns the id of the path for which delegate [dg] returned true or -1. 2317 // dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending). 2318 int hitTestDG(bool bestOrder=false, DG) (in float x, in float y, NVGPickKind kind, scope DG dg) if (IsGoodHitTestDG!DG || IsGoodHitTestInternalDG!DG) { 2319 if (pickscene is null) return -1; 2320 2321 NVGpickScene* ps = pickscene; 2322 int levelwidth = 1<<(ps.nlevels-1); 2323 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth); 2324 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth); 2325 int npicked = 0; 2326 2327 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) { 2328 NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; 2329 while (pp !is null) { 2330 if (nvg__pickPathTestBounds(svctx, ps, pp, x, y)) { 2331 int hit = 0; 2332 if ((kind&NVGPickKind.Stroke) && (pp.flags&NVGPathFlags.Stroke)) hit = nvg__pickPathStroke(ps, pp, x, y); 2333 if (!hit && (kind&NVGPickKind.Fill) && (pp.flags&NVGPathFlags.Fill)) hit = nvg__pickPath(ps, pp, x, y); 2334 if (hit) { 2335 static if (IsGoodHitTestDG!DG) { 2336 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) { 2337 if (dg(pp.id, cast(int)pp.order)) return pp.id; 2338 } else { 2339 dg(pp.id, cast(int)pp.order); 2340 } 2341 } else { 2342 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) { 2343 if (dg(pp)) return pp.id; 2344 } else { 2345 dg(pp); 2346 } 2347 } 2348 } 2349 } 2350 pp = pp.next; 2351 } 2352 cellx >>= 1; 2353 celly >>= 1; 2354 levelwidth >>= 1; 2355 } 2356 2357 return -1; 2358 } 2359 2360 // Fills ids with a list of the top most hit ids under the specified position. 2361 // Returns the slice of [ids]. 2362 int[] hitTestAll (in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc { 2363 if (pickscene is null || ids.length == 0) return ids[0..0]; 2364 2365 int npicked = 0; 2366 NVGpickScene* ps = pickscene; 2367 2368 hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 2369 if (npicked == ps.cpicked) { 2370 int cpicked = ps.cpicked+ps.cpicked; 2371 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked); 2372 if (picked is null) return true; // abort 2373 ps.cpicked = cpicked; 2374 ps.picked = picked; 2375 } 2376 ps.picked[npicked] = pp; 2377 ++npicked; 2378 return false; // go on 2379 }); 2380 2381 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths); 2382 2383 assert(npicked >= 0); 2384 if (npicked > ids.length) npicked = cast(int)ids.length; 2385 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id; 2386 2387 return ids[0..npicked]; 2388 } 2389 2390 // Returns the id of the pickable shape containing x,y or -1 if no shape was found. 2391 int hitTest (in float x, in float y, NVGPickKind kind) nothrow @trusted @nogc { 2392 if (pickscene is null) return -1; 2393 2394 int bestOrder = -1; 2395 int bestID = -1; 2396 2397 hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 2398 if (pp.order > bestOrder) { 2399 bestOrder = pp.order; 2400 bestID = pp.id; 2401 } 2402 }); 2403 2404 return bestID; 2405 } 2406 } 2407 2408 // Append current path to existing path set. Is is safe to call this with `null` [svp]. 2409 void appendCurrentPathToCache (NVGContext ctx, NVGPathSet svp, in ref NVGPaint paint) nothrow @trusted @nogc { 2410 if (ctx is null || svp is null) return; 2411 if (ctx !is svp.svctx) assert(0, "NanoVega: cannot save paths from different contexts"); 2412 if (ctx.ncommands == 0) { 2413 assert(ctx.cache.npaths == 0); 2414 return; 2415 } 2416 if (!ctx.cache.fillReady && !ctx.cache.strokeReady) return; 2417 2418 // tesselate current path 2419 //if (!ctx.cache.fillReady) nvg__prepareFill(ctx); 2420 //if (!ctx.cache.strokeReady) nvg__prepareStroke(ctx); 2421 2422 auto node = svp.allocPathNode(); 2423 NVGpathCache* cc = node.path; 2424 cc.copyFrom(ctx.cache); 2425 node.paint = paint; 2426 // copy path commands (we may need 'em for picking) 2427 version(all) { 2428 cc.ncommands = ctx.ncommands; 2429 if (cc.ncommands) { 2430 import core.stdc.stdlib : malloc; 2431 import core.stdc.string : memcpy; 2432 cc.commands = cast(float*)malloc(ctx.ncommands*float.sizeof); 2433 if (cc.commands is null) assert(0, "NanoVega: out of memory"); 2434 memcpy(cc.commands, ctx.commands, ctx.ncommands*float.sizeof); 2435 } else { 2436 cc.commands = null; 2437 } 2438 } 2439 } 2440 2441 // Create new empty path set. 2442 // Group: path_recording 2443 public NVGPathSet newPathSet (NVGContext ctx) nothrow @trusted @nogc { 2444 import core.stdc.stdlib : malloc; 2445 import core.stdc.string : memset; 2446 if (ctx is null) return null; 2447 NVGPathSet res = cast(NVGPathSet)malloc(NVGPathSetS.sizeof); 2448 if (res is null) assert(0, "NanoVega: out of memory"); 2449 memset(res, 0, NVGPathSetS.sizeof); 2450 res.svctx = ctx; 2451 return res; 2452 } 2453 2454 // Is the given path set empty? Empty path set can be `null`. 2455 // Group: path_recording 2456 public bool empty (NVGPathSet svp) pure nothrow @safe @nogc { pragma(inline, true); return (svp is null || svp.nnodes == 0); } 2457 2458 // Clear path set contents. Will release $(B some) allocated memory (this function is meant to clear something that will be reused). 2459 // Group: path_recording 2460 public void clear (NVGPathSet svp) nothrow @trusted @nogc { 2461 if (svp !is null) { 2462 import core.stdc.stdlib : free; 2463 foreach (immutable idx; 0.. svp.nnodes) svp.clearNode(idx); 2464 svp.nnodes = 0; 2465 } 2466 } 2467 2468 // Destroy path set (frees all allocated memory). 2469 // Group: path_recording 2470 public void kill (ref NVGPathSet svp) nothrow @trusted @nogc { 2471 if (svp !is null) { 2472 import core.stdc.stdlib : free; 2473 svp.clear(); 2474 if (svp.nodes !is null) free(svp.nodes); 2475 free(svp); 2476 if (svp.pickscene !is null) nvg__deletePickScene(svp.pickscene); 2477 svp = null; 2478 } 2479 } 2480 2481 // Start path recording. [svp] should be alive until recording is cancelled or stopped. 2482 // Group: path_recording 2483 public void startRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { 2484 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2485 ctx.stopRecording(); 2486 ctx.recset = svp; 2487 ctx.recstart = (svp !is null ? svp.nnodes : -1); 2488 ctx.recblockdraw = false; 2489 } 2490 2491 /* Start path recording. [svp] should be alive until recording is cancelled or stopped. 2492 * 2493 * This will block all rendering, so you can call your rendering functions to record paths without actual drawing. 2494 * Commiting or cancelling will re-enable rendering. 2495 * You can call this with `null` svp to block rendering without recording any paths. 2496 * 2497 * Group: path_recording 2498 */ 2499 public void startBlockingRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { 2500 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2501 ctx.stopRecording(); 2502 ctx.recset = svp; 2503 ctx.recstart = (svp !is null ? svp.nnodes : -1); 2504 ctx.recblockdraw = true; 2505 } 2506 2507 // Commit recorded paths. It is safe to call this when recording is not started. 2508 // Group: path_recording 2509 public void stopRecording (NVGContext ctx) nothrow @trusted @nogc { 2510 if (ctx.recset !is null && ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2511 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx); 2512 ctx.recset = null; 2513 ctx.recstart = -1; 2514 ctx.recblockdraw = false; 2515 } 2516 2517 // Cancel path recording. 2518 // Group: path_recording 2519 public void cancelRecording (NVGContext ctx) nothrow @trusted @nogc { 2520 if (ctx.recset !is null) { 2521 if (ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2522 assert(ctx.recstart >= 0 && ctx.recstart <= ctx.recset.nnodes); 2523 foreach (immutable idx; ctx.recstart..ctx.recset.nnodes) ctx.recset.clearNode(idx); 2524 ctx.recset.nnodes = ctx.recstart; 2525 ctx.recset = null; 2526 ctx.recstart = -1; 2527 } 2528 ctx.recblockdraw = false; 2529 } 2530 2531 /* Replay saved path set. 2532 * 2533 * Replaying record while you're recording another one is undefined behavior. 2534 * 2535 * Group: path_recording 2536 */ 2537 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint, in auto ref NVGColor strokeTint) nothrow @trusted @nogc { 2538 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2539 svp.replay(ctx, fillTint, strokeTint); 2540 } 2541 2542 /// Ditto. 2543 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint) nothrow @trusted @nogc { ctx.replayRecording(svp, fillTint, NVGColor.transparent); } 2544 2545 /// Ditto. 2546 public void replayRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { ctx.replayRecording(svp, NVGColor.transparent, NVGColor.transparent); } 2547 2548 2549 // ////////////////////////////////////////////////////////////////////////// // 2550 // Composite operation 2551 2552 /// Sets the composite operation. 2553 /// Group: composite_operation 2554 public void globalCompositeOperation (NVGContext ctx, NVGCompositeOperation op) nothrow @trusted @nogc { 2555 NVGstate* state = nvg__getState(ctx); 2556 state.compositeOperation = nvg__compositeOperationState(op); 2557 } 2558 2559 /// Sets the composite operation with custom pixel arithmetic. 2560 /// Group: composite_operation 2561 public void globalCompositeBlendFunc (NVGContext ctx, NVGBlendFactor sfactor, NVGBlendFactor dfactor) nothrow @trusted @nogc { 2562 ctx.globalCompositeBlendFuncSeparate(sfactor, dfactor, sfactor, dfactor); 2563 } 2564 2565 /// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. 2566 /// Group: composite_operation 2567 public void globalCompositeBlendFuncSeparate (NVGContext ctx, NVGBlendFactor srcRGB, NVGBlendFactor dstRGB, NVGBlendFactor srcAlpha, NVGBlendFactor dstAlpha) nothrow @trusted @nogc { 2568 NVGCompositeOperationState op; 2569 op.simple = false; 2570 op.srcRGB = srcRGB; 2571 op.dstRGB = dstRGB; 2572 op.srcAlpha = srcAlpha; 2573 op.dstAlpha = dstAlpha; 2574 NVGstate* state = nvg__getState(ctx); 2575 state.compositeOperation = op; 2576 } 2577 2578 2579 // ////////////////////////////////////////////////////////////////////////// // 2580 // Color utils 2581 2582 /// Returns a color value from string form. 2583 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb" 2584 /// Group: color_utils 2585 public NVGColor nvgRGB (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); } 2586 2587 /// Ditto. 2588 public NVGColor nvgRGBA (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); } 2589 2590 /// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f). 2591 /// Group: color_utils 2592 public NVGColor nvgRGB (int r, int g, int b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(nvgClampToByte(r), nvgClampToByte(g), nvgClampToByte(b), 255); } 2593 2594 /// Returns a color value from red, green, blue values. Alpha will be set to 1.0f. 2595 /// Group: color_utils 2596 public NVGColor nvgRGBf (float r, float g, float b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(r, g, b, 1.0f); } 2597 2598 /// Returns a color value from red, green, blue and alpha values. 2599 /// Group: color_utils 2600 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)); } 2601 2602 /// Returns a color value from red, green, blue and alpha values. 2603 /// Group: color_utils 2604 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); } 2605 2606 /// Returns new color with transparency (alpha) set to [a]. 2607 /// Group: color_utils 2608 public NVGColor nvgTransRGBA (NVGColor c, ubyte a) nothrow @trusted @nogc { 2609 pragma(inline, true); 2610 c.a = a/255.0f; 2611 return c; 2612 } 2613 2614 /// Ditto. 2615 public NVGColor nvgTransRGBAf (NVGColor c, float a) nothrow @trusted @nogc { 2616 pragma(inline, true); 2617 c.a = a; 2618 return c; 2619 } 2620 2621 /// Linearly interpolates from color c0 to c1, and returns resulting color value. 2622 /// Group: color_utils 2623 public NVGColor nvgLerpRGBA() (in auto ref NVGColor c0, in auto ref NVGColor c1, float u) nothrow @trusted @nogc { 2624 NVGColor cint = void; 2625 u = nvg__clamp(u, 0.0f, 1.0f); 2626 float oneminu = 1.0f-u; 2627 foreach (uint i; 0..4) cint.rgba.ptr[i] = c0.rgba.ptr[i]*oneminu+c1.rgba.ptr[i]*u; 2628 return cint; 2629 } 2630 2631 /* see below 2632 public NVGColor nvgHSL() (float h, float s, float l) { 2633 //pragma(inline, true); // alas 2634 return nvgHSLA(h, s, l, 255); 2635 } 2636 */ 2637 2638 float nvg__hue (float h, float m1, float m2) pure nothrow @safe @nogc { 2639 if (h < 0) h += 1; 2640 if (h > 1) h -= 1; 2641 if (h < 1.0f/6.0f) return m1+(m2-m1)*h*6.0f; 2642 if (h < 3.0f/6.0f) return m2; 2643 if (h < 4.0f/6.0f) return m1+(m2-m1)*(2.0f/3.0f-h)*6.0f; 2644 return m1; 2645 } 2646 2647 /// Returns color value specified by hue, saturation and lightness. 2648 /// HSL values are all in range [0..1], alpha will be set to 255. 2649 /// Group: color_utils 2650 public alias nvgHSL = nvgHSLA; // trick to allow inlining 2651 2652 /// Returns color value specified by hue, saturation and lightness and alpha. 2653 /// HSL values are all in range [0..1], alpha in range [0..255]. 2654 /// Group: color_utils 2655 public NVGColor nvgHSLA (float h, float s, float l, ubyte a=255) nothrow @trusted @nogc { 2656 pragma(inline, true); 2657 NVGColor col = void; 2658 h = nvg__modf(h, 1.0f); 2659 if (h < 0.0f) h += 1.0f; 2660 s = nvg__clamp(s, 0.0f, 1.0f); 2661 l = nvg__clamp(l, 0.0f, 1.0f); 2662 immutable float m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s); 2663 immutable float m1 = 2*l-m2; 2664 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2665 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f); 2666 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2667 col.a = a/255.0f; 2668 return col; 2669 } 2670 2671 /// Returns color value specified by hue, saturation and lightness and alpha. 2672 /// HSL values and alpha are all in range [0..1]. 2673 /// Group: color_utils 2674 public NVGColor nvgHSLA (float h, float s, float l, float a) nothrow @trusted @nogc { 2675 // sorry for copypasta, it is for inliner 2676 static if (__VERSION__ >= 2072) pragma(inline, true); 2677 NVGColor col = void; 2678 h = nvg__modf(h, 1.0f); 2679 if (h < 0.0f) h += 1.0f; 2680 s = nvg__clamp(s, 0.0f, 1.0f); 2681 l = nvg__clamp(l, 0.0f, 1.0f); 2682 immutable m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s); 2683 immutable m1 = 2*l-m2; 2684 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2685 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f); 2686 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2687 col.a = a; 2688 return col; 2689 } 2690 2691 2692 // ////////////////////////////////////////////////////////////////////////// // 2693 // Matrices and Transformations 2694 2695 /** Matrix class. 2696 * 2697 * Group: matrices 2698 */ 2699 public align(1) struct NVGMatrix { 2700 align(1): 2701 private: 2702 static immutable float[6] IdentityMat = [ 2703 1.0f, 0.0f, 2704 0.0f, 1.0f, 2705 0.0f, 0.0f, 2706 ]; 2707 2708 public: 2709 /// Matrix values. Initial value is identity matrix. 2710 float[6] mat = [ 2711 1.0f, 0.0f, 2712 0.0f, 1.0f, 2713 0.0f, 0.0f, 2714 ]; 2715 2716 public nothrow @trusted @nogc: 2717 /// Create Matrix with the given values. 2718 this (const(float)[] amat...) { 2719 pragma(inline, true); 2720 if (amat.length >= 6) { 2721 mat.ptr[0..6] = amat.ptr[0..6]; 2722 } else { 2723 mat.ptr[0..6] = 0; 2724 mat.ptr[0..amat.length] = amat[]; 2725 } 2726 } 2727 2728 /// Can be used to check validity of [inverted] result 2729 @property bool valid () const { import core.stdc.math : isfinite; return (isfinite(mat.ptr[0]) != 0); } 2730 2731 /// Returns `true` if this matrix is identity matrix. 2732 @property bool isIdentity () const { version(aliced) pragma(inline, true); return (mat[] == IdentityMat[]); } 2733 2734 /// Returns new inverse matrix. 2735 /// If inverted matrix cannot be calculated, `res.valid` fill be `false`. 2736 NVGMatrix inverted () const { 2737 NVGMatrix res = this; 2738 res.invert; 2739 return res; 2740 } 2741 2742 /// Inverts this matrix. 2743 /// If inverted matrix cannot be calculated, `this.valid` fill be `false`. 2744 ref NVGMatrix invert () return { 2745 float[6] inv = void; 2746 immutable double det = cast(double)mat.ptr[0]*mat.ptr[3]-cast(double)mat.ptr[2]*mat.ptr[1]; 2747 if (det > -1e-6 && det < 1e-6) { 2748 inv[] = float.nan; 2749 } else { 2750 immutable double invdet = 1.0/det; 2751 inv.ptr[0] = cast(float)(mat.ptr[3]*invdet); 2752 inv.ptr[2] = cast(float)(-mat.ptr[2]*invdet); 2753 inv.ptr[4] = cast(float)((cast(double)mat.ptr[2]*mat.ptr[5]-cast(double)mat.ptr[3]*mat.ptr[4])*invdet); 2754 inv.ptr[1] = cast(float)(-mat.ptr[1]*invdet); 2755 inv.ptr[3] = cast(float)(mat.ptr[0]*invdet); 2756 inv.ptr[5] = cast(float)((cast(double)mat.ptr[1]*mat.ptr[4]-cast(double)mat.ptr[0]*mat.ptr[5])*invdet); 2757 } 2758 mat.ptr[0..6] = inv.ptr[0..6]; 2759 return this; 2760 } 2761 2762 /// Sets this matrix to identity matrix. 2763 ref NVGMatrix identity () return { version(aliced) pragma(inline, true); mat[] = IdentityMat[]; return this; } 2764 2765 /// Translate this matrix. 2766 ref NVGMatrix translate (in float tx, in float ty) return { 2767 version(aliced) pragma(inline, true); 2768 return this.mul(Translated(tx, ty)); 2769 } 2770 2771 /// Scale this matrix. 2772 ref NVGMatrix scale (in float sx, in float sy) return { 2773 version(aliced) pragma(inline, true); 2774 return this.mul(Scaled(sx, sy)); 2775 } 2776 2777 /// Rotate this matrix. 2778 ref NVGMatrix rotate (in float a) return { 2779 version(aliced) pragma(inline, true); 2780 return this.mul(Rotated(a)); 2781 } 2782 2783 /// Skew this matrix by X axis. 2784 ref NVGMatrix skewX (in float a) return { 2785 version(aliced) pragma(inline, true); 2786 return this.mul(SkewedX(a)); 2787 } 2788 2789 /// Skew this matrix by Y axis. 2790 ref NVGMatrix skewY (in float a) return { 2791 version(aliced) pragma(inline, true); 2792 return this.mul(SkewedY(a)); 2793 } 2794 2795 /// Skew this matrix by both axes. 2796 ref NVGMatrix skewY (in float ax, in float ay) return { 2797 version(aliced) pragma(inline, true); 2798 return this.mul(SkewedXY(ax, ay)); 2799 } 2800 2801 /// Transform point with this matrix. `null` destinations are allowed. 2802 /// [sx] and [sy] is the source point. [dx] and [dy] may point to the same variables. 2803 void point (float* dx, float* dy, float sx, float sy) nothrow @trusted @nogc { 2804 version(aliced) pragma(inline, true); 2805 if (dx !is null) *dx = sx*mat.ptr[0]+sy*mat.ptr[2]+mat.ptr[4]; 2806 if (dy !is null) *dy = sx*mat.ptr[1]+sy*mat.ptr[3]+mat.ptr[5]; 2807 } 2808 2809 /// Transform point with this matrix. 2810 void point (ref float x, ref float y) nothrow @trusted @nogc { 2811 version(aliced) pragma(inline, true); 2812 immutable float nx = x*mat.ptr[0]+y*mat.ptr[2]+mat.ptr[4]; 2813 immutable float ny = x*mat.ptr[1]+y*mat.ptr[3]+mat.ptr[5]; 2814 x = nx; 2815 y = ny; 2816 } 2817 2818 /// Sets this matrix to the result of multiplication of `this` and [s] (this * S). 2819 ref NVGMatrix mul() (in auto ref NVGMatrix s) { 2820 immutable float t0 = mat.ptr[0]*s.mat.ptr[0]+mat.ptr[1]*s.mat.ptr[2]; 2821 immutable float t2 = mat.ptr[2]*s.mat.ptr[0]+mat.ptr[3]*s.mat.ptr[2]; 2822 immutable float t4 = mat.ptr[4]*s.mat.ptr[0]+mat.ptr[5]*s.mat.ptr[2]+s.mat.ptr[4]; 2823 mat.ptr[1] = mat.ptr[0]*s.mat.ptr[1]+mat.ptr[1]*s.mat.ptr[3]; 2824 mat.ptr[3] = mat.ptr[2]*s.mat.ptr[1]+mat.ptr[3]*s.mat.ptr[3]; 2825 mat.ptr[5] = mat.ptr[4]*s.mat.ptr[1]+mat.ptr[5]*s.mat.ptr[3]+s.mat.ptr[5]; 2826 mat.ptr[0] = t0; 2827 mat.ptr[2] = t2; 2828 mat.ptr[4] = t4; 2829 return this; 2830 } 2831 2832 /// Sets this matrix to the result of multiplication of [s] and `this` (S * this). 2833 /// Sets the transform to the result of multiplication of two transforms, of A = B*A. 2834 /// Group: matrices 2835 ref NVGMatrix premul() (in auto ref NVGMatrix s) { 2836 NVGMatrix s2 = s; 2837 s2.mul(this); 2838 mat[] = s2.mat[]; 2839 return this; 2840 } 2841 2842 /// Multiply this matrix by [s], return result as new matrix. 2843 /// Performs operations in this left-to-right order. 2844 NVGMatrix opBinary(string op="*") (in auto ref NVGMatrix s) const { 2845 version(aliced) pragma(inline, true); 2846 NVGMatrix res = this; 2847 res.mul(s); 2848 return res; 2849 } 2850 2851 /// Multiply this matrix by [s]. 2852 /// Performs operations in this left-to-right order. 2853 ref NVGMatrix opOpAssign(string op="*") (in auto ref NVGMatrix s) { 2854 version(aliced) pragma(inline, true); 2855 return this.mul(s); 2856 } 2857 2858 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. 2859 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. 2860 float rotation () const { pragma(inline, true); return nvg__atan2f(mat.ptr[1], mat.ptr[0]); } /// Returns rotation of this matrix. 2861 float tx () const { pragma(inline, true); return mat.ptr[4]; } /// Returns x translation of this matrix. 2862 float ty () const { pragma(inline, true); return mat.ptr[5]; } /// Returns y translation of this matrix. 2863 2864 ref NVGMatrix scaleX (in float v) return { pragma(inline, true); return scaleRotateTransform(v, scaleY, rotation, tx, ty); } /// Sets x scaling of this matrix. 2865 ref NVGMatrix scaleY (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, v, rotation, tx, ty); } /// Sets y scaling of this matrix. 2866 ref NVGMatrix rotation (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, scaleY, v, tx, ty); } /// Sets rotation of this matrix. 2867 ref NVGMatrix tx (in float v) return { pragma(inline, true); mat.ptr[4] = v; return this; } /// Sets x translation of this matrix. 2868 ref NVGMatrix ty (in float v) return { pragma(inline, true); mat.ptr[5] = v; return this; } /// Sets y translation of this matrix. 2869 2870 /// Utility function to be used in `setXXX()`. 2871 /// This is the same as doing: `mat.identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster 2872 ref NVGMatrix scaleRotateTransform (in float xscale, in float yscale, in float a, in float tx, in float ty) return { 2873 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2874 mat.ptr[0] = xscale*cs; mat.ptr[1] = yscale*sn; 2875 mat.ptr[2] = xscale*-sn; mat.ptr[3] = yscale*cs; 2876 mat.ptr[4] = tx; mat.ptr[5] = ty; 2877 return this; 2878 } 2879 2880 /// This is the same as doing: `mat.identity.rotate(a).translate(tx, ty)`, only faster 2881 ref NVGMatrix rotateTransform (in float a, in float tx, in float ty) return { 2882 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2883 mat.ptr[0] = cs; mat.ptr[1] = sn; 2884 mat.ptr[2] = -sn; mat.ptr[3] = cs; 2885 mat.ptr[4] = tx; mat.ptr[5] = ty; 2886 return this; 2887 } 2888 2889 /// Returns new identity matrix. 2890 static NVGMatrix Identity () { pragma(inline, true); return NVGMatrix.init; } 2891 2892 /// Returns new translation matrix. 2893 static NVGMatrix Translated (in float tx, in float ty) { 2894 version(aliced) pragma(inline, true); 2895 NVGMatrix res = void; 2896 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f; 2897 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f; 2898 res.mat.ptr[4] = tx; res.mat.ptr[5] = ty; 2899 return res; 2900 } 2901 2902 /// Returns new scaling matrix. 2903 static NVGMatrix Scaled (in float sx, in float sy) { 2904 version(aliced) pragma(inline, true); 2905 NVGMatrix res = void; 2906 res.mat.ptr[0] = sx; res.mat.ptr[1] = 0.0f; 2907 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = sy; 2908 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2909 return res; 2910 } 2911 2912 /// Returns new rotation matrix. Angle is specified in radians. 2913 static NVGMatrix Rotated (in float a) { 2914 version(aliced) pragma(inline, true); 2915 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2916 NVGMatrix res = void; 2917 res.mat.ptr[0] = cs; res.mat.ptr[1] = sn; 2918 res.mat.ptr[2] = -sn; res.mat.ptr[3] = cs; 2919 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2920 return res; 2921 } 2922 2923 /// Returns new x-skewing matrix. Angle is specified in radians. 2924 static NVGMatrix SkewedX (in float a) { 2925 version(aliced) pragma(inline, true); 2926 NVGMatrix res = void; 2927 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f; 2928 res.mat.ptr[2] = nvg__tanf(a); res.mat.ptr[3] = 1.0f; 2929 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2930 return res; 2931 } 2932 2933 /// Returns new y-skewing matrix. Angle is specified in radians. 2934 static NVGMatrix SkewedY (in float a) { 2935 version(aliced) pragma(inline, true); 2936 NVGMatrix res = void; 2937 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(a); 2938 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f; 2939 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2940 return res; 2941 } 2942 2943 /// Returns new xy-skewing matrix. Angles are specified in radians. 2944 static NVGMatrix SkewedXY (in float ax, in float ay) { 2945 version(aliced) pragma(inline, true); 2946 NVGMatrix res = void; 2947 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(ay); 2948 res.mat.ptr[2] = nvg__tanf(ax); res.mat.ptr[3] = 1.0f; 2949 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2950 return res; 2951 } 2952 2953 /// Utility function to be used in `setXXX()`. 2954 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster 2955 static NVGMatrix ScaledRotatedTransformed (in float xscale, in float yscale, in float a, in float tx, in float ty) { 2956 NVGMatrix res = void; 2957 res.scaleRotateTransform(xscale, yscale, a, tx, ty); 2958 return res; 2959 } 2960 2961 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).translate(tx, ty)`, only faster 2962 static NVGMatrix RotatedTransformed (in float a, in float tx, in float ty) { 2963 NVGMatrix res = void; 2964 res.rotateTransform(a, tx, ty); 2965 return res; 2966 } 2967 } 2968 2969 2970 /// Converts degrees to radians. 2971 /// Group: matrices 2972 public float nvgDegToRad() (in float deg) pure nothrow @safe @nogc { pragma(inline, true); return deg/180.0f*NVG_PI; } 2973 2974 /// Converts radians to degrees. 2975 /// Group: matrices 2976 public float nvgRadToDeg() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad/NVG_PI*180.0f; } 2977 2978 public alias nvgDegrees = nvgDegToRad; /// Use this like `42.nvgDegrees` 2979 public float nvgRadians() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad; } /// Use this like `0.1.nvgRadians` 2980 2981 2982 // ////////////////////////////////////////////////////////////////////////// // 2983 void nvg__setPaintColor() (ref NVGPaint p, in auto ref NVGColor color) nothrow @trusted @nogc { 2984 p.clear(); 2985 p.xform.identity; 2986 p.radius = 0.0f; 2987 p.feather = 1.0f; 2988 p.innerColor = p.middleColor = p.outerColor = color; 2989 p.midp = -1; 2990 p.simpleColor = true; 2991 } 2992 2993 2994 // ////////////////////////////////////////////////////////////////////////// // 2995 // State handling 2996 2997 version(nanovega_debug_clipping) { 2998 public void nvgClipDumpOn (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, true); } 2999 public void nvgClipDumpOff (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, false); } 3000 } 3001 3002 /** Pushes and saves the current render state into a state stack. 3003 * A matching [restore] must be used to restore the state. 3004 * Returns `false` if state stack overflowed. 3005 * 3006 * Group: state_handling 3007 */ 3008 public bool save (NVGContext ctx) nothrow @trusted @nogc { 3009 if (ctx.nstates >= NVG_MAX_STATES) return false; 3010 if (ctx.nstates > 0) { 3011 //memcpy(&ctx.states[ctx.nstates], &ctx.states[ctx.nstates-1], NVGstate.sizeof); 3012 ctx.states[ctx.nstates] = ctx.states[ctx.nstates-1]; 3013 ctx.params.renderPushClip(ctx.params.userPtr); 3014 } 3015 ++ctx.nstates; 3016 return true; 3017 } 3018 3019 /// Pops and restores current render state. 3020 /// Group: state_handling 3021 public bool restore (NVGContext ctx) nothrow @trusted @nogc { 3022 if (ctx.nstates <= 1) return false; 3023 ctx.states[ctx.nstates-1].clearPaint(); 3024 ctx.params.renderPopClip(ctx.params.userPtr); 3025 --ctx.nstates; 3026 return true; 3027 } 3028 3029 /// Resets current render state to default values. Does not affect the render state stack. 3030 /// Group: state_handling 3031 public void reset (NVGContext ctx) nothrow @trusted @nogc { 3032 NVGstate* state = nvg__getState(ctx); 3033 state.clearPaint(); 3034 3035 nvg__setPaintColor(state.fill, nvgRGBA(255, 255, 255, 255)); 3036 nvg__setPaintColor(state.stroke, nvgRGBA(0, 0, 0, 255)); 3037 state.compositeOperation = nvg__compositeOperationState(NVGCompositeOperation.SourceOver); 3038 state.shapeAntiAlias = true; 3039 state.strokeWidth = 1.0f; 3040 state.miterLimit = 10.0f; 3041 state.lineCap = NVGLineCap.Butt; 3042 state.lineJoin = NVGLineCap.Miter; 3043 state.alpha = 1.0f; 3044 state.xform.identity; 3045 3046 state.scissor.extent[] = -1.0f; 3047 3048 state.fontSize = 16.0f; 3049 state.letterSpacing = 0.0f; 3050 state.lineHeight = 1.0f; 3051 state.fontBlur = 0.0f; 3052 state.textAlign.reset; 3053 state.fontId = 0; 3054 state.evenOddMode = false; 3055 state.dashCount = 0; 3056 state.lastFlattenDashCount = 0; 3057 state.dashStart = 0; 3058 state.firstDashIsGap = false; 3059 state.dasherActive = false; 3060 3061 ctx.params.renderResetClip(ctx.params.userPtr); 3062 } 3063 3064 /** Returns `true` if we have any room in state stack. 3065 * It is guaranteed to have at least 32 stack slots. 3066 * 3067 * Group: state_handling 3068 */ 3069 public bool canSave (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates < NVG_MAX_STATES); } 3070 3071 /** Returns `true` if we have any saved state. 3072 * 3073 * Group: state_handling 3074 */ 3075 public bool canRestore (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates > 1); } 3076 3077 /// Returns `true` if rendering is currently blocked. 3078 /// Group: state_handling 3079 public bool renderBlocked (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.recblockdraw : false); } 3080 3081 /// Blocks/unblocks rendering 3082 /// Group: state_handling 3083 public void renderBlocked (NVGContext ctx, bool v) pure nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null && ctx.contextAlive) ctx.recblockdraw = v; } 3084 3085 /// Blocks/unblocks rendering; returns previous state. 3086 /// Group: state_handling 3087 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; } 3088 3089 3090 // ////////////////////////////////////////////////////////////////////////// // 3091 // Render styles 3092 3093 /// Sets filling mode to "even-odd". 3094 /// Group: render_styles 3095 public void evenOddFill (NVGContext ctx) nothrow @trusted @nogc { 3096 NVGstate* state = nvg__getState(ctx); 3097 state.evenOddMode = true; 3098 } 3099 3100 /// Sets filling mode to "non-zero" (this is default mode). 3101 /// Group: render_styles 3102 public void nonZeroFill (NVGContext ctx) nothrow @trusted @nogc { 3103 NVGstate* state = nvg__getState(ctx); 3104 state.evenOddMode = false; 3105 } 3106 3107 /// Sets whether to draw antialias for [stroke] and [fill]. It's enabled by default. 3108 /// Group: render_styles 3109 public void shapeAntiAlias (NVGContext ctx, bool enabled) { 3110 NVGstate* state = nvg__getState(ctx); 3111 state.shapeAntiAlias = enabled; 3112 } 3113 3114 /// Sets the stroke width of the stroke style. 3115 /// Group: render_styles 3116 @scriptable 3117 public void strokeWidth (NVGContext ctx, float width) nothrow @trusted @nogc { 3118 NVGstate* state = nvg__getState(ctx); 3119 state.strokeWidth = width; 3120 } 3121 3122 /// Sets the miter limit of the stroke style. Miter limit controls when a sharp corner is beveled. 3123 /// Group: render_styles 3124 public void miterLimit (NVGContext ctx, float limit) nothrow @trusted @nogc { 3125 NVGstate* state = nvg__getState(ctx); 3126 state.miterLimit = limit; 3127 } 3128 3129 /// Sets how the end of the line (cap) is drawn, 3130 /// Can be one of: NVGLineCap.Butt (default), NVGLineCap.Round, NVGLineCap.Square. 3131 /// Group: render_styles 3132 public void lineCap (NVGContext ctx, NVGLineCap cap) nothrow @trusted @nogc { 3133 NVGstate* state = nvg__getState(ctx); 3134 state.lineCap = cap; 3135 } 3136 3137 /// Sets how sharp path corners are drawn. 3138 /// Can be one of NVGLineCap.Miter (default), NVGLineCap.Round, NVGLineCap.Bevel. 3139 /// Group: render_styles 3140 public void lineJoin (NVGContext ctx, NVGLineCap join) nothrow @trusted @nogc { 3141 NVGstate* state = nvg__getState(ctx); 3142 state.lineJoin = join; 3143 } 3144 3145 /// Sets stroke dashing, using (dash_length, gap_length) pairs. 3146 /// Current limit is 16 pairs. 3147 /// Resets dash start to zero. 3148 /// Group: render_styles 3149 public void setLineDash (NVGContext ctx, const(float)[] dashdata) nothrow @trusted @nogc { 3150 NVGstate* state = nvg__getState(ctx); 3151 state.dashCount = 0; 3152 state.dashStart = 0; 3153 state.firstDashIsGap = false; 3154 if (dashdata.length >= 2) { 3155 bool curFIsGap = true; // trick 3156 foreach (immutable idx, float f; dashdata) { 3157 curFIsGap = !curFIsGap; 3158 if (f < 0.01f) continue; // skip it 3159 if (idx == 0) { 3160 // register first dash 3161 state.firstDashIsGap = curFIsGap; 3162 state.dashes.ptr[state.dashCount++] = f; 3163 } else { 3164 if ((idx&1) != ((state.dashCount&1)^cast(uint)state.firstDashIsGap)) { 3165 // oops, continuation 3166 state.dashes[state.dashCount-1] += f; 3167 } else { 3168 if (state.dashCount == state.dashes.length) break; 3169 state.dashes[state.dashCount++] = f; 3170 } 3171 } 3172 } 3173 if (state.dashCount&1) { 3174 if (state.dashCount == 1) { 3175 state.dashCount = 0; 3176 } else { 3177 assert(state.dashCount < state.dashes.length); 3178 state.dashes[state.dashCount++] = 0; 3179 } 3180 } 3181 // calculate total dash path length 3182 state.totalDashLen = 0; 3183 foreach (float f; state.dashes.ptr[0..state.dashCount]) state.totalDashLen += f; 3184 if (state.totalDashLen < 0.01f) { 3185 state.dashCount = 0; // nothing to do 3186 } else { 3187 if (state.lastFlattenDashCount != 0) state.lastFlattenDashCount = uint.max; // force re-flattening 3188 } 3189 } 3190 } 3191 3192 public alias lineDash = setLineDash; /// Ditto. 3193 3194 /// Sets stroke dashing, using (dash_length, gap_length) pairs. 3195 /// Current limit is 16 pairs. 3196 /// Group: render_styles 3197 public void setLineDashStart (NVGContext ctx, in float dashStart) nothrow @trusted @nogc { 3198 NVGstate* state = nvg__getState(ctx); 3199 if (state.lastFlattenDashCount != 0 && state.dashStart != dashStart) { 3200 state.lastFlattenDashCount = uint.max; // force re-flattening 3201 } 3202 state.dashStart = dashStart; 3203 } 3204 3205 public alias lineDashStart = setLineDashStart; /// Ditto. 3206 3207 /// Sets the transparency applied to all rendered shapes. 3208 /// Already transparent paths will get proportionally more transparent as well. 3209 /// Group: render_styles 3210 public void globalAlpha (NVGContext ctx, float alpha) nothrow @trusted @nogc { 3211 NVGstate* state = nvg__getState(ctx); 3212 state.alpha = alpha; 3213 } 3214 3215 private void strokeColor() {} 3216 3217 static if (NanoVegaHasArsdColor) { 3218 /// Sets current stroke style to a solid color. 3219 /// Group: render_styles 3220 @scriptable 3221 public void strokeColor (NVGContext ctx, Color color) nothrow @trusted @nogc { 3222 NVGstate* state = nvg__getState(ctx); 3223 nvg__setPaintColor(state.stroke, NVGColor(color)); 3224 } 3225 } 3226 3227 /// Sets current stroke style to a solid color. 3228 /// Group: render_styles 3229 public void strokeColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc { 3230 NVGstate* state = nvg__getState(ctx); 3231 nvg__setPaintColor(state.stroke, color); 3232 } 3233 3234 @scriptable 3235 public void strokePaint(NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc { 3236 strokePaint(ctx, *paint); 3237 } 3238 3239 /// Sets current stroke style to a paint, which can be a one of the gradients or a pattern. 3240 /// Group: render_styles 3241 @scriptable 3242 public void strokePaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc { 3243 NVGstate* state = nvg__getState(ctx); 3244 state.stroke = paint; 3245 //nvgTransformMultiply(state.stroke.xform[], state.xform[]); 3246 state.stroke.xform.mul(state.xform); 3247 } 3248 3249 // this is a hack to work around https://issues.dlang.org/show_bug.cgi?id=16206 3250 // for scriptable reflection. it just needs to be declared first among the overloads 3251 private void fillColor (NVGContext ctx) nothrow @trusted @nogc { } 3252 3253 static if (NanoVegaHasArsdColor) { 3254 /// Sets current fill style to a solid color. 3255 /// Group: render_styles 3256 @scriptable 3257 public void fillColor (NVGContext ctx, Color color) nothrow @trusted @nogc { 3258 NVGstate* state = nvg__getState(ctx); 3259 nvg__setPaintColor(state.fill, NVGColor(color)); 3260 } 3261 } 3262 3263 /// Sets current fill style to a solid color. 3264 /// Group: render_styles 3265 public void fillColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc { 3266 NVGstate* state = nvg__getState(ctx); 3267 nvg__setPaintColor(state.fill, color); 3268 3269 } 3270 3271 @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) 3272 public void fillPaint (NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc { 3273 fillPaint(ctx, *paint); 3274 } 3275 3276 /// Sets current fill style to a paint, which can be a one of the gradients or a pattern. 3277 /// Group: render_styles 3278 @scriptable 3279 public void fillPaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc { 3280 NVGstate* state = nvg__getState(ctx); 3281 state.fill = paint; 3282 //nvgTransformMultiply(state.fill.xform[], state.xform[]); 3283 state.fill.xform.mul(state.xform); 3284 } 3285 3286 /// Sets current fill style to a multistop linear gradient. 3287 /// Group: render_styles 3288 public void fillPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc { 3289 if (!lgs.valid) { 3290 NVGPaint p = void; 3291 memset(&p, 0, p.sizeof); 3292 nvg__setPaintColor(p, NVGColor.red); 3293 ctx.fillPaint = p; 3294 } else if (lgs.midp >= -1) { 3295 //{ import core.stdc.stdio; printf("SIMPLE! midp=%f\n", cast(double)lgs.midp); } 3296 ctx.fillPaint = ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc); 3297 } else { 3298 ctx.fillPaint = ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid); 3299 } 3300 } 3301 3302 /// Returns current transformation matrix. 3303 /// Group: render_transformations 3304 public NVGMatrix currTransform (NVGContext ctx) pure nothrow @trusted @nogc { 3305 NVGstate* state = nvg__getState(ctx); 3306 return state.xform; 3307 } 3308 3309 /// Sets current transformation matrix. 3310 /// Group: render_transformations 3311 public void currTransform() (NVGContext ctx, in auto ref NVGMatrix m) nothrow @trusted @nogc { 3312 NVGstate* state = nvg__getState(ctx); 3313 state.xform = m; 3314 } 3315 3316 /// Resets current transform to an identity matrix. 3317 /// Group: render_transformations 3318 @scriptable 3319 public void resetTransform (NVGContext ctx) nothrow @trusted @nogc { 3320 NVGstate* state = nvg__getState(ctx); 3321 state.xform.identity; 3322 } 3323 3324 /// Premultiplies current coordinate system by specified matrix. 3325 /// Group: render_transformations 3326 public void transform() (NVGContext ctx, in auto ref NVGMatrix mt) nothrow @trusted @nogc { 3327 NVGstate* state = nvg__getState(ctx); 3328 //nvgTransformPremultiply(state.xform[], t[]); 3329 state.xform *= mt; 3330 } 3331 3332 /// Translates current coordinate system. 3333 /// Group: render_transformations 3334 @scriptable 3335 public void translate (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 3336 NVGstate* state = nvg__getState(ctx); 3337 //NVGMatrix t = void; 3338 //nvgTransformTranslate(t[], x, y); 3339 //nvgTransformPremultiply(state.xform[], t[]); 3340 state.xform.premul(NVGMatrix.Translated(x, y)); 3341 } 3342 3343 /// Rotates current coordinate system. Angle is specified in radians. 3344 /// Group: render_transformations 3345 @scriptable 3346 public void rotate (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3347 NVGstate* state = nvg__getState(ctx); 3348 //NVGMatrix t = void; 3349 //nvgTransformRotate(t[], angle); 3350 //nvgTransformPremultiply(state.xform[], t[]); 3351 state.xform.premul(NVGMatrix.Rotated(angle)); 3352 } 3353 3354 /// Skews the current coordinate system along X axis. Angle is specified in radians. 3355 /// Group: render_transformations 3356 @scriptable 3357 public void skewX (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3358 NVGstate* state = nvg__getState(ctx); 3359 //NVGMatrix t = void; 3360 //nvgTransformSkewX(t[], angle); 3361 //nvgTransformPremultiply(state.xform[], t[]); 3362 state.xform.premul(NVGMatrix.SkewedX(angle)); 3363 } 3364 3365 /// Skews the current coordinate system along Y axis. Angle is specified in radians. 3366 /// Group: render_transformations 3367 @scriptable 3368 public void skewY (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3369 NVGstate* state = nvg__getState(ctx); 3370 //NVGMatrix t = void; 3371 //nvgTransformSkewY(t[], angle); 3372 //nvgTransformPremultiply(state.xform[], t[]); 3373 state.xform.premul(NVGMatrix.SkewedY(angle)); 3374 } 3375 3376 /// Scales the current coordinate system. 3377 /// Group: render_transformations 3378 @scriptable 3379 public void scale (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 3380 NVGstate* state = nvg__getState(ctx); 3381 //NVGMatrix t = void; 3382 //nvgTransformScale(t[], x, y); 3383 //nvgTransformPremultiply(state.xform[], t[]); 3384 state.xform.premul(NVGMatrix.Scaled(x, y)); 3385 } 3386 3387 3388 // ////////////////////////////////////////////////////////////////////////// // 3389 // Images 3390 3391 /// Creates image by loading it from the disk from specified file name. 3392 /// Returns handle to the image or 0 on error. 3393 /// Group: images 3394 public NVGImage createImage() (NVGContext ctx, const(char)[] filename, const(NVGImageFlag)[] imageFlagsList...) { 3395 static if (NanoVegaHasArsdImage) { 3396 import arsd.image; 3397 // do we have new arsd API to load images? 3398 static if (!is(typeof(MemoryImage.fromImageFile)) || !is(typeof(MemoryImage.clearInternal))) { 3399 static assert(0, "Sorry, your ARSD is too old. Please, update it."); 3400 } 3401 try { 3402 auto oimg = MemoryImage.fromImageFile(filename); 3403 if (auto img = cast(TrueColorImage)oimg) { 3404 scope(exit) oimg.clearInternal(); 3405 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList); 3406 } else { 3407 TrueColorImage img = oimg.getAsTrueColorImage; 3408 scope(exit) img.clearInternal(); 3409 oimg.clearInternal(); // drop original image, as `getAsTrueColorImage()` MUST create a new one here 3410 oimg = null; 3411 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList); 3412 } 3413 } catch (Exception) {} 3414 return NVGImage.init; 3415 } else { 3416 import std.internal.cstring; 3417 ubyte* img; 3418 int w, h, n; 3419 stbi_set_unpremultiply_on_load(1); 3420 stbi_convert_iphone_png_to_rgb(1); 3421 img = stbi_load(filename.tempCString, &w, &h, &n, 4); 3422 if (img is null) { 3423 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); 3424 return NVGImage.init; 3425 } 3426 auto image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList); 3427 stbi_image_free(img); 3428 return image; 3429 } 3430 } 3431 3432 static if (NanoVegaHasArsdImage) { 3433 /// Creates image by loading it from the specified memory image. 3434 /// Returns handle to the image or 0 on error. 3435 /// Group: images 3436 public NVGImage createImageFromMemoryImage() (NVGContext ctx, MemoryImage img, const(NVGImageFlag)[] imageFlagsList...) { 3437 if (img is null) return NVGImage.init; 3438 if (auto tc = cast(TrueColorImage)img) { 3439 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList); 3440 } else { 3441 auto tc = img.getAsTrueColorImage; 3442 scope(exit) tc.clearInternal(); // here, it is guaranteed that `tc` is newly allocated image, so it is safe to kill it 3443 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList); 3444 } 3445 } 3446 } else { 3447 /// Creates image by loading it from the specified chunk of memory. 3448 /// Returns handle to the image or 0 on error. 3449 /// Group: images 3450 public NVGImage createImageMem() (NVGContext ctx, const(ubyte)* data, int ndata, const(NVGImageFlag)[] imageFlagsList...) { 3451 int w, h, n, image; 3452 ubyte* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4); 3453 if (img is null) { 3454 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); 3455 return NVGImage.init; 3456 } 3457 image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList); 3458 stbi_image_free(img); 3459 return image; 3460 } 3461 } 3462 3463 /// Creates image from specified image data. 3464 /// Returns handle to the image or 0 on error. 3465 /// Group: images 3466 public NVGImage createImageRGBA (NVGContext ctx, int w, int h, const(void)[] data, const(NVGImageFlag)[] imageFlagsList...) nothrow @trusted @nogc { 3467 if (w < 1 || h < 1 || data.length < w*h*4) return NVGImage.init; 3468 uint imageFlags = 0; 3469 foreach (immutable uint flag; imageFlagsList) imageFlags |= flag; 3470 NVGImage res; 3471 res.id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.RGBA, w, h, imageFlags, cast(const(ubyte)*)data.ptr); 3472 if (res.id > 0) { 3473 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); } 3474 res.ctx = ctx; 3475 ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount 3476 } 3477 return res; 3478 } 3479 3480 /// Updates image data specified by image handle. 3481 /// Group: images 3482 public void updateImage() (NVGContext ctx, auto ref NVGImage image, const(void)[] data) nothrow @trusted @nogc { 3483 if (image.valid) { 3484 int w, h; 3485 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3486 ctx.params.renderGetTextureSize(ctx.params.userPtr, image.id, &w, &h); 3487 ctx.params.renderUpdateTexture(ctx.params.userPtr, image.id, 0, 0, w, h, cast(const(ubyte)*)data.ptr); 3488 } 3489 } 3490 3491 /// Returns the dimensions of a created image. 3492 /// Group: images 3493 public void imageSize() (NVGContext ctx, in auto ref NVGImage image, out int w, out int h) nothrow @trusted @nogc { 3494 if (image.valid) { 3495 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3496 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, image.id, &w, &h); 3497 } 3498 } 3499 3500 /// Deletes created image. 3501 /// Group: images 3502 public void deleteImage() (NVGContext ctx, ref NVGImage image) nothrow @trusted @nogc { 3503 if (ctx is null || !image.valid) return; 3504 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3505 image.clear(); 3506 } 3507 3508 3509 // ////////////////////////////////////////////////////////////////////////// // 3510 // Paints 3511 3512 private void linearGradient() {} // hack for dmd bug 3513 3514 static if (NanoVegaHasArsdColor) { 3515 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates 3516 * of the linear gradient, icol specifies the start color and ocol the end color. 3517 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3518 * 3519 * Group: paints 3520 */ 3521 @scriptable 3522 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 { 3523 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), NVGColor(ocol)); 3524 } 3525 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start 3526 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in 3527 * range `(0..1)`, and ocol the end color. 3528 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3529 * 3530 * Group: paints 3531 */ 3532 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 { 3533 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), midp, NVGColor(mcol), NVGColor(ocol)); 3534 } 3535 } 3536 3537 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates 3538 * of the linear gradient, icol specifies the start color and ocol the end color. 3539 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3540 * 3541 * Group: paints 3542 */ 3543 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 { 3544 enum large = 1e5f; 3545 3546 NVGPaint p = void; 3547 memset(&p, 0, p.sizeof); 3548 p.simpleColor = false; 3549 3550 // Calculate transform aligned to the line 3551 float dx = ex-sx; 3552 float dy = ey-sy; 3553 immutable float d = nvg__sqrtf(dx*dx+dy*dy); 3554 if (d > 0.0001f) { 3555 dx /= d; 3556 dy /= d; 3557 } else { 3558 dx = 0; 3559 dy = 1; 3560 } 3561 3562 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx; 3563 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy; 3564 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large; 3565 3566 p.extent.ptr[0] = large; 3567 p.extent.ptr[1] = large+d*0.5f; 3568 3569 p.radius = 0.0f; 3570 3571 p.feather = nvg__max(NVG_MIN_FEATHER, d); 3572 3573 p.innerColor = p.middleColor = icol; 3574 p.outerColor = ocol; 3575 p.midp = -1; 3576 3577 return p; 3578 } 3579 3580 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start 3581 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in 3582 * range `(0..1)`, and ocol the end color. 3583 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3584 * 3585 * Group: paints 3586 */ 3587 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 { 3588 enum large = 1e5f; 3589 3590 NVGPaint p = void; 3591 memset(&p, 0, p.sizeof); 3592 p.simpleColor = false; 3593 3594 // Calculate transform aligned to the line 3595 float dx = ex-sx; 3596 float dy = ey-sy; 3597 immutable float d = nvg__sqrtf(dx*dx+dy*dy); 3598 if (d > 0.0001f) { 3599 dx /= d; 3600 dy /= d; 3601 } else { 3602 dx = 0; 3603 dy = 1; 3604 } 3605 3606 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx; 3607 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy; 3608 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large; 3609 3610 p.extent.ptr[0] = large; 3611 p.extent.ptr[1] = large+d*0.5f; 3612 3613 p.radius = 0.0f; 3614 3615 p.feather = nvg__max(NVG_MIN_FEATHER, d); 3616 3617 if (midp <= 0) { 3618 p.innerColor = p.middleColor = mcol; 3619 p.midp = -1; 3620 } else if (midp > 1) { 3621 p.innerColor = p.middleColor = icol; 3622 p.midp = -1; 3623 } else { 3624 p.innerColor = icol; 3625 p.middleColor = mcol; 3626 p.midp = midp; 3627 } 3628 p.outerColor = ocol; 3629 3630 return p; 3631 } 3632 3633 static if (NanoVegaHasArsdColor) { 3634 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify 3635 * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. 3636 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3637 * 3638 * Group: paints 3639 */ 3640 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 { 3641 return ctx.radialGradient(cx, cy, inr, outr, NVGColor(icol), NVGColor(ocol)); 3642 } 3643 } 3644 3645 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify 3646 * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. 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 radialGradient() (NVGContext ctx, float cx, float cy, float inr, float outr, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc { 3652 immutable float r = (inr+outr)*0.5f; 3653 immutable float f = (outr-inr); 3654 3655 NVGPaint p = void; 3656 memset(&p, 0, p.sizeof); 3657 p.simpleColor = false; 3658 3659 p.xform.identity; 3660 p.xform.mat.ptr[4] = cx; 3661 p.xform.mat.ptr[5] = cy; 3662 3663 p.extent.ptr[0] = r; 3664 p.extent.ptr[1] = r; 3665 3666 p.radius = r; 3667 3668 p.feather = nvg__max(NVG_MIN_FEATHER, f); 3669 3670 p.innerColor = p.middleColor = icol; 3671 p.outerColor = ocol; 3672 p.midp = -1; 3673 3674 return p; 3675 } 3676 3677 static if (NanoVegaHasArsdColor) { 3678 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering 3679 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle, 3680 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry 3681 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. 3682 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3683 * 3684 * Group: paints 3685 */ 3686 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 { 3687 return ctx.boxGradient(x, y, w, h, r, f, NVGColor(icol), NVGColor(ocol)); 3688 } 3689 } 3690 3691 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering 3692 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle, 3693 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry 3694 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. 3695 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3696 * 3697 * Group: paints 3698 */ 3699 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 { 3700 NVGPaint p = void; 3701 memset(&p, 0, p.sizeof); 3702 p.simpleColor = false; 3703 3704 p.xform.identity; 3705 p.xform.mat.ptr[4] = x+w*0.5f; 3706 p.xform.mat.ptr[5] = y+h*0.5f; 3707 3708 p.extent.ptr[0] = w*0.5f; 3709 p.extent.ptr[1] = h*0.5f; 3710 3711 p.radius = r; 3712 3713 p.feather = nvg__max(NVG_MIN_FEATHER, f); 3714 3715 p.innerColor = p.middleColor = icol; 3716 p.outerColor = ocol; 3717 p.midp = -1; 3718 3719 return p; 3720 } 3721 3722 /** Creates and returns an image pattern. Parameters `(cx, cy)` specify the left-top location of the image pattern, 3723 * `(w, h)` the size of one image, [angle] rotation around the top-left corner, [image] is handle to the image to render. 3724 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3725 * 3726 * Group: paints 3727 */ 3728 public NVGPaint imagePattern() (NVGContext ctx, float cx, float cy, float w, float h, float angle, in auto ref NVGImage image, float alpha=1) nothrow @trusted @nogc { 3729 NVGPaint p = void; 3730 memset(&p, 0, p.sizeof); 3731 p.simpleColor = false; 3732 3733 p.xform.identity.rotate(angle); 3734 p.xform.mat.ptr[4] = cx; 3735 p.xform.mat.ptr[5] = cy; 3736 3737 p.extent.ptr[0] = w; 3738 p.extent.ptr[1] = h; 3739 3740 p.image = image; 3741 3742 p.innerColor = p.middleColor = p.outerColor = nvgRGBAf(1, 1, 1, alpha); 3743 p.midp = -1; 3744 3745 return p; 3746 } 3747 3748 /// Linear gradient with multiple stops. 3749 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3750 /// Group: paints 3751 public struct NVGLGS { 3752 private: 3753 NVGColor ic, mc, oc; // inner, middle, out 3754 float midp; 3755 NVGImage imgid; 3756 // [imagePattern] arguments 3757 float cx, cy, dimx, dimy; // dimx and dimy are ex and ey for simple gradients 3758 public float angle; /// 3759 3760 public: 3761 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (imgid.valid || midp >= -1); } /// 3762 void clear () nothrow @safe @nogc { pragma(inline, true); imgid.clear(); midp = float.nan; } /// 3763 } 3764 3765 /** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops]. 3766 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3767 * 3768 * $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3769 * Group: paints 3770 */ 3771 public NVGPaint asPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc { 3772 if (!lgs.valid) { 3773 NVGPaint p = void; 3774 memset(&p, 0, p.sizeof); 3775 nvg__setPaintColor(p, NVGColor.red); 3776 return p; 3777 } else if (lgs.midp >= -1) { 3778 return ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc); 3779 } else { 3780 return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid); 3781 } 3782 } 3783 3784 /// Gradient Stop Point. 3785 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3786 /// Group: paints 3787 public struct NVGGradientStop { 3788 float offset = 0; /// [0..1] 3789 NVGColor color; /// 3790 3791 this() (in float aofs, in auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } /// 3792 static if (NanoVegaHasArsdColor) { 3793 this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } /// 3794 } 3795 } 3796 3797 /// Create linear gradient data suitable to use with `linearGradient(res)`. 3798 /// Don't forget to destroy the result when you don't need it anymore with `ctx.kill(res);`. 3799 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3800 /// Group: paints 3801 public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, const(NVGGradientStop)[] stops...) nothrow @trusted @nogc { 3802 // based on the code by Jorge Acereda <jacereda@gmail.com> 3803 enum NVG_GRADIENT_SAMPLES = 1024; 3804 static void gradientSpan (uint* dst, const(NVGGradientStop)* s0, const(NVGGradientStop)* s1) nothrow @trusted @nogc { 3805 immutable float s0o = nvg__clamp(s0.offset, 0.0f, 1.0f); 3806 immutable float s1o = nvg__clamp(s1.offset, 0.0f, 1.0f); 3807 uint s = cast(uint)(s0o*NVG_GRADIENT_SAMPLES); 3808 uint e = cast(uint)(s1o*NVG_GRADIENT_SAMPLES); 3809 uint sc = 0xffffffffU; 3810 uint sh = 24; 3811 uint r = cast(uint)(s0.color.rgba[0]*sc); 3812 uint g = cast(uint)(s0.color.rgba[1]*sc); 3813 uint b = cast(uint)(s0.color.rgba[2]*sc); 3814 uint a = cast(uint)(s0.color.rgba[3]*sc); 3815 uint dr = cast(uint)((s1.color.rgba[0]*sc-r)/(e-s)); 3816 uint dg = cast(uint)((s1.color.rgba[1]*sc-g)/(e-s)); 3817 uint db = cast(uint)((s1.color.rgba[2]*sc-b)/(e-s)); 3818 uint da = cast(uint)((s1.color.rgba[3]*sc-a)/(e-s)); 3819 dst += s; 3820 foreach (immutable _; s..e) { 3821 version(BigEndian) { 3822 *dst++ = ((r>>sh)<<24)+((g>>sh)<<16)+((b>>sh)<<8)+((a>>sh)<<0); 3823 } else { 3824 *dst++ = ((a>>sh)<<24)+((b>>sh)<<16)+((g>>sh)<<8)+((r>>sh)<<0); 3825 } 3826 r += dr; 3827 g += dg; 3828 b += db; 3829 a += da; 3830 } 3831 } 3832 3833 NVGLGS res; 3834 res.cx = sx; 3835 res.cy = sy; 3836 3837 if (stops.length == 2 && stops.ptr[0].offset <= 0 && stops.ptr[1].offset >= 1) { 3838 // create simple linear gradient 3839 res.ic = res.mc = stops.ptr[0].color; 3840 res.oc = stops.ptr[1].color; 3841 res.midp = -1; 3842 res.dimx = ex; 3843 res.dimy = ey; 3844 } else if (stops.length == 3 && stops.ptr[0].offset <= 0 && stops.ptr[2].offset >= 1) { 3845 // create simple linear gradient with middle stop 3846 res.ic = stops.ptr[0].color; 3847 res.mc = stops.ptr[1].color; 3848 res.oc = stops.ptr[2].color; 3849 res.midp = stops.ptr[1].offset; 3850 res.dimx = ex; 3851 res.dimy = ey; 3852 } else { 3853 // create image gradient 3854 uint[NVG_GRADIENT_SAMPLES] data = void; 3855 immutable float w = ex-sx; 3856 immutable float h = ey-sy; 3857 res.dimx = nvg__sqrtf(w*w+h*h); 3858 res.dimy = 1; //??? 3859 3860 res.angle = 3861 (/*nvg__absf(h) < 0.0001 ? 0 : 3862 nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/ 3863 nvg__atan2f(h/*ey-sy*/, w/*ex-sx*/)); 3864 3865 if (stops.length > 0) { 3866 auto s0 = NVGGradientStop(0, nvgRGBAf(0, 0, 0, 1)); 3867 auto s1 = NVGGradientStop(1, nvgRGBAf(1, 1, 1, 1)); 3868 if (stops.length > 64) stops = stops[0..64]; 3869 if (stops.length) { 3870 s0.color = stops[0].color; 3871 s1.color = stops[$-1].color; 3872 } 3873 gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1)); 3874 foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1); 3875 gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1); 3876 res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[]/*, NVGImageFlag.RepeatX, NVGImageFlag.RepeatY*/); 3877 } 3878 } 3879 return res; 3880 } 3881 3882 3883 // ////////////////////////////////////////////////////////////////////////// // 3884 // Scissoring 3885 3886 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform. 3887 /// Group: scissoring 3888 public void scissor (NVGContext ctx, in float x, in float y, float w, float h) nothrow @trusted @nogc { 3889 NVGstate* state = nvg__getState(ctx); 3890 3891 w = nvg__max(0.0f, w); 3892 h = nvg__max(0.0f, h); 3893 3894 state.scissor.xform.identity; 3895 state.scissor.xform.mat.ptr[4] = x+w*0.5f; 3896 state.scissor.xform.mat.ptr[5] = y+h*0.5f; 3897 //nvgTransformMultiply(state.scissor.xform[], state.xform[]); 3898 state.scissor.xform.mul(state.xform); 3899 3900 state.scissor.extent.ptr[0] = w*0.5f; 3901 state.scissor.extent.ptr[1] = h*0.5f; 3902 } 3903 3904 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform. 3905 /// Arguments: [x, y, w, h]* 3906 /// Group: scissoring 3907 public void scissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 3908 enum ArgC = 4; 3909 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [scissor] call"); 3910 if (args.length < ArgC) return; 3911 NVGstate* state = nvg__getState(ctx); 3912 const(float)* aptr = args.ptr; 3913 foreach (immutable idx; 0..args.length/ArgC) { 3914 immutable x = *aptr++; 3915 immutable y = *aptr++; 3916 immutable w = nvg__max(0.0f, *aptr++); 3917 immutable h = nvg__max(0.0f, *aptr++); 3918 3919 state.scissor.xform.identity; 3920 state.scissor.xform.mat.ptr[4] = x+w*0.5f; 3921 state.scissor.xform.mat.ptr[5] = y+h*0.5f; 3922 //nvgTransformMultiply(state.scissor.xform[], state.xform[]); 3923 state.scissor.xform.mul(state.xform); 3924 3925 state.scissor.extent.ptr[0] = w*0.5f; 3926 state.scissor.extent.ptr[1] = h*0.5f; 3927 } 3928 } 3929 3930 void nvg__isectRects (float* dst, float ax, float ay, float aw, float ah, float bx, float by, float bw, float bh) nothrow @trusted @nogc { 3931 immutable float minx = nvg__max(ax, bx); 3932 immutable float miny = nvg__max(ay, by); 3933 immutable float maxx = nvg__min(ax+aw, bx+bw); 3934 immutable float maxy = nvg__min(ay+ah, by+bh); 3935 dst[0] = minx; 3936 dst[1] = miny; 3937 dst[2] = nvg__max(0.0f, maxx-minx); 3938 dst[3] = nvg__max(0.0f, maxy-miny); 3939 } 3940 3941 /** Intersects current scissor rectangle with the specified rectangle. 3942 * The scissor rectangle is transformed by the current transform. 3943 * Note: in case the rotation of previous scissor rect differs from 3944 * the current one, the intersection will be done between the specified 3945 * rectangle and the previous scissor rectangle transformed in the current 3946 * transform space. The resulting shape is always rectangle. 3947 * 3948 * Group: scissoring 3949 */ 3950 public void intersectScissor (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc { 3951 NVGstate* state = nvg__getState(ctx); 3952 3953 // If no previous scissor has been set, set the scissor as current scissor. 3954 if (state.scissor.extent.ptr[0] < 0) { 3955 ctx.scissor(x, y, w, h); 3956 return; 3957 } 3958 3959 NVGMatrix pxform = void; 3960 NVGMatrix invxorm = void; 3961 float[4] rect = void; 3962 3963 // Transform the current scissor rect into current transform space. 3964 // If there is difference in rotation, this will be approximation. 3965 //memcpy(pxform.mat.ptr, state.scissor.xform.ptr, float.sizeof*6); 3966 pxform = state.scissor.xform; 3967 immutable float ex = state.scissor.extent.ptr[0]; 3968 immutable float ey = state.scissor.extent.ptr[1]; 3969 //nvgTransformInverse(invxorm[], state.xform[]); 3970 invxorm = state.xform.inverted; 3971 //nvgTransformMultiply(pxform[], invxorm[]); 3972 pxform.mul(invxorm); 3973 immutable float tex = ex*nvg__absf(pxform.mat.ptr[0])+ey*nvg__absf(pxform.mat.ptr[2]); 3974 immutable float tey = ex*nvg__absf(pxform.mat.ptr[1])+ey*nvg__absf(pxform.mat.ptr[3]); 3975 3976 // Intersect rects. 3977 nvg__isectRects(rect.ptr, pxform.mat.ptr[4]-tex, pxform.mat.ptr[5]-tey, tex*2, tey*2, x, y, w, h); 3978 3979 //ctx.scissor(rect.ptr[0], rect.ptr[1], rect.ptr[2], rect.ptr[3]); 3980 ctx.scissor(rect.ptr[0..4]); 3981 } 3982 3983 /** Intersects current scissor rectangle with the specified rectangle. 3984 * The scissor rectangle is transformed by the current transform. 3985 * Note: in case the rotation of previous scissor rect differs from 3986 * the current one, the intersection will be done between the specified 3987 * rectangle and the previous scissor rectangle transformed in the current 3988 * transform space. The resulting shape is always rectangle. 3989 * 3990 * Arguments: [x, y, w, h]* 3991 * 3992 * Group: scissoring 3993 */ 3994 public void intersectScissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 3995 enum ArgC = 4; 3996 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [intersectScissor] call"); 3997 if (args.length < ArgC) return; 3998 const(float)* aptr = args.ptr; 3999 foreach (immutable idx; 0..args.length/ArgC) { 4000 immutable x = *aptr++; 4001 immutable y = *aptr++; 4002 immutable w = *aptr++; 4003 immutable h = *aptr++; 4004 ctx.intersectScissor(x, y, w, h); 4005 } 4006 } 4007 4008 /// Reset and disables scissoring. 4009 /// Group: scissoring 4010 public void resetScissor (NVGContext ctx) nothrow @trusted @nogc { 4011 NVGstate* state = nvg__getState(ctx); 4012 state.scissor.xform.mat[] = 0.0f; 4013 state.scissor.extent[] = -1.0f; 4014 } 4015 4016 4017 // ////////////////////////////////////////////////////////////////////////// // 4018 // Render-Time Affine Transformations 4019 4020 /// Sets GPU affine transformatin matrix. Don't do scaling or skewing here. 4021 /// This matrix won't be saved/restored with context state save/restore operations, as it is not a part of that state. 4022 /// Group: gpu_affine 4023 public void affineGPU() (NVGContext ctx, in auto ref NVGMatrix mat) nothrow @trusted @nogc { 4024 ctx.gpuAffine = mat; 4025 ctx.params.renderSetAffine(ctx.params.userPtr, ctx.gpuAffine); 4026 } 4027 4028 /// Get current GPU affine transformatin matrix. 4029 /// Group: gpu_affine 4030 public NVGMatrix affineGPU (NVGContext ctx) nothrow @safe @nogc { 4031 pragma(inline, true); 4032 return ctx.gpuAffine; 4033 } 4034 4035 /// "Untransform" point using current GPU affine matrix. 4036 /// Group: gpu_affine 4037 public void gpuUntransformPoint (NVGContext ctx, float *dx, float *dy, in float x, in float y) nothrow @safe @nogc { 4038 if (ctx.gpuAffine.isIdentity) { 4039 if (dx !is null) *dx = x; 4040 if (dy !is null) *dy = y; 4041 } else { 4042 // inverse GPU transformation 4043 NVGMatrix igpu = ctx.gpuAffine.inverted; 4044 igpu.point(dx, dy, x, y); 4045 } 4046 } 4047 4048 4049 // ////////////////////////////////////////////////////////////////////////// // 4050 // rasterization (tesselation) code 4051 4052 int nvg__ptEquals (float x1, float y1, float x2, float y2, float tol) pure nothrow @safe @nogc { 4053 //pragma(inline, true); 4054 immutable float dx = x2-x1; 4055 immutable float dy = y2-y1; 4056 return dx*dx+dy*dy < tol*tol; 4057 } 4058 4059 float nvg__distPtSeg (float x, float y, float px, float py, float qx, float qy) pure nothrow @safe @nogc { 4060 immutable float pqx = qx-px; 4061 immutable float pqy = qy-py; 4062 float dx = x-px; 4063 float dy = y-py; 4064 immutable float d = pqx*pqx+pqy*pqy; 4065 float t = pqx*dx+pqy*dy; 4066 if (d > 0) t /= d; 4067 if (t < 0) t = 0; else if (t > 1) t = 1; 4068 dx = px+t*pqx-x; 4069 dy = py+t*pqy-y; 4070 return dx*dx+dy*dy; 4071 } 4072 4073 void nvg__appendCommands(bool useCommand=true) (NVGContext ctx, Command acmd, const(float)[] vals...) nothrow @trusted @nogc { 4074 int nvals = cast(int)vals.length; 4075 static if (useCommand) { 4076 enum addon = 1; 4077 } else { 4078 enum addon = 0; 4079 if (nvals == 0) return; // nothing to do 4080 } 4081 4082 NVGstate* state = nvg__getState(ctx); 4083 4084 if (ctx.ncommands+nvals+addon > ctx.ccommands) { 4085 //int ccommands = ctx.ncommands+nvals+ctx.ccommands/2; 4086 int ccommands = ((ctx.ncommands+(nvals+addon))|0xfff)+1; 4087 float* commands = cast(float*)realloc(ctx.commands, float.sizeof*ccommands); 4088 if (commands is null) assert(0, "NanoVega: out of memory"); 4089 ctx.commands = commands; 4090 ctx.ccommands = ccommands; 4091 assert(ctx.ncommands+(nvals+addon) <= ctx.ccommands); 4092 } 4093 4094 static if (!useCommand) acmd = cast(Command)vals.ptr[0]; 4095 4096 if (acmd != Command.Close && acmd != Command.Winding) { 4097 //assert(nvals+addon >= 3); 4098 ctx.commandx = vals.ptr[nvals-2]; 4099 ctx.commandy = vals.ptr[nvals-1]; 4100 } 4101 4102 // copy commands 4103 float* vp = ctx.commands+ctx.ncommands; 4104 static if (useCommand) { 4105 vp[0] = cast(float)acmd; 4106 if (nvals > 0) memcpy(vp+1, vals.ptr, nvals*float.sizeof); 4107 } else { 4108 memcpy(vp, vals.ptr, nvals*float.sizeof); 4109 } 4110 ctx.ncommands += nvals+addon; 4111 4112 // transform commands 4113 int i = nvals+addon; 4114 while (i > 0) { 4115 int nlen = 1; 4116 final switch (cast(Command)(*vp)) { 4117 case Command.MoveTo: 4118 case Command.LineTo: 4119 assert(i >= 3); 4120 state.xform.point(vp+1, vp+2, vp[1], vp[2]); 4121 nlen = 3; 4122 break; 4123 case Command.BezierTo: 4124 assert(i >= 7); 4125 state.xform.point(vp+1, vp+2, vp[1], vp[2]); 4126 state.xform.point(vp+3, vp+4, vp[3], vp[4]); 4127 state.xform.point(vp+5, vp+6, vp[5], vp[6]); 4128 nlen = 7; 4129 break; 4130 case Command.Close: 4131 nlen = 1; 4132 break; 4133 case Command.Winding: 4134 nlen = 2; 4135 break; 4136 } 4137 assert(nlen > 0 && nlen <= i); 4138 i -= nlen; 4139 vp += nlen; 4140 } 4141 } 4142 4143 void nvg__clearPathCache (NVGContext ctx) nothrow @trusted @nogc { 4144 // no need to clear paths, as data is not copied there 4145 //foreach (ref p; ctx.cache.paths[0..ctx.cache.npaths]) p.clear(); 4146 ctx.cache.npoints = 0; 4147 ctx.cache.npaths = 0; 4148 ctx.cache.fillReady = ctx.cache.strokeReady = false; 4149 ctx.cache.clipmode = NVGClipMode.None; 4150 } 4151 4152 NVGpath* nvg__lastPath (NVGContext ctx) nothrow @trusted @nogc { 4153 return (ctx.cache.npaths > 0 ? &ctx.cache.paths[ctx.cache.npaths-1] : null); 4154 } 4155 4156 void nvg__addPath (NVGContext ctx) nothrow @trusted @nogc { 4157 import core.stdc.stdlib : realloc; 4158 import core.stdc.string : memset; 4159 4160 if (ctx.cache.npaths+1 > ctx.cache.cpaths) { 4161 int cpaths = ctx.cache.npaths+1+ctx.cache.cpaths/2; 4162 NVGpath* paths = cast(NVGpath*)realloc(ctx.cache.paths, NVGpath.sizeof*cpaths); 4163 if (paths is null) assert(0, "NanoVega: out of memory"); 4164 ctx.cache.paths = paths; 4165 ctx.cache.cpaths = cpaths; 4166 } 4167 4168 NVGpath* path = &ctx.cache.paths[ctx.cache.npaths++]; 4169 memset(path, 0, NVGpath.sizeof); 4170 path.first = ctx.cache.npoints; 4171 path.mWinding = NVGWinding.CCW; 4172 } 4173 4174 NVGpoint* nvg__lastPoint (NVGContext ctx) nothrow @trusted @nogc { 4175 return (ctx.cache.npoints > 0 ? &ctx.cache.points[ctx.cache.npoints-1] : null); 4176 } 4177 4178 void nvg__addPoint (NVGContext ctx, float x, float y, int flags) nothrow @trusted @nogc { 4179 NVGpath* path = nvg__lastPath(ctx); 4180 if (path is null) return; 4181 4182 if (path.count > 0 && ctx.cache.npoints > 0) { 4183 NVGpoint* pt = nvg__lastPoint(ctx); 4184 if (nvg__ptEquals(pt.x, pt.y, x, y, ctx.distTol)) { 4185 pt.flags |= flags; 4186 return; 4187 } 4188 } 4189 4190 if (ctx.cache.npoints+1 > ctx.cache.cpoints) { 4191 int cpoints = ctx.cache.npoints+1+ctx.cache.cpoints/2; 4192 NVGpoint* points = cast(NVGpoint*)realloc(ctx.cache.points, NVGpoint.sizeof*cpoints); 4193 if (points is null) return; 4194 ctx.cache.points = points; 4195 ctx.cache.cpoints = cpoints; 4196 } 4197 4198 NVGpoint* pt = &ctx.cache.points[ctx.cache.npoints]; 4199 memset(pt, 0, (*pt).sizeof); 4200 pt.x = x; 4201 pt.y = y; 4202 pt.flags = cast(ubyte)flags; 4203 4204 ++ctx.cache.npoints; 4205 ++path.count; 4206 } 4207 4208 void nvg__closePath (NVGContext ctx) nothrow @trusted @nogc { 4209 NVGpath* path = nvg__lastPath(ctx); 4210 if (path is null) return; 4211 path.closed = true; 4212 } 4213 4214 void nvg__pathWinding (NVGContext ctx, NVGWinding winding) nothrow @trusted @nogc { 4215 NVGpath* path = nvg__lastPath(ctx); 4216 if (path is null) return; 4217 path.mWinding = winding; 4218 } 4219 4220 float nvg__getAverageScale() (in auto ref NVGMatrix t) /*pure*/ nothrow @trusted @nogc { 4221 immutable float sx = nvg__sqrtf(t.mat.ptr[0]*t.mat.ptr[0]+t.mat.ptr[2]*t.mat.ptr[2]); 4222 immutable float sy = nvg__sqrtf(t.mat.ptr[1]*t.mat.ptr[1]+t.mat.ptr[3]*t.mat.ptr[3]); 4223 return (sx+sy)*0.5f; 4224 } 4225 4226 NVGVertex* nvg__allocTempVerts (NVGContext ctx, int nverts) nothrow @trusted @nogc { 4227 if (nverts > ctx.cache.cverts) { 4228 int cverts = (nverts+0xff)&~0xff; // Round up to prevent allocations when things change just slightly. 4229 NVGVertex* verts = cast(NVGVertex*)realloc(ctx.cache.verts, NVGVertex.sizeof*cverts); 4230 if (verts is null) return null; 4231 ctx.cache.verts = verts; 4232 ctx.cache.cverts = cverts; 4233 } 4234 4235 return ctx.cache.verts; 4236 } 4237 4238 float nvg__triarea2 (float ax, float ay, float bx, float by, float cx, float cy) pure nothrow @safe @nogc { 4239 immutable float abx = bx-ax; 4240 immutable float aby = by-ay; 4241 immutable float acx = cx-ax; 4242 immutable float acy = cy-ay; 4243 return acx*aby-abx*acy; 4244 } 4245 4246 float nvg__polyArea (NVGpoint* pts, int npts) nothrow @trusted @nogc { 4247 float area = 0; 4248 foreach (int i; 2..npts) { 4249 NVGpoint* a = &pts[0]; 4250 NVGpoint* b = &pts[i-1]; 4251 NVGpoint* c = &pts[i]; 4252 area += nvg__triarea2(a.x, a.y, b.x, b.y, c.x, c.y); 4253 } 4254 return area*0.5f; 4255 } 4256 4257 void nvg__polyReverse (NVGpoint* pts, int npts) nothrow @trusted @nogc { 4258 NVGpoint tmp = void; 4259 int i = 0, j = npts-1; 4260 while (i < j) { 4261 tmp = pts[i]; 4262 pts[i] = pts[j]; 4263 pts[j] = tmp; 4264 ++i; 4265 --j; 4266 } 4267 } 4268 4269 void nvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc { 4270 vtx.x = x; 4271 vtx.y = y; 4272 vtx.u = u; 4273 vtx.v = v; 4274 } 4275 4276 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 { 4277 if (level > 10) return; 4278 4279 // check for collinear points, and use AFD tesselator on such curves (it is WAY faster for this case) 4280 /* 4281 if (level == 0 && ctx.tesselatortype == NVGTesselation.Combined) { 4282 static bool collinear (in float v0x, in float v0y, in float v1x, in float v1y, in float v2x, in float v2y) nothrow @trusted @nogc { 4283 immutable float cz = (v1x-v0x)*(v2y-v0y)-(v2x-v0x)*(v1y-v0y); 4284 return (nvg__absf(cz*cz) <= 0.01f); // arbitrary number, seems to work ok with NanoSVG output 4285 } 4286 if (collinear(x1, y1, x2, y2, x3, y3) && collinear(x2, y2, x3, y3, x3, y4)) { 4287 //{ import core.stdc.stdio; printf("AFD fallback!\n"); } 4288 ctx.nvg__tesselateBezierAFD(x1, y1, x2, y2, x3, y3, x4, y4, type); 4289 return; 4290 } 4291 } 4292 */ 4293 4294 immutable float x12 = (x1+x2)*0.5f; 4295 immutable float y12 = (y1+y2)*0.5f; 4296 immutable float x23 = (x2+x3)*0.5f; 4297 immutable float y23 = (y2+y3)*0.5f; 4298 immutable float x34 = (x3+x4)*0.5f; 4299 immutable float y34 = (y3+y4)*0.5f; 4300 immutable float x123 = (x12+x23)*0.5f; 4301 immutable float y123 = (y12+y23)*0.5f; 4302 4303 immutable float dx = x4-x1; 4304 immutable float dy = y4-y1; 4305 immutable float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx)); 4306 immutable float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx)); 4307 4308 if ((d2+d3)*(d2+d3) < ctx.tessTol*(dx*dx+dy*dy)) { 4309 nvg__addPoint(ctx, x4, y4, type); 4310 return; 4311 } 4312 4313 immutable float x234 = (x23+x34)*0.5f; 4314 immutable float y234 = (y23+y34)*0.5f; 4315 immutable float x1234 = (x123+x234)*0.5f; 4316 immutable float y1234 = (y123+y234)*0.5f; 4317 4318 // "taxicab" / "manhattan" check for flat curves 4319 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) { 4320 nvg__addPoint(ctx, x1234, y1234, type); 4321 return; 4322 } 4323 4324 nvg__tesselateBezier(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0); 4325 nvg__tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type); 4326 } 4327 4328 // based on the ideas and code of Maxim Shemanarev. Rest in Peace, bro! 4329 // see http://www.antigrain.com/research/adaptive_bezier/index.html 4330 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 { 4331 enum CollinearEPS = 0.00000001f; // 0.00001f; 4332 enum AngleTolEPS = 0.01f; 4333 4334 static float distSquared (in float x1, in float y1, in float x2, in float y2) pure nothrow @safe @nogc { 4335 pragma(inline, true); 4336 immutable float dx = x2-x1; 4337 immutable float dy = y2-y1; 4338 return dx*dx+dy*dy; 4339 } 4340 4341 if (level == 0) { 4342 nvg__addPoint(ctx, x1, y1, 0); 4343 nvg__tesselateBezierMcSeem(ctx, x1, y1, x2, y2, x3, y3, x4, y4, 1, type); 4344 nvg__addPoint(ctx, x4, y4, type); 4345 return; 4346 } 4347 4348 if (level >= 32) return; // recurse limit; practically, it should be never reached, but... 4349 4350 // calculate all the mid-points of the line segments 4351 immutable float x12 = (x1+x2)*0.5f; 4352 immutable float y12 = (y1+y2)*0.5f; 4353 immutable float x23 = (x2+x3)*0.5f; 4354 immutable float y23 = (y2+y3)*0.5f; 4355 immutable float x34 = (x3+x4)*0.5f; 4356 immutable float y34 = (y3+y4)*0.5f; 4357 immutable float x123 = (x12+x23)*0.5f; 4358 immutable float y123 = (y12+y23)*0.5f; 4359 immutable float x234 = (x23+x34)*0.5f; 4360 immutable float y234 = (y23+y34)*0.5f; 4361 immutable float x1234 = (x123+x234)*0.5f; 4362 immutable float y1234 = (y123+y234)*0.5f; 4363 4364 // try to approximate the full cubic curve by a single straight line 4365 immutable float dx = x4-x1; 4366 immutable float dy = y4-y1; 4367 4368 float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx)); 4369 float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx)); 4370 //immutable float da1, da2, k; 4371 4372 final switch ((cast(int)(d2 > CollinearEPS)<<1)+cast(int)(d3 > CollinearEPS)) { 4373 case 0: 4374 // all collinear or p1 == p4 4375 float k = dx*dx+dy*dy; 4376 if (k == 0) { 4377 d2 = distSquared(x1, y1, x2, y2); 4378 d3 = distSquared(x4, y4, x3, y3); 4379 } else { 4380 k = 1.0f/k; 4381 float da1 = x2-x1; 4382 float da2 = y2-y1; 4383 d2 = k*(da1*dx+da2*dy); 4384 da1 = x3-x1; 4385 da2 = y3-y1; 4386 d3 = k*(da1*dx+da2*dy); 4387 if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) { 4388 // Simple collinear case, 1---2---3---4 4389 // We can leave just two endpoints 4390 return; 4391 } 4392 if (d2 <= 0) d2 = distSquared(x2, y2, x1, y1); 4393 else if (d2 >= 1) d2 = distSquared(x2, y2, x4, y4); 4394 else d2 = distSquared(x2, y2, x1+d2*dx, y1+d2*dy); 4395 4396 if (d3 <= 0) d3 = distSquared(x3, y3, x1, y1); 4397 else if (d3 >= 1) d3 = distSquared(x3, y3, x4, y4); 4398 else d3 = distSquared(x3, y3, x1+d3*dx, y1+d3*dy); 4399 } 4400 if (d2 > d3) { 4401 if (d2 < ctx.tessTol) { 4402 nvg__addPoint(ctx, x2, y2, type); 4403 return; 4404 } 4405 } if (d3 < ctx.tessTol) { 4406 nvg__addPoint(ctx, x3, y3, type); 4407 return; 4408 } 4409 break; 4410 case 1: 4411 // p1,p2,p4 are collinear, p3 is significant 4412 if (d3*d3 <= ctx.tessTol*(dx*dx+dy*dy)) { 4413 if (ctx.angleTol < AngleTolEPS) { 4414 nvg__addPoint(ctx, x23, y23, type); 4415 return; 4416 } else { 4417 // angle condition 4418 float da1 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-nvg__atan2f(y3-y2, x3-x2)); 4419 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4420 if (da1 < ctx.angleTol) { 4421 nvg__addPoint(ctx, x2, y2, type); 4422 nvg__addPoint(ctx, x3, y3, type); 4423 return; 4424 } 4425 if (ctx.cuspLimit != 0.0) { 4426 if (da1 > ctx.cuspLimit) { 4427 nvg__addPoint(ctx, x3, y3, type); 4428 return; 4429 } 4430 } 4431 } 4432 } 4433 break; 4434 case 2: 4435 // p1,p3,p4 are collinear, p2 is significant 4436 if (d2*d2 <= ctx.tessTol*(dx*dx+dy*dy)) { 4437 if (ctx.angleTol < AngleTolEPS) { 4438 nvg__addPoint(ctx, x23, y23, type); 4439 return; 4440 } else { 4441 // angle condition 4442 float da1 = nvg__absf(nvg__atan2f(y3-y2, x3-x2)-nvg__atan2f(y2-y1, x2-x1)); 4443 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4444 if (da1 < ctx.angleTol) { 4445 nvg__addPoint(ctx, x2, y2, type); 4446 nvg__addPoint(ctx, x3, y3, type); 4447 return; 4448 } 4449 if (ctx.cuspLimit != 0.0) { 4450 if (da1 > ctx.cuspLimit) { 4451 nvg__addPoint(ctx, x2, y2, type); 4452 return; 4453 } 4454 } 4455 } 4456 } 4457 break; 4458 case 3: 4459 // regular case 4460 if ((d2+d3)*(d2+d3) <= ctx.tessTol*(dx*dx+dy*dy)) { 4461 // if the curvature doesn't exceed the distance tolerance value, we tend to finish subdivisions 4462 if (ctx.angleTol < AngleTolEPS) { 4463 nvg__addPoint(ctx, x23, y23, type); 4464 return; 4465 } else { 4466 // angle and cusp condition 4467 immutable float k = nvg__atan2f(y3-y2, x3-x2); 4468 float da1 = nvg__absf(k-nvg__atan2f(y2-y1, x2-x1)); 4469 float da2 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-k); 4470 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4471 if (da2 >= NVG_PI) da2 = 2*NVG_PI-da2; 4472 if (da1+da2 < ctx.angleTol) { 4473 // finally we can stop the recursion 4474 nvg__addPoint(ctx, x23, y23, type); 4475 return; 4476 } 4477 if (ctx.cuspLimit != 0.0) { 4478 if (da1 > ctx.cuspLimit) { 4479 nvg__addPoint(ctx, x2, y2, type); 4480 return; 4481 } 4482 if (da2 > ctx.cuspLimit) { 4483 nvg__addPoint(ctx, x3, y3, type); 4484 return; 4485 } 4486 } 4487 } 4488 } 4489 break; 4490 } 4491 4492 // continue subdivision 4493 nvg__tesselateBezierMcSeem(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0); 4494 nvg__tesselateBezierMcSeem(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type); 4495 } 4496 4497 4498 // Adaptive forward differencing for bezier tesselation. 4499 // See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt. 4500 // "Adaptive forward differencing for rendering curves and surfaces." 4501 // ACM SIGGRAPH Computer Graphics. Vol. 21. No. 4. ACM, 1987. 4502 // original code by Taylor Holliday <taylor@audulus.com> 4503 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 { 4504 enum AFD_ONE = (1<<10); 4505 4506 // power basis 4507 immutable float ax = -x1+3*x2-3*x3+x4; 4508 immutable float ay = -y1+3*y2-3*y3+y4; 4509 immutable float bx = 3*x1-6*x2+3*x3; 4510 immutable float by = 3*y1-6*y2+3*y3; 4511 immutable float cx = -3*x1+3*x2; 4512 immutable float cy = -3*y1+3*y2; 4513 4514 // Transform to forward difference basis (stepsize 1) 4515 float px = x1; 4516 float py = y1; 4517 float dx = ax+bx+cx; 4518 float dy = ay+by+cy; 4519 float ddx = 6*ax+2*bx; 4520 float ddy = 6*ay+2*by; 4521 float dddx = 6*ax; 4522 float dddy = 6*ay; 4523 4524 //printf("dx: %f, dy: %f\n", dx, dy); 4525 //printf("ddx: %f, ddy: %f\n", ddx, ddy); 4526 //printf("dddx: %f, dddy: %f\n", dddx, dddy); 4527 4528 int t = 0; 4529 int dt = AFD_ONE; 4530 4531 immutable float tol = ctx.tessTol*4; 4532 4533 while (t < AFD_ONE) { 4534 // Flatness measure. 4535 float d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4536 4537 // printf("d: %f, th: %f\n", d, th); 4538 4539 // Go to higher resolution if we're moving a lot or overshooting the end. 4540 while ((d > tol && dt > 1) || (t+dt > AFD_ONE)) { 4541 // printf("up\n"); 4542 4543 // Apply L to the curve. Increase curve resolution. 4544 dx = 0.5f*dx-(1.0f/8.0f)*ddx+(1.0f/16.0f)*dddx; 4545 dy = 0.5f*dy-(1.0f/8.0f)*ddy+(1.0f/16.0f)*dddy; 4546 ddx = (1.0f/4.0f)*ddx-(1.0f/8.0f)*dddx; 4547 ddy = (1.0f/4.0f)*ddy-(1.0f/8.0f)*dddy; 4548 dddx = (1.0f/8.0f)*dddx; 4549 dddy = (1.0f/8.0f)*dddy; 4550 4551 // Half the stepsize. 4552 dt >>= 1; 4553 4554 // Recompute d 4555 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4556 } 4557 4558 // Go to lower resolution if we're really flat 4559 // and we aren't going to overshoot the end. 4560 // XXX: tol/32 is just a guess for when we are too flat. 4561 while ((d > 0 && d < tol/32.0f && dt < AFD_ONE) && (t+2*dt <= AFD_ONE)) { 4562 // printf("down\n"); 4563 4564 // Apply L^(-1) to the curve. Decrease curve resolution. 4565 dx = 2*dx+ddx; 4566 dy = 2*dy+ddy; 4567 ddx = 4*ddx+4*dddx; 4568 ddy = 4*ddy+4*dddy; 4569 dddx = 8*dddx; 4570 dddy = 8*dddy; 4571 4572 // Double the stepsize. 4573 dt <<= 1; 4574 4575 // Recompute d 4576 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4577 } 4578 4579 // Forward differencing. 4580 px += dx; 4581 py += dy; 4582 dx += ddx; 4583 dy += ddy; 4584 ddx += dddx; 4585 ddy += dddy; 4586 4587 // Output a point. 4588 nvg__addPoint(ctx, px, py, (t > 0 ? type : 0)); 4589 4590 // Advance along the curve. 4591 t += dt; 4592 4593 // Ensure we don't overshoot. 4594 assert(t <= AFD_ONE); 4595 } 4596 } 4597 4598 4599 void nvg__dashLastPath (NVGContext ctx) nothrow @trusted @nogc { 4600 import core.stdc.stdlib : realloc; 4601 import core.stdc.string : memcpy; 4602 4603 NVGpathCache* cache = ctx.cache; 4604 if (cache.npaths == 0) return; 4605 4606 NVGpath* path = nvg__lastPath(ctx); 4607 if (path is null) return; 4608 4609 NVGstate* state = nvg__getState(ctx); 4610 if (!state.dasherActive) return; 4611 4612 static NVGpoint* pts = null; 4613 static uint ptsCount = 0; 4614 static uint ptsSize = 0; 4615 4616 if (path.count < 2) return; // just in case 4617 4618 // copy path points (reserve one point for closed pathes) 4619 if (ptsSize < path.count+1) { 4620 ptsSize = cast(uint)(path.count+1); 4621 pts = cast(NVGpoint*)realloc(pts, ptsSize*NVGpoint.sizeof); 4622 if (pts is null) assert(0, "NanoVega: out of memory"); 4623 } 4624 ptsCount = cast(uint)path.count; 4625 memcpy(pts, &cache.points[path.first], ptsCount*NVGpoint.sizeof); 4626 // add closing point for closed pathes 4627 if (path.closed && !nvg__ptEquals(pts[0].x, pts[0].y, pts[ptsCount-1].x, pts[ptsCount-1].y, ctx.distTol)) { 4628 pts[ptsCount++] = pts[0]; 4629 } 4630 4631 // remove last path (with its points) 4632 --cache.npaths; 4633 cache.npoints -= path.count; 4634 4635 // add stroked pathes 4636 const(float)* dashes = state.dashes.ptr; 4637 immutable uint dashCount = state.dashCount; 4638 float currDashStart = 0; 4639 uint currDashIdx = 0; 4640 immutable bool firstIsGap = state.firstDashIsGap; 4641 4642 // calculate lengthes 4643 { 4644 NVGpoint* v1 = &pts[0]; 4645 NVGpoint* v2 = &pts[1]; 4646 foreach (immutable _; 0..ptsCount) { 4647 float dx = v2.x-v1.x; 4648 float dy = v2.y-v1.y; 4649 v1.len = nvg__normalize(&dx, &dy); 4650 v1 = v2++; 4651 } 4652 } 4653 4654 void calcDashStart (float ds) { 4655 if (ds < 0) { 4656 ds = ds%state.totalDashLen; 4657 while (ds < 0) ds += state.totalDashLen; 4658 } 4659 currDashIdx = 0; 4660 currDashStart = 0; 4661 while (ds > 0) { 4662 if (ds > dashes[currDashIdx]) { 4663 ds -= dashes[currDashIdx]; 4664 ++currDashIdx; 4665 currDashStart = 0; 4666 if (currDashIdx >= dashCount) currDashIdx = 0; 4667 } else { 4668 currDashStart = ds; 4669 ds = 0; 4670 } 4671 } 4672 } 4673 4674 calcDashStart(state.dashStart); 4675 4676 uint srcPointIdx = 1; 4677 const(NVGpoint)* v1 = &pts[0]; 4678 const(NVGpoint)* v2 = &pts[1]; 4679 float currRest = v1.len; 4680 nvg__addPath(ctx); 4681 nvg__addPoint(ctx, v1.x, v1.y, PointFlag.Corner); 4682 4683 void fixLastPoint () { 4684 auto lpt = nvg__lastPath(ctx); 4685 if (lpt !is null && lpt.count > 0) { 4686 // fix last point 4687 if (auto lps = nvg__lastPoint(ctx)) lps.flags = PointFlag.Corner; 4688 // fix first point 4689 NVGpathCache* cache = ctx.cache; 4690 cache.points[lpt.first].flags = PointFlag.Corner; 4691 } 4692 } 4693 4694 for (;;) { 4695 immutable float dlen = dashes[currDashIdx]; 4696 if (dlen == 0) { 4697 ++currDashIdx; 4698 if (currDashIdx >= dashCount) currDashIdx = 0; 4699 continue; 4700 } 4701 immutable float dashRest = dlen-currDashStart; 4702 if ((currDashIdx&1) != firstIsGap) { 4703 // this is "moveto" command, so create new path 4704 fixLastPoint(); 4705 nvg__addPath(ctx); 4706 } 4707 if (currRest > dashRest) { 4708 currRest -= dashRest; 4709 ++currDashIdx; 4710 if (currDashIdx >= dashCount) currDashIdx = 0; 4711 currDashStart = 0; 4712 nvg__addPoint(ctx, 4713 v2.x-(v2.x-v1.x)*currRest/v1.len, 4714 v2.y-(v2.y-v1.y)*currRest/v1.len, 4715 PointFlag.Corner 4716 ); 4717 } else { 4718 currDashStart += currRest; 4719 nvg__addPoint(ctx, v2.x, v2.y, v1.flags); //k8:fix flags here? 4720 ++srcPointIdx; 4721 v1 = v2; 4722 currRest = v1.len; 4723 if (srcPointIdx >= ptsCount) break; 4724 v2 = &pts[srcPointIdx]; 4725 } 4726 } 4727 fixLastPoint(); 4728 } 4729 4730 4731 version(nanovg_bench_flatten) import iv.timer : Timer; 4732 4733 void nvg__flattenPaths(bool asStroke) (NVGContext ctx) nothrow @trusted @nogc { 4734 version(nanovg_bench_flatten) { 4735 Timer timer; 4736 char[128] tmbuf; 4737 int bzcount; 4738 } 4739 NVGpathCache* cache = ctx.cache; 4740 NVGstate* state = nvg__getState(ctx); 4741 4742 // check if we already did flattening 4743 static if (asStroke) { 4744 if (state.dashCount >= 2) { 4745 if (cache.npaths > 0 && state.lastFlattenDashCount == state.dashCount) return; // already flattened 4746 state.dasherActive = true; 4747 state.lastFlattenDashCount = state.dashCount; 4748 } else { 4749 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened 4750 state.dasherActive = false; 4751 state.lastFlattenDashCount = 0; 4752 } 4753 } else { 4754 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened 4755 state.lastFlattenDashCount = 0; // so next stroke flattening will redo it 4756 state.dasherActive = false; 4757 } 4758 4759 // clear path cache 4760 cache.npaths = 0; 4761 cache.npoints = 0; 4762 4763 // flatten 4764 version(nanovg_bench_flatten) timer.restart(); 4765 int i = 0; 4766 while (i < ctx.ncommands) { 4767 final switch (cast(Command)ctx.commands[i]) { 4768 case Command.MoveTo: 4769 //assert(i+3 <= ctx.ncommands); 4770 static if (asStroke) { 4771 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx); 4772 } 4773 nvg__addPath(ctx); 4774 const p = &ctx.commands[i+1]; 4775 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner); 4776 i += 3; 4777 break; 4778 case Command.LineTo: 4779 //assert(i+3 <= ctx.ncommands); 4780 const p = &ctx.commands[i+1]; 4781 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner); 4782 i += 3; 4783 break; 4784 case Command.BezierTo: 4785 //assert(i+7 <= ctx.ncommands); 4786 const last = nvg__lastPoint(ctx); 4787 if (last !is null) { 4788 const cp1 = &ctx.commands[i+1]; 4789 const cp2 = &ctx.commands[i+3]; 4790 const p = &ctx.commands[i+5]; 4791 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) { 4792 nvg__tesselateBezier(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner); 4793 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) { 4794 nvg__tesselateBezierMcSeem(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner); 4795 } else { 4796 nvg__tesselateBezierAFD(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], PointFlag.Corner); 4797 } 4798 version(nanovg_bench_flatten) ++bzcount; 4799 } 4800 i += 7; 4801 break; 4802 case Command.Close: 4803 //assert(i+1 <= ctx.ncommands); 4804 nvg__closePath(ctx); 4805 i += 1; 4806 break; 4807 case Command.Winding: 4808 //assert(i+2 <= ctx.ncommands); 4809 nvg__pathWinding(ctx, cast(NVGWinding)ctx.commands[i+1]); 4810 i += 2; 4811 break; 4812 } 4813 } 4814 static if (asStroke) { 4815 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx); 4816 } 4817 version(nanovg_bench_flatten) {{ 4818 timer.stop(); 4819 auto xb = timer.toBuffer(tmbuf[]); 4820 import core.stdc.stdio : printf; 4821 printf("flattening time: [%.*s] (%d beziers)\n", cast(uint)xb.length, xb.ptr, bzcount); 4822 }} 4823 4824 cache.bounds.ptr[0] = cache.bounds.ptr[1] = float.max; 4825 cache.bounds.ptr[2] = cache.bounds.ptr[3] = -float.max; 4826 4827 // calculate the direction and length of line segments 4828 version(nanovg_bench_flatten) timer.restart(); 4829 foreach (int j; 0..cache.npaths) { 4830 NVGpath* path = &cache.paths[j]; 4831 NVGpoint* pts = &cache.points[path.first]; 4832 4833 // if the first and last points are the same, remove the last, mark as closed path 4834 NVGpoint* p0 = &pts[path.count-1]; 4835 NVGpoint* p1 = &pts[0]; 4836 if (nvg__ptEquals(p0.x, p0.y, p1.x, p1.y, ctx.distTol)) { 4837 --path.count; 4838 p0 = &pts[path.count-1]; 4839 path.closed = true; 4840 } 4841 4842 // enforce winding 4843 if (path.count > 2) { 4844 immutable float area = nvg__polyArea(pts, path.count); 4845 if (path.mWinding == NVGWinding.CCW && area < 0.0f) nvg__polyReverse(pts, path.count); 4846 if (path.mWinding == NVGWinding.CW && area > 0.0f) nvg__polyReverse(pts, path.count); 4847 } 4848 4849 foreach (immutable _; 0..path.count) { 4850 // calculate segment direction and length 4851 p0.dx = p1.x-p0.x; 4852 p0.dy = p1.y-p0.y; 4853 p0.len = nvg__normalize(&p0.dx, &p0.dy); 4854 // update bounds 4855 cache.bounds.ptr[0] = nvg__min(cache.bounds.ptr[0], p0.x); 4856 cache.bounds.ptr[1] = nvg__min(cache.bounds.ptr[1], p0.y); 4857 cache.bounds.ptr[2] = nvg__max(cache.bounds.ptr[2], p0.x); 4858 cache.bounds.ptr[3] = nvg__max(cache.bounds.ptr[3], p0.y); 4859 // advance 4860 p0 = p1++; 4861 } 4862 } 4863 version(nanovg_bench_flatten) {{ 4864 timer.stop(); 4865 auto xb = timer.toBuffer(tmbuf[]); 4866 import core.stdc.stdio : printf; 4867 printf("segment calculation time: [%.*s]\n", cast(uint)xb.length, xb.ptr); 4868 }} 4869 } 4870 4871 int nvg__curveDivs (float r, float arc, float tol) nothrow @trusted @nogc { 4872 immutable float da = nvg__acosf(r/(r+tol))*2.0f; 4873 return nvg__max(2, cast(int)nvg__ceilf(arc/da)); 4874 } 4875 4876 void nvg__chooseBevel (int bevel, NVGpoint* p0, NVGpoint* p1, float w, float* x0, float* y0, float* x1, float* y1) nothrow @trusted @nogc { 4877 if (bevel) { 4878 *x0 = p1.x+p0.dy*w; 4879 *y0 = p1.y-p0.dx*w; 4880 *x1 = p1.x+p1.dy*w; 4881 *y1 = p1.y-p1.dx*w; 4882 } else { 4883 *x0 = p1.x+p1.dmx*w; 4884 *y0 = p1.y+p1.dmy*w; 4885 *x1 = p1.x+p1.dmx*w; 4886 *y1 = p1.y+p1.dmy*w; 4887 } 4888 } 4889 4890 NVGVertex* nvg__roundJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, int ncap, float fringe) nothrow @trusted @nogc { 4891 float dlx0 = p0.dy; 4892 float dly0 = -p0.dx; 4893 float dlx1 = p1.dy; 4894 float dly1 = -p1.dx; 4895 //NVG_NOTUSED(fringe); 4896 4897 if (p1.flags&PointFlag.Left) { 4898 float lx0 = void, ly0 = void, lx1 = void, ly1 = void; 4899 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1); 4900 immutable float a0 = nvg__atan2f(-dly0, -dlx0); 4901 float a1 = nvg__atan2f(-dly1, -dlx1); 4902 if (a1 > a0) a1 -= NVG_PI*2; 4903 4904 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4905 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4906 4907 int n = nvg__clamp(cast(int)nvg__ceilf(((a0-a1)/NVG_PI)*ncap), 2, ncap); 4908 for (int i = 0; i < n; ++i) { 4909 float u = i/cast(float)(n-1); 4910 float a = a0+u*(a1-a0); 4911 float rx = p1.x+nvg__cosf(a)*rw; 4912 float ry = p1.y+nvg__sinf(a)*rw; 4913 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4914 nvg__vset(dst, rx, ry, ru, 1); ++dst; 4915 } 4916 4917 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4918 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4919 4920 } else { 4921 float rx0 = void, ry0 = void, rx1 = void, ry1 = void; 4922 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1); 4923 immutable float a0 = nvg__atan2f(dly0, dlx0); 4924 float a1 = nvg__atan2f(dly1, dlx1); 4925 if (a1 < a0) a1 += NVG_PI*2; 4926 4927 nvg__vset(dst, p1.x+dlx0*rw, p1.y+dly0*rw, lu, 1); ++dst; 4928 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4929 4930 int n = nvg__clamp(cast(int)nvg__ceilf(((a1-a0)/NVG_PI)*ncap), 2, ncap); 4931 for (int i = 0; i < n; i++) { 4932 float u = i/cast(float)(n-1); 4933 float a = a0+u*(a1-a0); 4934 float lx = p1.x+nvg__cosf(a)*lw; 4935 float ly = p1.y+nvg__sinf(a)*lw; 4936 nvg__vset(dst, lx, ly, lu, 1); ++dst; 4937 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4938 } 4939 4940 nvg__vset(dst, p1.x+dlx1*rw, p1.y+dly1*rw, lu, 1); ++dst; 4941 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 4942 4943 } 4944 return dst; 4945 } 4946 4947 NVGVertex* nvg__bevelJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, float fringe) nothrow @trusted @nogc { 4948 float rx0, ry0, rx1, ry1; 4949 float lx0, ly0, lx1, ly1; 4950 float dlx0 = p0.dy; 4951 float dly0 = -p0.dx; 4952 float dlx1 = p1.dy; 4953 float dly1 = -p1.dx; 4954 //NVG_NOTUSED(fringe); 4955 4956 if (p1.flags&PointFlag.Left) { 4957 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1); 4958 4959 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4960 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4961 4962 if (p1.flags&PointFlag.Bevel) { 4963 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4964 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4965 4966 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4967 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4968 } else { 4969 rx0 = p1.x-p1.dmx*rw; 4970 ry0 = p1.y-p1.dmy*rw; 4971 4972 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4973 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4974 4975 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4976 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4977 4978 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4979 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4980 } 4981 4982 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4983 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4984 4985 } else { 4986 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1); 4987 4988 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 4989 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4990 4991 if (p1.flags&PointFlag.Bevel) { 4992 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 4993 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4994 4995 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 4996 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 4997 } else { 4998 lx0 = p1.x+p1.dmx*lw; 4999 ly0 = p1.y+p1.dmy*lw; 5000 5001 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 5002 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 5003 5004 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 5005 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 5006 5007 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 5008 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 5009 } 5010 5011 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 5012 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 5013 } 5014 5015 return dst; 5016 } 5017 5018 NVGVertex* nvg__buttCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc { 5019 immutable float px = p.x-dx*d; 5020 immutable float py = p.y-dy*d; 5021 immutable float dlx = dy; 5022 immutable float dly = -dx; 5023 nvg__vset(dst, px+dlx*w-dx*aa, py+dly*w-dy*aa, 0, 0); ++dst; 5024 nvg__vset(dst, px-dlx*w-dx*aa, py-dly*w-dy*aa, 1, 0); ++dst; 5025 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5026 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5027 return dst; 5028 } 5029 5030 NVGVertex* nvg__buttCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc { 5031 immutable float px = p.x+dx*d; 5032 immutable float py = p.y+dy*d; 5033 immutable float dlx = dy; 5034 immutable float dly = -dx; 5035 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5036 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5037 nvg__vset(dst, px+dlx*w+dx*aa, py+dly*w+dy*aa, 0, 0); ++dst; 5038 nvg__vset(dst, px-dlx*w+dx*aa, py-dly*w+dy*aa, 1, 0); ++dst; 5039 return dst; 5040 } 5041 5042 NVGVertex* nvg__roundCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc { 5043 immutable float px = p.x; 5044 immutable float py = p.y; 5045 immutable float dlx = dy; 5046 immutable float dly = -dx; 5047 //NVG_NOTUSED(aa); 5048 immutable float ncpf = cast(float)(ncap-1); 5049 foreach (int i; 0..ncap) { 5050 float a = i/*/cast(float)(ncap-1)*//ncpf*NVG_PI; 5051 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w; 5052 nvg__vset(dst, px-dlx*ax-dx*ay, py-dly*ax-dy*ay, 0, 1); ++dst; 5053 nvg__vset(dst, px, py, 0.5f, 1); ++dst; 5054 } 5055 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5056 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5057 return dst; 5058 } 5059 5060 NVGVertex* nvg__roundCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc { 5061 immutable float px = p.x; 5062 immutable float py = p.y; 5063 immutable float dlx = dy; 5064 immutable float dly = -dx; 5065 //NVG_NOTUSED(aa); 5066 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5067 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5068 immutable float ncpf = cast(float)(ncap-1); 5069 foreach (int i; 0..ncap) { 5070 float a = i/*cast(float)(ncap-1)*//ncpf*NVG_PI; 5071 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w; 5072 nvg__vset(dst, px, py, 0.5f, 1); ++dst; 5073 nvg__vset(dst, px-dlx*ax+dx*ay, py-dly*ax+dy*ay, 0, 1); ++dst; 5074 } 5075 return dst; 5076 } 5077 5078 void nvg__calculateJoins (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5079 NVGpathCache* cache = ctx.cache; 5080 float iw = 0.0f; 5081 5082 if (w > 0.0f) iw = 1.0f/w; 5083 5084 // Calculate which joins needs extra vertices to append, and gather vertex count. 5085 foreach (int i; 0..cache.npaths) { 5086 NVGpath* path = &cache.paths[i]; 5087 NVGpoint* pts = &cache.points[path.first]; 5088 NVGpoint* p0 = &pts[path.count-1]; 5089 NVGpoint* p1 = &pts[0]; 5090 int nleft = 0; 5091 5092 path.nbevel = 0; 5093 5094 foreach (int j; 0..path.count) { 5095 //float dlx0, dly0, dlx1, dly1, dmr2, cross, limit; 5096 immutable float dlx0 = p0.dy; 5097 immutable float dly0 = -p0.dx; 5098 immutable float dlx1 = p1.dy; 5099 immutable float dly1 = -p1.dx; 5100 // Calculate extrusions 5101 p1.dmx = (dlx0+dlx1)*0.5f; 5102 p1.dmy = (dly0+dly1)*0.5f; 5103 immutable float dmr2 = p1.dmx*p1.dmx+p1.dmy*p1.dmy; 5104 if (dmr2 > 0.000001f) { 5105 float scale = 1.0f/dmr2; 5106 if (scale > 600.0f) scale = 600.0f; 5107 p1.dmx *= scale; 5108 p1.dmy *= scale; 5109 } 5110 5111 // Clear flags, but keep the corner. 5112 p1.flags = (p1.flags&PointFlag.Corner) ? PointFlag.Corner : 0; 5113 5114 // Keep track of left turns. 5115 immutable float cross = p1.dx*p0.dy-p0.dx*p1.dy; 5116 if (cross > 0.0f) { 5117 nleft++; 5118 p1.flags |= PointFlag.Left; 5119 } 5120 5121 // Calculate if we should use bevel or miter for inner join. 5122 immutable float limit = nvg__max(1.01f, nvg__min(p0.len, p1.len)*iw); 5123 if ((dmr2*limit*limit) < 1.0f) p1.flags |= PointFlag.InnerBevelPR; 5124 5125 // Check to see if the corner needs to be beveled. 5126 if (p1.flags&PointFlag.Corner) { 5127 if ((dmr2*miterLimit*miterLimit) < 1.0f || lineJoin == NVGLineCap.Bevel || lineJoin == NVGLineCap.Round) { 5128 p1.flags |= PointFlag.Bevel; 5129 } 5130 } 5131 5132 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) path.nbevel++; 5133 5134 p0 = p1++; 5135 } 5136 5137 path.convex = (nleft == path.count); 5138 } 5139 } 5140 5141 void nvg__expandStroke (NVGContext ctx, float w, int lineCap, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5142 NVGpathCache* cache = ctx.cache; 5143 immutable float aa = ctx.fringeWidth; 5144 int ncap = nvg__curveDivs(w, NVG_PI, ctx.tessTol); // Calculate divisions per half circle. 5145 5146 nvg__calculateJoins(ctx, w, lineJoin, miterLimit); 5147 5148 // Calculate max vertex usage. 5149 int cverts = 0; 5150 foreach (int i; 0..cache.npaths) { 5151 NVGpath* path = &cache.paths[i]; 5152 immutable bool loop = path.closed; 5153 if (lineJoin == NVGLineCap.Round) { 5154 cverts += (path.count+path.nbevel*(ncap+2)+1)*2; // plus one for loop 5155 } else { 5156 cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop 5157 } 5158 if (!loop) { 5159 // space for caps 5160 if (lineCap == NVGLineCap.Round) { 5161 cverts += (ncap*2+2)*2; 5162 } else { 5163 cverts += (3+3)*2; 5164 } 5165 } 5166 } 5167 5168 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts); 5169 if (verts is null) return; 5170 5171 foreach (int i; 0..cache.npaths) { 5172 NVGpath* path = &cache.paths[i]; 5173 NVGpoint* pts = &cache.points[path.first]; 5174 NVGpoint* p0; 5175 NVGpoint* p1; 5176 int s, e; 5177 5178 path.fill = null; 5179 path.nfill = 0; 5180 5181 // Calculate fringe or stroke 5182 immutable bool loop = path.closed; 5183 NVGVertex* dst = verts; 5184 path.stroke = dst; 5185 5186 if (loop) { 5187 // Looping 5188 p0 = &pts[path.count-1]; 5189 p1 = &pts[0]; 5190 s = 0; 5191 e = path.count; 5192 } else { 5193 // Add cap 5194 p0 = &pts[0]; 5195 p1 = &pts[1]; 5196 s = 1; 5197 e = path.count-1; 5198 } 5199 5200 if (!loop) { 5201 // Add cap 5202 float dx = p1.x-p0.x; 5203 float dy = p1.y-p0.y; 5204 nvg__normalize(&dx, &dy); 5205 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa); 5206 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa); 5207 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa); 5208 } 5209 5210 foreach (int j; s..e) { 5211 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) { 5212 if (lineJoin == NVGLineCap.Round) { 5213 dst = nvg__roundJoin(dst, p0, p1, w, w, 0, 1, ncap, aa); 5214 } else { 5215 dst = nvg__bevelJoin(dst, p0, p1, w, w, 0, 1, aa); 5216 } 5217 } else { 5218 nvg__vset(dst, p1.x+(p1.dmx*w), p1.y+(p1.dmy*w), 0, 1); ++dst; 5219 nvg__vset(dst, p1.x-(p1.dmx*w), p1.y-(p1.dmy*w), 1, 1); ++dst; 5220 } 5221 p0 = p1++; 5222 } 5223 5224 if (loop) { 5225 // Loop it 5226 nvg__vset(dst, verts[0].x, verts[0].y, 0, 1); ++dst; 5227 nvg__vset(dst, verts[1].x, verts[1].y, 1, 1); ++dst; 5228 } else { 5229 // Add cap 5230 float dx = p1.x-p0.x; 5231 float dy = p1.y-p0.y; 5232 nvg__normalize(&dx, &dy); 5233 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa); 5234 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa); 5235 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa); 5236 } 5237 5238 path.nstroke = cast(int)(dst-verts); 5239 5240 verts = dst; 5241 } 5242 } 5243 5244 void nvg__expandFill (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5245 NVGpathCache* cache = ctx.cache; 5246 immutable float aa = ctx.fringeWidth; 5247 bool fringe = (w > 0.0f); 5248 5249 nvg__calculateJoins(ctx, w, lineJoin, miterLimit); 5250 5251 // Calculate max vertex usage. 5252 int cverts = 0; 5253 foreach (int i; 0..cache.npaths) { 5254 NVGpath* path = &cache.paths[i]; 5255 cverts += path.count+path.nbevel+1; 5256 if (fringe) cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop 5257 } 5258 5259 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts); 5260 if (verts is null) return; 5261 5262 bool convex = (cache.npaths == 1 && cache.paths[0].convex); 5263 5264 foreach (int i; 0..cache.npaths) { 5265 NVGpath* path = &cache.paths[i]; 5266 NVGpoint* pts = &cache.points[path.first]; 5267 5268 // Calculate shape vertices. 5269 immutable float woff = 0.5f*aa; 5270 NVGVertex* dst = verts; 5271 path.fill = dst; 5272 5273 if (fringe) { 5274 // Looping 5275 NVGpoint* p0 = &pts[path.count-1]; 5276 NVGpoint* p1 = &pts[0]; 5277 foreach (int j; 0..path.count) { 5278 if (p1.flags&PointFlag.Bevel) { 5279 immutable float dlx0 = p0.dy; 5280 immutable float dly0 = -p0.dx; 5281 immutable float dlx1 = p1.dy; 5282 immutable float dly1 = -p1.dx; 5283 if (p1.flags&PointFlag.Left) { 5284 immutable float lx = p1.x+p1.dmx*woff; 5285 immutable float ly = p1.y+p1.dmy*woff; 5286 nvg__vset(dst, lx, ly, 0.5f, 1); ++dst; 5287 } else { 5288 immutable float lx0 = p1.x+dlx0*woff; 5289 immutable float ly0 = p1.y+dly0*woff; 5290 immutable float lx1 = p1.x+dlx1*woff; 5291 immutable float ly1 = p1.y+dly1*woff; 5292 nvg__vset(dst, lx0, ly0, 0.5f, 1); ++dst; 5293 nvg__vset(dst, lx1, ly1, 0.5f, 1); ++dst; 5294 } 5295 } else { 5296 nvg__vset(dst, p1.x+(p1.dmx*woff), p1.y+(p1.dmy*woff), 0.5f, 1); ++dst; 5297 } 5298 p0 = p1++; 5299 } 5300 } else { 5301 foreach (int j; 0..path.count) { 5302 nvg__vset(dst, pts[j].x, pts[j].y, 0.5f, 1); 5303 ++dst; 5304 } 5305 } 5306 5307 path.nfill = cast(int)(dst-verts); 5308 verts = dst; 5309 5310 // Calculate fringe 5311 if (fringe) { 5312 float lw = w+woff; 5313 immutable float rw = w-woff; 5314 float lu = 0; 5315 immutable float ru = 1; 5316 dst = verts; 5317 path.stroke = dst; 5318 5319 // Create only half a fringe for convex shapes so that 5320 // the shape can be rendered without stenciling. 5321 if (convex) { 5322 lw = woff; // This should generate the same vertex as fill inset above. 5323 lu = 0.5f; // Set outline fade at middle. 5324 } 5325 5326 // Looping 5327 NVGpoint* p0 = &pts[path.count-1]; 5328 NVGpoint* p1 = &pts[0]; 5329 5330 foreach (int j; 0..path.count) { 5331 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) { 5332 dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx.fringeWidth); 5333 } else { 5334 nvg__vset(dst, p1.x+(p1.dmx*lw), p1.y+(p1.dmy*lw), lu, 1); ++dst; 5335 nvg__vset(dst, p1.x-(p1.dmx*rw), p1.y-(p1.dmy*rw), ru, 1); ++dst; 5336 } 5337 p0 = p1++; 5338 } 5339 5340 // Loop it 5341 nvg__vset(dst, verts[0].x, verts[0].y, lu, 1); ++dst; 5342 nvg__vset(dst, verts[1].x, verts[1].y, ru, 1); ++dst; 5343 5344 path.nstroke = cast(int)(dst-verts); 5345 verts = dst; 5346 } else { 5347 path.stroke = null; 5348 path.nstroke = 0; 5349 } 5350 } 5351 } 5352 5353 5354 // ////////////////////////////////////////////////////////////////////////// // 5355 // Paths 5356 5357 /// Clears the current path and sub-paths. 5358 /// Group: paths 5359 @scriptable 5360 public void beginPath (NVGContext ctx) nothrow @trusted @nogc { 5361 ctx.ncommands = 0; 5362 ctx.pathPickRegistered &= NVGPickKind.All; // reset "registered" flags 5363 nvg__clearPathCache(ctx); 5364 } 5365 5366 public alias newPath = beginPath; /// Ditto. 5367 5368 /// Starts new sub-path with specified point as first point. 5369 /// Group: paths 5370 @scriptable 5371 public void moveTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 5372 nvg__appendCommands(ctx, Command.MoveTo, x, y); 5373 } 5374 5375 /// Starts new sub-path with specified point as first point. 5376 /// Arguments: [x, y]* 5377 /// Group: paths 5378 public void moveTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5379 enum ArgC = 2; 5380 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [moveTo] call"); 5381 if (args.length < ArgC) return; 5382 nvg__appendCommands(ctx, Command.MoveTo, args[$-2..$]); 5383 } 5384 5385 /// Adds line segment from the last point in the path to the specified point. 5386 /// Group: paths 5387 @scriptable 5388 public void lineTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 5389 nvg__appendCommands(ctx, Command.LineTo, x, y); 5390 } 5391 5392 /// Adds line segment from the last point in the path to the specified point. 5393 /// Arguments: [x, y]* 5394 /// Group: paths 5395 public void lineTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5396 enum ArgC = 2; 5397 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [lineTo] call"); 5398 if (args.length < ArgC) return; 5399 foreach (immutable idx; 0..args.length/ArgC) { 5400 nvg__appendCommands(ctx, Command.LineTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]); 5401 } 5402 } 5403 5404 /// Adds cubic bezier segment from last point in the path via two control points to the specified point. 5405 /// Group: paths 5406 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 { 5407 nvg__appendCommands(ctx, Command.BezierTo, c1x, c1y, c2x, c2y, x, y); 5408 } 5409 5410 /// Adds cubic bezier segment from last point in the path via two control points to the specified point. 5411 /// Arguments: [c1x, c1y, c2x, c2y, x, y]* 5412 /// Group: paths 5413 public void bezierTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5414 enum ArgC = 6; 5415 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [bezierTo] call"); 5416 if (args.length < ArgC) return; 5417 foreach (immutable idx; 0..args.length/ArgC) { 5418 nvg__appendCommands(ctx, Command.BezierTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]); 5419 } 5420 } 5421 5422 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point. 5423 /// Group: paths 5424 public void quadTo (NVGContext ctx, in float cx, in float cy, in float x, in float y) nothrow @trusted @nogc { 5425 immutable float x0 = ctx.commandx; 5426 immutable float y0 = ctx.commandy; 5427 nvg__appendCommands(ctx, 5428 Command.BezierTo, 5429 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 5430 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 5431 x, y, 5432 ); 5433 } 5434 5435 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point. 5436 /// Arguments: [cx, cy, x, y]* 5437 /// Group: paths 5438 public void quadTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5439 enum ArgC = 4; 5440 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [quadTo] call"); 5441 if (args.length < ArgC) return; 5442 const(float)* aptr = args.ptr; 5443 foreach (immutable idx; 0..args.length/ArgC) { 5444 immutable float x0 = ctx.commandx; 5445 immutable float y0 = ctx.commandy; 5446 immutable float cx = *aptr++; 5447 immutable float cy = *aptr++; 5448 immutable float x = *aptr++; 5449 immutable float y = *aptr++; 5450 nvg__appendCommands(ctx, 5451 Command.BezierTo, 5452 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 5453 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 5454 x, y, 5455 ); 5456 } 5457 } 5458 5459 /// Adds an arc segment at the corner defined by the last path point, and two specified points. 5460 /// Group: paths 5461 public void arcTo (NVGContext ctx, in float x1, in float y1, in float x2, in float y2, in float radius) nothrow @trusted @nogc { 5462 if (ctx.ncommands == 0) return; 5463 5464 immutable float x0 = ctx.commandx; 5465 immutable float y0 = ctx.commandy; 5466 5467 // handle degenerate cases 5468 if (nvg__ptEquals(x0, y0, x1, y1, ctx.distTol) || 5469 nvg__ptEquals(x1, y1, x2, y2, ctx.distTol) || 5470 nvg__distPtSeg(x1, y1, x0, y0, x2, y2) < ctx.distTol*ctx.distTol || 5471 radius < ctx.distTol) 5472 { 5473 ctx.lineTo(x1, y1); 5474 return; 5475 } 5476 5477 // calculate tangential circle to lines (x0, y0)-(x1, y1) and (x1, y1)-(x2, y2) 5478 float dx0 = x0-x1; 5479 float dy0 = y0-y1; 5480 float dx1 = x2-x1; 5481 float dy1 = y2-y1; 5482 nvg__normalize(&dx0, &dy0); 5483 nvg__normalize(&dx1, &dy1); 5484 immutable float a = nvg__acosf(dx0*dx1+dy0*dy1); 5485 immutable float d = radius/nvg__tanf(a/2.0f); 5486 5487 //printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d); 5488 5489 if (d > 10000.0f) { 5490 ctx.lineTo(x1, y1); 5491 return; 5492 } 5493 5494 float cx = void, cy = void, a0 = void, a1 = void; 5495 NVGWinding dir; 5496 if (nvg__cross(dx0, dy0, dx1, dy1) > 0.0f) { 5497 cx = x1+dx0*d+dy0*radius; 5498 cy = y1+dy0*d+-dx0*radius; 5499 a0 = nvg__atan2f(dx0, -dy0); 5500 a1 = nvg__atan2f(-dx1, dy1); 5501 dir = NVGWinding.CW; 5502 //printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); 5503 } else { 5504 cx = x1+dx0*d+-dy0*radius; 5505 cy = y1+dy0*d+dx0*radius; 5506 a0 = nvg__atan2f(-dx0, dy0); 5507 a1 = nvg__atan2f(dx1, -dy1); 5508 dir = NVGWinding.CCW; 5509 //printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); 5510 } 5511 5512 ctx.arc(dir, cx, cy, radius, a0, a1); // first is line 5513 } 5514 5515 5516 /// Adds an arc segment at the corner defined by the last path point, and two specified points. 5517 /// Arguments: [x1, y1, x2, y2, radius]* 5518 /// Group: paths 5519 public void arcTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5520 enum ArgC = 5; 5521 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arcTo] call"); 5522 if (args.length < ArgC) return; 5523 if (ctx.ncommands == 0) return; 5524 const(float)* aptr = args.ptr; 5525 foreach (immutable idx; 0..args.length/ArgC) { 5526 immutable float x0 = ctx.commandx; 5527 immutable float y0 = ctx.commandy; 5528 immutable float x1 = *aptr++; 5529 immutable float y1 = *aptr++; 5530 immutable float x2 = *aptr++; 5531 immutable float y2 = *aptr++; 5532 immutable float radius = *aptr++; 5533 ctx.arcTo(x1, y1, x2, y2, radius); 5534 } 5535 } 5536 5537 /// Closes current sub-path with a line segment. 5538 /// Group: paths 5539 @scriptable 5540 public void closePath (NVGContext ctx) nothrow @trusted @nogc { 5541 nvg__appendCommands(ctx, Command.Close); 5542 } 5543 5544 /// Sets the current sub-path winding, see NVGWinding and NVGSolidity. 5545 /// Group: paths 5546 public void pathWinding (NVGContext ctx, NVGWinding dir) nothrow @trusted @nogc { 5547 nvg__appendCommands(ctx, Command.Winding, cast(float)dir); 5548 } 5549 5550 /// Ditto. 5551 public void pathWinding (NVGContext ctx, NVGSolidity dir) nothrow @trusted @nogc { 5552 nvg__appendCommands(ctx, Command.Winding, cast(float)dir); 5553 } 5554 5555 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r, 5556 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW). 5557 * Angles are specified in radians. 5558 * 5559 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo 5560 * 5561 * Group: paths 5562 */ 5563 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 { 5564 static assert(mode == "original" || mode == "move" || mode == "line"); 5565 5566 float[3+5*7+100] vals = void; 5567 //int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo); 5568 static if (mode == "original") { 5569 immutable int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo); 5570 } else static if (mode == "move") { 5571 enum move = Command.MoveTo; 5572 } else static if (mode == "line") { 5573 enum move = Command.LineTo; 5574 } else { 5575 static assert(0, "wtf?!"); 5576 } 5577 5578 // Clamp angles 5579 float da = a1-a0; 5580 if (dir == NVGWinding.CW) { 5581 if (nvg__absf(da) >= NVG_PI*2) { 5582 da = NVG_PI*2; 5583 } else { 5584 while (da < 0.0f) da += NVG_PI*2; 5585 } 5586 } else { 5587 if (nvg__absf(da) >= NVG_PI*2) { 5588 da = -NVG_PI*2; 5589 } else { 5590 while (da > 0.0f) da -= NVG_PI*2; 5591 } 5592 } 5593 5594 // Split arc into max 90 degree segments. 5595 immutable int ndivs = nvg__max(1, nvg__min(cast(int)(nvg__absf(da)/(NVG_PI*0.5f)+0.5f), 5)); 5596 immutable float hda = (da/cast(float)ndivs)/2.0f; 5597 float kappa = nvg__absf(4.0f/3.0f*(1.0f-nvg__cosf(hda))/nvg__sinf(hda)); 5598 5599 if (dir == NVGWinding.CCW) kappa = -kappa; 5600 5601 int nvals = 0; 5602 float px = 0, py = 0, ptanx = 0, ptany = 0; 5603 foreach (int i; 0..ndivs+1) { 5604 immutable float a = a0+da*(i/cast(float)ndivs); 5605 immutable float dx = nvg__cosf(a); 5606 immutable float dy = nvg__sinf(a); 5607 immutable float x = cx+dx*r; 5608 immutable float y = cy+dy*r; 5609 immutable float tanx = -dy*r*kappa; 5610 immutable float tany = dx*r*kappa; 5611 5612 if (i == 0) { 5613 if (vals.length-nvals < 3) { 5614 // flush 5615 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5616 nvals = 0; 5617 } 5618 vals.ptr[nvals++] = cast(float)move; 5619 vals.ptr[nvals++] = x; 5620 vals.ptr[nvals++] = y; 5621 } else { 5622 if (vals.length-nvals < 7) { 5623 // flush 5624 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5625 nvals = 0; 5626 } 5627 vals.ptr[nvals++] = Command.BezierTo; 5628 vals.ptr[nvals++] = px+ptanx; 5629 vals.ptr[nvals++] = py+ptany; 5630 vals.ptr[nvals++] = x-tanx; 5631 vals.ptr[nvals++] = y-tany; 5632 vals.ptr[nvals++] = x; 5633 vals.ptr[nvals++] = y; 5634 } 5635 px = x; 5636 py = y; 5637 ptanx = tanx; 5638 ptany = tany; 5639 } 5640 5641 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5642 } 5643 5644 5645 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r, 5646 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW). 5647 * Angles are specified in radians. 5648 * 5649 * Arguments: [cx, cy, r, a0, a1]* 5650 * 5651 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo 5652 * 5653 * Group: paths 5654 */ 5655 public void arc(string mode="original") (NVGContext ctx, NVGWinding dir, in float[] args) nothrow @trusted @nogc { 5656 static assert(mode == "original" || mode == "move" || mode == "line"); 5657 enum ArgC = 5; 5658 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arc] call"); 5659 if (args.length < ArgC) return; 5660 const(float)* aptr = args.ptr; 5661 foreach (immutable idx; 0..args.length/ArgC) { 5662 immutable cx = *aptr++; 5663 immutable cy = *aptr++; 5664 immutable r = *aptr++; 5665 immutable a0 = *aptr++; 5666 immutable a1 = *aptr++; 5667 ctx.arc!mode(dir, cx, cy, r, a0, a1); 5668 } 5669 } 5670 5671 /// Creates new rectangle shaped sub-path. 5672 /// Group: paths 5673 @scriptable 5674 public void rect (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc { 5675 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5676 Command.MoveTo, x, y, 5677 Command.LineTo, x, y+h, 5678 Command.LineTo, x+w, y+h, 5679 Command.LineTo, x+w, y, 5680 Command.Close, 5681 ); 5682 } 5683 5684 /// Creates new rectangle shaped sub-path. 5685 /// Arguments: [x, y, w, h]* 5686 /// Group: paths 5687 public void rect (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5688 enum ArgC = 4; 5689 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [rect] call"); 5690 if (args.length < ArgC) return; 5691 const(float)* aptr = args.ptr; 5692 foreach (immutable idx; 0..args.length/ArgC) { 5693 immutable x = *aptr++; 5694 immutable y = *aptr++; 5695 immutable w = *aptr++; 5696 immutable h = *aptr++; 5697 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5698 Command.MoveTo, x, y, 5699 Command.LineTo, x, y+h, 5700 Command.LineTo, x+w, y+h, 5701 Command.LineTo, x+w, y, 5702 Command.Close, 5703 ); 5704 } 5705 } 5706 5707 /// Creates new rounded rectangle shaped sub-path. 5708 /// Group: paths 5709 @scriptable 5710 public void roundedRect (NVGContext ctx, in float x, in float y, in float w, in float h, in float radius) nothrow @trusted @nogc { 5711 ctx.roundedRectVarying(x, y, w, h, radius, radius, radius, radius); 5712 } 5713 5714 /// Creates new rounded rectangle shaped sub-path. 5715 /// Arguments: [x, y, w, h, radius]* 5716 /// Group: paths 5717 public void roundedRect (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5718 enum ArgC = 5; 5719 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRect] call"); 5720 if (args.length < ArgC) return; 5721 const(float)* aptr = args.ptr; 5722 foreach (immutable idx; 0..args.length/ArgC) { 5723 immutable x = *aptr++; 5724 immutable y = *aptr++; 5725 immutable w = *aptr++; 5726 immutable h = *aptr++; 5727 immutable r = *aptr++; 5728 ctx.roundedRectVarying(x, y, w, h, r, r, r, r); 5729 } 5730 } 5731 5732 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it. 5733 /// Group: paths 5734 @scriptable 5735 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 { 5736 if (rw < 0.1f || rh < 0.1f) { 5737 rect(ctx, x, y, w, h); 5738 } else { 5739 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5740 Command.MoveTo, x+rw, y, 5741 Command.LineTo, x+w-rw, y, 5742 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh, 5743 Command.LineTo, x+w, y+h-rh, 5744 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h, 5745 Command.LineTo, x+rw, y+h, 5746 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh, 5747 Command.LineTo, x, y+rh, 5748 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y, 5749 Command.Close, 5750 ); 5751 } 5752 } 5753 5754 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it. 5755 /// Arguments: [x, y, w, h, rw, rh]* 5756 /// Group: paths 5757 public void roundedRectEllipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5758 enum ArgC = 6; 5759 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectEllipse] call"); 5760 if (args.length < ArgC) return; 5761 const(float)* aptr = args.ptr; 5762 foreach (immutable idx; 0..args.length/ArgC) { 5763 immutable x = *aptr++; 5764 immutable y = *aptr++; 5765 immutable w = *aptr++; 5766 immutable h = *aptr++; 5767 immutable rw = *aptr++; 5768 immutable rh = *aptr++; 5769 if (rw < 0.1f || rh < 0.1f) { 5770 rect(ctx, x, y, w, h); 5771 } else { 5772 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5773 Command.MoveTo, x+rw, y, 5774 Command.LineTo, x+w-rw, y, 5775 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh, 5776 Command.LineTo, x+w, y+h-rh, 5777 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h, 5778 Command.LineTo, x+rw, y+h, 5779 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh, 5780 Command.LineTo, x, y+rh, 5781 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y, 5782 Command.Close, 5783 ); 5784 } 5785 } 5786 } 5787 5788 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner. 5789 /// Group: paths 5790 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 { 5791 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { 5792 ctx.rect(x, y, w, h); 5793 } else { 5794 immutable float halfw = nvg__absf(w)*0.5f; 5795 immutable float halfh = nvg__absf(h)*0.5f; 5796 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h); 5797 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h); 5798 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h); 5799 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h); 5800 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5801 Command.MoveTo, x, y+ryTL, 5802 Command.LineTo, x, y+h-ryBL, 5803 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h, 5804 Command.LineTo, x+w-rxBR, y+h, 5805 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR, 5806 Command.LineTo, x+w, y+ryTR, 5807 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y, 5808 Command.LineTo, x+rxTL, y, 5809 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL, 5810 Command.Close, 5811 ); 5812 } 5813 } 5814 5815 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner. 5816 /// Arguments: [x, y, w, h, radTopLeft, radTopRight, radBottomRight, radBottomLeft]* 5817 /// Group: paths 5818 public void roundedRectVarying (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5819 enum ArgC = 8; 5820 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectVarying] call"); 5821 if (args.length < ArgC) return; 5822 const(float)* aptr = args.ptr; 5823 foreach (immutable idx; 0..args.length/ArgC) { 5824 immutable x = *aptr++; 5825 immutable y = *aptr++; 5826 immutable w = *aptr++; 5827 immutable h = *aptr++; 5828 immutable radTopLeft = *aptr++; 5829 immutable radTopRight = *aptr++; 5830 immutable radBottomRight = *aptr++; 5831 immutable radBottomLeft = *aptr++; 5832 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { 5833 ctx.rect(x, y, w, h); 5834 } else { 5835 immutable float halfw = nvg__absf(w)*0.5f; 5836 immutable float halfh = nvg__absf(h)*0.5f; 5837 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h); 5838 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h); 5839 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h); 5840 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h); 5841 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5842 Command.MoveTo, x, y+ryTL, 5843 Command.LineTo, x, y+h-ryBL, 5844 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h, 5845 Command.LineTo, x+w-rxBR, y+h, 5846 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR, 5847 Command.LineTo, x+w, y+ryTR, 5848 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y, 5849 Command.LineTo, x+rxTL, y, 5850 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL, 5851 Command.Close, 5852 ); 5853 } 5854 } 5855 } 5856 5857 /// Creates new ellipse shaped sub-path. 5858 /// Group: paths 5859 public void ellipse (NVGContext ctx, in float cx, in float cy, in float rx, in float ry) nothrow @trusted @nogc { 5860 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5861 Command.MoveTo, cx-rx, cy, 5862 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry, 5863 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy, 5864 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry, 5865 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy, 5866 Command.Close, 5867 ); 5868 } 5869 5870 /// Creates new ellipse shaped sub-path. 5871 /// Arguments: [cx, cy, rx, ry]* 5872 /// Group: paths 5873 public void ellipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5874 enum ArgC = 4; 5875 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [ellipse] call"); 5876 if (args.length < ArgC) return; 5877 const(float)* aptr = args.ptr; 5878 foreach (immutable idx; 0..args.length/ArgC) { 5879 immutable cx = *aptr++; 5880 immutable cy = *aptr++; 5881 immutable rx = *aptr++; 5882 immutable ry = *aptr++; 5883 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5884 Command.MoveTo, cx-rx, cy, 5885 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry, 5886 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy, 5887 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry, 5888 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy, 5889 Command.Close, 5890 ); 5891 } 5892 } 5893 5894 /// Creates new circle shaped sub-path. 5895 /// Group: paths 5896 public void circle (NVGContext ctx, in float cx, in float cy, in float r) nothrow @trusted @nogc { 5897 ctx.ellipse(cx, cy, r, r); 5898 } 5899 5900 /// Creates new circle shaped sub-path. 5901 /// Arguments: [cx, cy, r]* 5902 /// Group: paths 5903 public void circle (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5904 enum ArgC = 3; 5905 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [circle] call"); 5906 if (args.length < ArgC) return; 5907 const(float)* aptr = args.ptr; 5908 foreach (immutable idx; 0..args.length/ArgC) { 5909 immutable cx = *aptr++; 5910 immutable cy = *aptr++; 5911 immutable r = *aptr++; 5912 ctx.ellipse(cx, cy, r, r); 5913 } 5914 } 5915 5916 // Debug function to dump cached path data. 5917 debug public void debugDumpPathCache (NVGContext ctx) nothrow @trusted @nogc { 5918 import core.stdc.stdio : printf; 5919 const(NVGpath)* path; 5920 printf("Dumping %d cached paths\n", ctx.cache.npaths); 5921 for (int i = 0; i < ctx.cache.npaths; ++i) { 5922 path = &ctx.cache.paths[i]; 5923 printf("-Path %d\n", i); 5924 if (path.nfill) { 5925 printf("-fill: %d\n", path.nfill); 5926 for (int j = 0; j < path.nfill; ++j) printf("%f\t%f\n", path.fill[j].x, path.fill[j].y); 5927 } 5928 if (path.nstroke) { 5929 printf("-stroke: %d\n", path.nstroke); 5930 for (int j = 0; j < path.nstroke; ++j) printf("%f\t%f\n", path.stroke[j].x, path.stroke[j].y); 5931 } 5932 } 5933 } 5934 5935 // Flatten path, prepare it for fill operation. 5936 void nvg__prepareFill (NVGContext ctx) nothrow @trusted @nogc { 5937 NVGpathCache* cache = ctx.cache; 5938 NVGstate* state = nvg__getState(ctx); 5939 5940 nvg__flattenPaths!false(ctx); 5941 5942 if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) { 5943 nvg__expandFill(ctx, ctx.fringeWidth, NVGLineCap.Miter, 2.4f); 5944 } else { 5945 nvg__expandFill(ctx, 0.0f, NVGLineCap.Miter, 2.4f); 5946 } 5947 5948 cache.evenOddMode = state.evenOddMode; 5949 cache.fringeWidth = ctx.fringeWidth; 5950 cache.fillReady = true; 5951 cache.strokeReady = false; 5952 cache.clipmode = NVGClipMode.None; 5953 } 5954 5955 // Flatten path, prepare it for stroke operation. 5956 void nvg__prepareStroke (NVGContext ctx) nothrow @trusted @nogc { 5957 NVGstate* state = nvg__getState(ctx); 5958 NVGpathCache* cache = ctx.cache; 5959 5960 nvg__flattenPaths!true(ctx); 5961 5962 immutable float scale = nvg__getAverageScale(state.xform); 5963 float strokeWidth = nvg__clamp(state.strokeWidth*scale, 0.0f, 200.0f); 5964 5965 if (strokeWidth < ctx.fringeWidth) { 5966 // If the stroke width is less than pixel size, use alpha to emulate coverage. 5967 // Since coverage is area, scale by alpha*alpha. 5968 immutable float alpha = nvg__clamp(strokeWidth/ctx.fringeWidth, 0.0f, 1.0f); 5969 cache.strokeAlphaMul = alpha*alpha; 5970 strokeWidth = ctx.fringeWidth; 5971 } else { 5972 cache.strokeAlphaMul = 1.0f; 5973 } 5974 cache.strokeWidth = strokeWidth; 5975 5976 if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) { 5977 nvg__expandStroke(ctx, strokeWidth*0.5f+ctx.fringeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit); 5978 } else { 5979 nvg__expandStroke(ctx, strokeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit); 5980 } 5981 5982 cache.fringeWidth = ctx.fringeWidth; 5983 cache.fillReady = false; 5984 cache.strokeReady = true; 5985 cache.clipmode = NVGClipMode.None; 5986 } 5987 5988 /// Fills the current path with current fill style. 5989 /// Group: paths 5990 @scriptable 5991 public void fill (NVGContext ctx) nothrow @trusted @nogc { 5992 NVGstate* state = nvg__getState(ctx); 5993 5994 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) { 5995 ctx.pathPickRegistered |= NVGPickKind.Fill<<16; 5996 ctx.currFillHitId = ctx.pathPickId; 5997 } 5998 5999 nvg__prepareFill(ctx); 6000 6001 // apply global alpha 6002 NVGPaint fillPaint = state.fill; 6003 fillPaint.innerColor.a *= state.alpha; 6004 fillPaint.middleColor.a *= state.alpha; 6005 fillPaint.outerColor.a *= state.alpha; 6006 6007 ctx.appendCurrentPathToCache(ctx.recset, state.fill); 6008 6009 if (ctx.recblockdraw) return; 6010 6011 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); 6012 6013 // count triangles 6014 foreach (int i; 0..ctx.cache.npaths) { 6015 NVGpath* path = &ctx.cache.paths[i]; 6016 ctx.fillTriCount += path.nfill-2; 6017 ctx.fillTriCount += path.nstroke-2; 6018 ctx.drawCallCount += 2; 6019 } 6020 } 6021 6022 /// Fills the current path with current stroke style. 6023 /// Group: paths 6024 @scriptable 6025 public void stroke (NVGContext ctx) nothrow @trusted @nogc { 6026 NVGstate* state = nvg__getState(ctx); 6027 6028 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) { 6029 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16; 6030 ctx.currStrokeHitId = ctx.pathPickId; 6031 } 6032 6033 nvg__prepareStroke(ctx); 6034 6035 NVGpathCache* cache = ctx.cache; 6036 6037 NVGPaint strokePaint = state.stroke; 6038 strokePaint.innerColor.a *= cache.strokeAlphaMul; 6039 strokePaint.middleColor.a *= cache.strokeAlphaMul; 6040 strokePaint.outerColor.a *= cache.strokeAlphaMul; 6041 6042 // apply global alpha 6043 strokePaint.innerColor.a *= state.alpha; 6044 strokePaint.middleColor.a *= state.alpha; 6045 strokePaint.outerColor.a *= state.alpha; 6046 6047 ctx.appendCurrentPathToCache(ctx.recset, state.stroke); 6048 6049 if (ctx.recblockdraw) return; 6050 6051 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths); 6052 6053 // count triangles 6054 foreach (int i; 0..ctx.cache.npaths) { 6055 NVGpath* path = &ctx.cache.paths[i]; 6056 ctx.strokeTriCount += path.nstroke-2; 6057 ++ctx.drawCallCount; 6058 } 6059 } 6060 6061 /// Sets current path as clipping region. 6062 /// Group: clipping 6063 public void clip (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc { 6064 NVGstate* state = nvg__getState(ctx); 6065 6066 if (aclipmode == NVGClipMode.None) return; 6067 if (ctx.recblockdraw) return; //??? 6068 6069 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr); 6070 6071 /* 6072 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) { 6073 ctx.pathPickRegistered |= NVGPickKind.Fill<<16; 6074 ctx.currFillHitId = ctx.pathPickId; 6075 } 6076 */ 6077 6078 nvg__prepareFill(ctx); 6079 6080 // apply global alpha 6081 NVGPaint fillPaint = state.fill; 6082 fillPaint.innerColor.a *= state.alpha; 6083 fillPaint.middleColor.a *= state.alpha; 6084 fillPaint.outerColor.a *= state.alpha; 6085 6086 //ctx.appendCurrentPathToCache(ctx.recset, state.fill); 6087 6088 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); 6089 6090 // count triangles 6091 foreach (int i; 0..ctx.cache.npaths) { 6092 NVGpath* path = &ctx.cache.paths[i]; 6093 ctx.fillTriCount += path.nfill-2; 6094 ctx.fillTriCount += path.nstroke-2; 6095 ctx.drawCallCount += 2; 6096 } 6097 } 6098 6099 /// Sets current path as clipping region. 6100 /// Group: clipping 6101 public alias clipFill = clip; 6102 6103 /// Sets current path' stroke as clipping region. 6104 /// Group: clipping 6105 public void clipStroke (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc { 6106 NVGstate* state = nvg__getState(ctx); 6107 6108 if (aclipmode == NVGClipMode.None) return; 6109 if (ctx.recblockdraw) return; //??? 6110 6111 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr); 6112 6113 /* 6114 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) { 6115 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16; 6116 ctx.currStrokeHitId = ctx.pathPickId; 6117 } 6118 */ 6119 6120 nvg__prepareStroke(ctx); 6121 6122 NVGpathCache* cache = ctx.cache; 6123 6124 NVGPaint strokePaint = state.stroke; 6125 strokePaint.innerColor.a *= cache.strokeAlphaMul; 6126 strokePaint.middleColor.a *= cache.strokeAlphaMul; 6127 strokePaint.outerColor.a *= cache.strokeAlphaMul; 6128 6129 // apply global alpha 6130 strokePaint.innerColor.a *= state.alpha; 6131 strokePaint.middleColor.a *= state.alpha; 6132 strokePaint.outerColor.a *= state.alpha; 6133 6134 //ctx.appendCurrentPathToCache(ctx.recset, state.stroke); 6135 6136 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, aclipmode, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths); 6137 6138 // count triangles 6139 foreach (int i; 0..ctx.cache.npaths) { 6140 NVGpath* path = &ctx.cache.paths[i]; 6141 ctx.strokeTriCount += path.nstroke-2; 6142 ++ctx.drawCallCount; 6143 } 6144 } 6145 6146 6147 // ////////////////////////////////////////////////////////////////////////// // 6148 // Picking API 6149 6150 // most of the code is by Michael Wynne <mike@mikesspace.net> 6151 // https://github.com/memononen/nanovg/pull/230 6152 // https://github.com/MikeWW/nanovg 6153 6154 /// Pick type query. Used in [hitTest] and [hitTestAll]. 6155 /// Group: picking_api 6156 public enum NVGPickKind : ubyte { 6157 Fill = 0x01, /// 6158 Stroke = 0x02, /// 6159 All = 0x03, /// 6160 } 6161 6162 /// Marks the fill of the current path as pickable with the specified id. 6163 /// Note that you can create and mark path without rasterizing it. 6164 /// Group: picking_api 6165 public void currFillHitId (NVGContext ctx, int id) nothrow @trusted @nogc { 6166 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6167 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/false); 6168 nvg__pickSceneInsert(ps, pp); 6169 } 6170 6171 public alias currFillPickId = currFillHitId; /// Ditto. 6172 6173 /// Marks the stroke of the current path as pickable with the specified id. 6174 /// Note that you can create and mark path without rasterizing it. 6175 /// Group: picking_api 6176 public void currStrokeHitId (NVGContext ctx, int id) nothrow @trusted @nogc { 6177 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6178 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/true); 6179 nvg__pickSceneInsert(ps, pp); 6180 } 6181 6182 public alias currStrokePickId = currStrokeHitId; /// Ditto. 6183 6184 // Marks the saved path set (fill) as pickable with the specified id. 6185 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!) 6186 // Group: picking_api 6187 /+ 6188 public void pathSetFillHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc { 6189 if (svp is null) return; 6190 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context"); 6191 foreach (ref cp; svp.caches[0..svp.ncaches]) { 6192 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6193 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/false); 6194 nvg__pickSceneInsert(ps, pp); 6195 } 6196 } 6197 +/ 6198 6199 // Marks the saved path set (stroke) as pickable with the specified id. 6200 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!) 6201 // Group: picking_api 6202 /+ 6203 public void pathSetStrokeHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc { 6204 if (svp is null) return; 6205 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context"); 6206 foreach (ref cp; svp.caches[0..svp.ncaches]) { 6207 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6208 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/true); 6209 nvg__pickSceneInsert(ps, pp); 6210 } 6211 } 6212 +/ 6213 6214 private template IsGoodHitTestDG(DG) { 6215 enum IsGoodHitTestDG = 6216 __traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); }) || 6217 __traits(compiles, (){ DG dg; dg(cast(int)42, cast(int)666); }); 6218 } 6219 6220 private template IsGoodHitTestInternalDG(DG) { 6221 enum IsGoodHitTestInternalDG = 6222 __traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); }) || 6223 __traits(compiles, (){ DG dg; NVGpickPath* pp; dg(pp); }); 6224 } 6225 6226 /// Call delegate [dg] for each path under the specified position (in no particular order). 6227 /// Returns the id of the path for which delegate [dg] returned true or [NVGNoPick]. 6228 /// dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending). 6229 /// Group: picking_api 6230 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) { 6231 if (ctx.pickScene is null || ctx.pickScene.npaths == 0 || (kind&NVGPickKind.All) == 0) return -1; 6232 6233 NVGpickScene* ps = ctx.pickScene; 6234 int levelwidth = 1<<(ps.nlevels-1); 6235 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth); 6236 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth); 6237 int npicked = 0; 6238 6239 // if we are interested only in most-toplevel path, there is no reason to check paths with worser order. 6240 // but we cannot just get out on the first path found, 'cause we are using quad tree to speed up bounds 6241 // checking, so path walking order is not guaranteed. 6242 static if (bestOrder) { 6243 int lastBestOrder = int.min; 6244 } 6245 6246 //{ import core.stdc.stdio; printf("npaths=%d\n", ps.npaths); } 6247 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) { 6248 for (NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; pp !is null; pp = pp.next) { 6249 //{ 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); } 6250 static if (bestOrder) { 6251 // reject earlier paths 6252 if (pp.order <= lastBestOrder) continue; // not interesting 6253 } 6254 immutable uint kpx = kind&pp.flags&3; 6255 if (kpx == 0) continue; // not interesting 6256 if (!nvg__pickPathTestBounds(ctx, ps, pp, x, y)) continue; // not interesting 6257 //{ import core.stdc.stdio; printf("in bounds!\n"); } 6258 int hit = 0; 6259 if (kpx&NVGPickKind.Stroke) hit = nvg__pickPathStroke(ps, pp, x, y); 6260 if (!hit && (kpx&NVGPickKind.Fill)) hit = nvg__pickPath(ps, pp, x, y); 6261 if (!hit) continue; 6262 //{ import core.stdc.stdio; printf(" HIT!\n"); } 6263 static if (bestOrder) lastBestOrder = pp.order; 6264 static if (IsGoodHitTestDG!DG) { 6265 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) { 6266 if (dg(pp.id, cast(int)pp.order)) return pp.id; 6267 } else { 6268 dg(pp.id, cast(int)pp.order); 6269 } 6270 } else { 6271 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) { 6272 if (dg(pp)) return pp.id; 6273 } else { 6274 dg(pp); 6275 } 6276 } 6277 } 6278 cellx >>= 1; 6279 celly >>= 1; 6280 levelwidth >>= 1; 6281 } 6282 6283 return -1; 6284 } 6285 6286 /// Fills ids with a list of the top most hit ids (from bottom to top) under the specified position. 6287 /// Returns the slice of [ids]. 6288 /// Group: picking_api 6289 public int[] hitTestAll (NVGContext ctx, in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc { 6290 if (ctx.pickScene is null || ids.length == 0) return ids[0..0]; 6291 6292 int npicked = 0; 6293 NVGpickScene* ps = ctx.pickScene; 6294 6295 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 6296 if (npicked == ps.cpicked) { 6297 int cpicked = ps.cpicked+ps.cpicked; 6298 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked); 6299 if (picked is null) return true; // abort 6300 ps.cpicked = cpicked; 6301 ps.picked = picked; 6302 } 6303 ps.picked[npicked] = pp; 6304 ++npicked; 6305 return false; // go on 6306 }); 6307 6308 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths); 6309 6310 assert(npicked >= 0); 6311 if (npicked > ids.length) npicked = cast(int)ids.length; 6312 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id; 6313 6314 return ids[0..npicked]; 6315 } 6316 6317 /// Returns the id of the pickable shape containing x,y or [NVGNoPick] if no shape was found. 6318 /// Group: picking_api 6319 public int hitTest (NVGContext ctx, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc { 6320 if (ctx.pickScene is null) return -1; 6321 6322 int bestOrder = int.min; 6323 int bestID = -1; 6324 6325 ctx.hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) { 6326 if (pp.order > bestOrder) { 6327 bestOrder = pp.order; 6328 bestID = pp.id; 6329 } 6330 }); 6331 6332 return bestID; 6333 } 6334 6335 /// Returns `true` if the path with the given id contains x,y. 6336 /// Group: picking_api 6337 public bool hitTestForId (NVGContext ctx, in int id, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc { 6338 if (ctx.pickScene is null || id == NVGNoPick) return false; 6339 6340 bool res = false; 6341 6342 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) { 6343 if (pp.id == id) { 6344 res = true; 6345 return true; // stop 6346 } 6347 return false; // continue 6348 }); 6349 6350 return res; 6351 } 6352 6353 /// Returns `true` if the given point is within the fill of the currently defined path. 6354 /// This operation can be done before rasterizing the current path. 6355 /// Group: picking_api 6356 public bool hitTestCurrFill (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 6357 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6358 int oldnpoints = ps.npoints; 6359 int oldnsegments = ps.nsegments; 6360 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/false); 6361 if (pp is null) return false; // oops 6362 scope(exit) { 6363 nvg__freePickPath(ps, pp); 6364 ps.npoints = oldnpoints; 6365 ps.nsegments = oldnsegments; 6366 } 6367 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPath(ps, pp, x, y) : false); 6368 } 6369 6370 public alias isPointInPath = hitTestCurrFill; /// Ditto. 6371 6372 /// Returns `true` if the given point is within the stroke of the currently defined path. 6373 /// This operation can be done before rasterizing the current path. 6374 /// Group: picking_api 6375 public bool hitTestCurrStroke (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 6376 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6377 int oldnpoints = ps.npoints; 6378 int oldnsegments = ps.nsegments; 6379 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/true); 6380 if (pp is null) return false; // oops 6381 scope(exit) { 6382 nvg__freePickPath(ps, pp); 6383 ps.npoints = oldnpoints; 6384 ps.nsegments = oldnsegments; 6385 } 6386 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPathStroke(ps, pp, x, y) : false); 6387 } 6388 6389 6390 nothrow @trusted @nogc { 6391 extern(C) { 6392 private alias _compare_fp_t = int function (const void*, const void*) nothrow @nogc; 6393 private extern(C) void qsort (scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc; 6394 6395 extern(C) int nvg__comparePaths (const void* a, const void* b) { 6396 return (*cast(const(NVGpickPath)**)b).order-(*cast(const(NVGpickPath)**)a).order; 6397 } 6398 } 6399 6400 enum NVGPickEPS = 0.0001f; 6401 6402 // Segment flags 6403 enum NVGSegmentFlags { 6404 Corner = 1, 6405 Bevel = 2, 6406 InnerBevel = 4, 6407 Cap = 8, 6408 Endcap = 16, 6409 } 6410 6411 // Path flags 6412 enum NVGPathFlags : ushort { 6413 Fill = NVGPickKind.Fill, 6414 Stroke = NVGPickKind.Stroke, 6415 Scissor = 0x80, 6416 } 6417 6418 struct NVGsegment { 6419 int firstPoint; // Index into NVGpickScene.points 6420 short type; // NVG_LINETO or NVG_BEZIERTO 6421 short flags; // Flags relate to the corner between the prev segment and this one. 6422 float[4] bounds; 6423 float[2] startDir; // Direction at t == 0 6424 float[2] endDir; // Direction at t == 1 6425 float[2] miterDir; // Direction of miter of corner between the prev segment and this one. 6426 } 6427 6428 struct NVGpickSubPath { 6429 short winding; // TODO: Merge to flag field 6430 bool closed; // TODO: Merge to flag field 6431 6432 int firstSegment; // Index into NVGpickScene.segments 6433 int nsegments; 6434 6435 float[4] bounds; 6436 6437 NVGpickSubPath* next; 6438 } 6439 6440 struct NVGpickPath { 6441 int id; 6442 short flags; 6443 short order; 6444 float strokeWidth; 6445 float miterLimit; 6446 short lineCap; 6447 short lineJoin; 6448 bool evenOddMode; 6449 6450 float[4] bounds; 6451 int scissor; // Indexes into ps->points and defines scissor rect as XVec, YVec and Center 6452 6453 NVGpickSubPath* subPaths; 6454 NVGpickPath* next; 6455 NVGpickPath* cellnext; 6456 } 6457 6458 struct NVGpickScene { 6459 int npaths; 6460 6461 NVGpickPath* paths; // Linked list of paths 6462 NVGpickPath* lastPath; // The last path in the paths linked list (the first path added) 6463 NVGpickPath* freePaths; // Linked list of free paths 6464 6465 NVGpickSubPath* freeSubPaths; // Linked list of free sub paths 6466 6467 int width; 6468 int height; 6469 6470 // Points for all path sub paths. 6471 float* points; 6472 int npoints; 6473 int cpoints; 6474 6475 // Segments for all path sub paths 6476 NVGsegment* segments; 6477 int nsegments; 6478 int csegments; 6479 6480 // Implicit quadtree 6481 float xdim; // Width / (1 << nlevels) 6482 float ydim; // Height / (1 << nlevels) 6483 int ncells; // Total number of cells in all levels 6484 int nlevels; 6485 NVGpickPath*** levels; // Index: [Level][LevelY * LevelW + LevelX] Value: Linked list of paths 6486 6487 // Temp storage for picking 6488 int cpicked; 6489 NVGpickPath** picked; 6490 } 6491 6492 6493 // bounds utilities 6494 void nvg__initBounds (ref float[4] bounds) { 6495 bounds.ptr[0] = bounds.ptr[1] = float.max; 6496 bounds.ptr[2] = bounds.ptr[3] = -float.max; 6497 } 6498 6499 void nvg__expandBounds (ref float[4] bounds, const(float)* points, int npoints) { 6500 npoints *= 2; 6501 for (int i = 0; i < npoints; i += 2) { 6502 bounds.ptr[0] = nvg__min(bounds.ptr[0], points[i]); 6503 bounds.ptr[1] = nvg__min(bounds.ptr[1], points[i+1]); 6504 bounds.ptr[2] = nvg__max(bounds.ptr[2], points[i]); 6505 bounds.ptr[3] = nvg__max(bounds.ptr[3], points[i+1]); 6506 } 6507 } 6508 6509 void nvg__unionBounds (ref float[4] bounds, in ref float[4] boundsB) { 6510 bounds.ptr[0] = nvg__min(bounds.ptr[0], boundsB.ptr[0]); 6511 bounds.ptr[1] = nvg__min(bounds.ptr[1], boundsB.ptr[1]); 6512 bounds.ptr[2] = nvg__max(bounds.ptr[2], boundsB.ptr[2]); 6513 bounds.ptr[3] = nvg__max(bounds.ptr[3], boundsB.ptr[3]); 6514 } 6515 6516 void nvg__intersectBounds (ref float[4] bounds, in ref float[4] boundsB) { 6517 bounds.ptr[0] = nvg__max(boundsB.ptr[0], bounds.ptr[0]); 6518 bounds.ptr[1] = nvg__max(boundsB.ptr[1], bounds.ptr[1]); 6519 bounds.ptr[2] = nvg__min(boundsB.ptr[2], bounds.ptr[2]); 6520 bounds.ptr[3] = nvg__min(boundsB.ptr[3], bounds.ptr[3]); 6521 6522 bounds.ptr[2] = nvg__max(bounds.ptr[0], bounds.ptr[2]); 6523 bounds.ptr[3] = nvg__max(bounds.ptr[1], bounds.ptr[3]); 6524 } 6525 6526 bool nvg__pointInBounds (in float x, in float y, in ref float[4] bounds) { 6527 pragma(inline, true); 6528 return (x >= bounds.ptr[0] && x <= bounds.ptr[2] && y >= bounds.ptr[1] && y <= bounds.ptr[3]); 6529 } 6530 6531 // building paths & sub paths 6532 int nvg__pickSceneAddPoints (NVGpickScene* ps, const(float)* xy, int n) { 6533 import core.stdc.string : memcpy; 6534 if (ps.npoints+n > ps.cpoints) { 6535 import core.stdc.stdlib : realloc; 6536 int cpoints = ps.npoints+n+(ps.cpoints<<1); 6537 float* points = cast(float*)realloc(ps.points, float.sizeof*2*cpoints); 6538 if (points is null) assert(0, "NanoVega: out of memory"); 6539 ps.points = points; 6540 ps.cpoints = cpoints; 6541 } 6542 int i = ps.npoints; 6543 if (xy !is null) memcpy(&ps.points[i*2], xy, float.sizeof*2*n); 6544 ps.npoints += n; 6545 return i; 6546 } 6547 6548 void nvg__pickSubPathAddSegment (NVGpickScene* ps, NVGpickSubPath* psp, int firstPoint, int type, short flags) { 6549 NVGsegment* seg = null; 6550 if (ps.nsegments == ps.csegments) { 6551 int csegments = 1+ps.csegments+(ps.csegments<<1); 6552 NVGsegment* segments = cast(NVGsegment*)realloc(ps.segments, NVGsegment.sizeof*csegments); 6553 if (segments is null) assert(0, "NanoVega: out of memory"); 6554 ps.segments = segments; 6555 ps.csegments = csegments; 6556 } 6557 6558 if (psp.firstSegment == -1) psp.firstSegment = ps.nsegments; 6559 6560 seg = &ps.segments[ps.nsegments]; 6561 ++ps.nsegments; 6562 seg.firstPoint = firstPoint; 6563 seg.type = cast(short)type; 6564 seg.flags = flags; 6565 ++psp.nsegments; 6566 6567 nvg__segmentDir(ps, psp, seg, 0, seg.startDir); 6568 nvg__segmentDir(ps, psp, seg, 1, seg.endDir); 6569 } 6570 6571 void nvg__segmentDir (NVGpickScene* ps, NVGpickSubPath* psp, NVGsegment* seg, float t, ref float[2] d) { 6572 const(float)* points = &ps.points[seg.firstPoint*2]; 6573 immutable float x0 = points[0*2+0], x1 = points[1*2+0]; 6574 immutable float y0 = points[0*2+1], y1 = points[1*2+1]; 6575 switch (seg.type) { 6576 case Command.LineTo: 6577 d.ptr[0] = x1-x0; 6578 d.ptr[1] = y1-y0; 6579 nvg__normalize(&d.ptr[0], &d.ptr[1]); 6580 break; 6581 case Command.BezierTo: 6582 immutable float x2 = points[2*2+0]; 6583 immutable float y2 = points[2*2+1]; 6584 immutable float x3 = points[3*2+0]; 6585 immutable float y3 = points[3*2+1]; 6586 6587 immutable float omt = 1.0f-t; 6588 immutable float omt2 = omt*omt; 6589 immutable float t2 = t*t; 6590 6591 d.ptr[0] = 6592 3.0f*omt2*(x1-x0)+ 6593 6.0f*omt*t*(x2-x1)+ 6594 3.0f*t2*(x3-x2); 6595 6596 d.ptr[1] = 6597 3.0f*omt2*(y1-y0)+ 6598 6.0f*omt*t*(y2-y1)+ 6599 3.0f*t2*(y3-y2); 6600 6601 nvg__normalize(&d.ptr[0], &d.ptr[1]); 6602 break; 6603 default: 6604 break; 6605 } 6606 } 6607 6608 void nvg__pickSubPathAddFillSupports (NVGpickScene* ps, NVGpickSubPath* psp) { 6609 if (psp.firstSegment == -1) return; 6610 NVGsegment* segments = &ps.segments[psp.firstSegment]; 6611 for (int s = 0; s < psp.nsegments; ++s) { 6612 NVGsegment* seg = &segments[s]; 6613 const(float)* points = &ps.points[seg.firstPoint*2]; 6614 if (seg.type == Command.LineTo) { 6615 nvg__initBounds(seg.bounds); 6616 nvg__expandBounds(seg.bounds, points, 2); 6617 } else { 6618 nvg__bezierBounds(points, seg.bounds); 6619 } 6620 } 6621 } 6622 6623 void nvg__pickSubPathAddStrokeSupports (NVGpickScene* ps, NVGpickSubPath* psp, float strokeWidth, int lineCap, int lineJoin, float miterLimit) { 6624 if (psp.firstSegment == -1) return; 6625 immutable bool closed = psp.closed; 6626 const(float)* points = ps.points; 6627 NVGsegment* seg = null; 6628 NVGsegment* segments = &ps.segments[psp.firstSegment]; 6629 int nsegments = psp.nsegments; 6630 NVGsegment* prevseg = (closed ? &segments[psp.nsegments-1] : null); 6631 6632 int ns = 0; // nsupports 6633 float[32] supportingPoints = void; 6634 int firstPoint, lastPoint; 6635 6636 if (!closed) { 6637 segments[0].flags |= NVGSegmentFlags.Cap; 6638 segments[nsegments-1].flags |= NVGSegmentFlags.Endcap; 6639 } 6640 6641 for (int s = 0; s < nsegments; ++s) { 6642 seg = &segments[s]; 6643 nvg__initBounds(seg.bounds); 6644 6645 firstPoint = seg.firstPoint*2; 6646 lastPoint = firstPoint+(seg.type == Command.LineTo ? 2 : 6); 6647 6648 ns = 0; 6649 6650 // First two supporting points are either side of the start point 6651 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[1]*strokeWidth; 6652 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.startDir.ptr[0]*strokeWidth; 6653 6654 supportingPoints.ptr[ns++] = points[firstPoint]+seg.startDir.ptr[1]*strokeWidth; 6655 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[0]*strokeWidth; 6656 6657 // Second two supporting points are either side of the end point 6658 supportingPoints.ptr[ns++] = points[lastPoint]-seg.endDir.ptr[1]*strokeWidth; 6659 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[0]*strokeWidth; 6660 6661 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[1]*strokeWidth; 6662 supportingPoints.ptr[ns++] = points[lastPoint+1]-seg.endDir.ptr[0]*strokeWidth; 6663 6664 if ((seg.flags&NVGSegmentFlags.Corner) && prevseg !is null) { 6665 seg.miterDir.ptr[0] = 0.5f*(-prevseg.endDir.ptr[1]-seg.startDir.ptr[1]); 6666 seg.miterDir.ptr[1] = 0.5f*(prevseg.endDir.ptr[0]+seg.startDir.ptr[0]); 6667 6668 immutable float M2 = seg.miterDir.ptr[0]*seg.miterDir.ptr[0]+seg.miterDir.ptr[1]*seg.miterDir.ptr[1]; 6669 6670 if (M2 > 0.000001f) { 6671 float scale = 1.0f/M2; 6672 if (scale > 600.0f) scale = 600.0f; 6673 seg.miterDir.ptr[0] *= scale; 6674 seg.miterDir.ptr[1] *= scale; 6675 } 6676 6677 //NVG_PICK_DEBUG_VECTOR_SCALE(&points[firstPoint], seg.miterDir, 10); 6678 6679 // Add an additional support at the corner on the other line 6680 supportingPoints.ptr[ns++] = points[firstPoint]-prevseg.endDir.ptr[1]*strokeWidth; 6681 supportingPoints.ptr[ns++] = points[firstPoint+1]+prevseg.endDir.ptr[0]*strokeWidth; 6682 6683 if (lineJoin == NVGLineCap.Miter || lineJoin == NVGLineCap.Bevel) { 6684 // Set a corner as beveled if the join type is bevel or mitered and 6685 // miterLimit is hit. 6686 if (lineJoin == NVGLineCap.Bevel || (M2*miterLimit*miterLimit) < 1.0f) { 6687 seg.flags |= NVGSegmentFlags.Bevel; 6688 } else { 6689 // Corner is mitered - add miter point as a support 6690 supportingPoints.ptr[ns++] = points[firstPoint]+seg.miterDir.ptr[0]*strokeWidth; 6691 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.miterDir.ptr[1]*strokeWidth; 6692 } 6693 } else if (lineJoin == NVGLineCap.Round) { 6694 // ... and at the midpoint of the corner arc 6695 float[2] vertexN = [ -seg.startDir.ptr[0]+prevseg.endDir.ptr[0], -seg.startDir.ptr[1]+prevseg.endDir.ptr[1] ]; 6696 nvg__normalize(&vertexN[0], &vertexN[1]); 6697 6698 supportingPoints.ptr[ns++] = points[firstPoint]+vertexN[0]*strokeWidth; 6699 supportingPoints.ptr[ns++] = points[firstPoint+1]+vertexN[1]*strokeWidth; 6700 } 6701 } 6702 6703 if (seg.flags&NVGSegmentFlags.Cap) { 6704 switch (lineCap) { 6705 case NVGLineCap.Butt: 6706 // supports for butt already added 6707 break; 6708 case NVGLineCap.Square: 6709 // square cap supports are just the original two supports moved out along the direction 6710 supportingPoints.ptr[ns++] = supportingPoints.ptr[0]-seg.startDir.ptr[0]*strokeWidth; 6711 supportingPoints.ptr[ns++] = supportingPoints.ptr[1]-seg.startDir.ptr[1]*strokeWidth; 6712 supportingPoints.ptr[ns++] = supportingPoints.ptr[2]-seg.startDir.ptr[0]*strokeWidth; 6713 supportingPoints.ptr[ns++] = supportingPoints.ptr[3]-seg.startDir.ptr[1]*strokeWidth; 6714 break; 6715 case NVGLineCap.Round: 6716 // add one additional support for the round cap along the dir 6717 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[0]*strokeWidth; 6718 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[1]*strokeWidth; 6719 break; 6720 default: 6721 break; 6722 } 6723 } 6724 6725 if (seg.flags&NVGSegmentFlags.Endcap) { 6726 // end supporting points, either side of line 6727 int end = 4; 6728 switch(lineCap) { 6729 case NVGLineCap.Butt: 6730 // supports for butt already added 6731 break; 6732 case NVGLineCap.Square: 6733 // square cap supports are just the original two supports moved out along the direction 6734 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+0]+seg.endDir.ptr[0]*strokeWidth; 6735 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+1]+seg.endDir.ptr[1]*strokeWidth; 6736 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+2]+seg.endDir.ptr[0]*strokeWidth; 6737 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+3]+seg.endDir.ptr[1]*strokeWidth; 6738 break; 6739 case NVGLineCap.Round: 6740 // add one additional support for the round cap along the dir 6741 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[0]*strokeWidth; 6742 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[1]*strokeWidth; 6743 break; 6744 default: 6745 break; 6746 } 6747 } 6748 6749 nvg__expandBounds(seg.bounds, supportingPoints.ptr, ns/2); 6750 6751 prevseg = seg; 6752 } 6753 } 6754 6755 NVGpickPath* nvg__pickPathCreate (NVGContext context, const(float)[] acommands, int id, bool forStroke) { 6756 NVGpickScene* ps = nvg__pickSceneGet(context); 6757 if (ps is null) return null; 6758 6759 int i = 0; 6760 6761 int ncommands = cast(int)acommands.length; 6762 const(float)* commands = acommands.ptr; 6763 6764 NVGpickPath* pp = null; 6765 NVGpickSubPath* psp = null; 6766 float[2] start = void; 6767 int firstPoint; 6768 6769 //bool hasHoles = false; 6770 NVGpickSubPath* prev = null; 6771 6772 float[8] points = void; 6773 float[2] inflections = void; 6774 int ninflections = 0; 6775 6776 NVGstate* state = nvg__getState(context); 6777 float[4] totalBounds = void; 6778 NVGsegment* segments = null; 6779 const(NVGsegment)* seg = null; 6780 NVGpickSubPath *curpsp; 6781 6782 pp = nvg__allocPickPath(ps); 6783 if (pp is null) return null; 6784 6785 pp.id = id; 6786 6787 bool hasPoints = false; 6788 6789 void closeIt () { 6790 if (psp is null || !hasPoints) return; 6791 if (ps.points[(ps.npoints-1)*2] != start.ptr[0] || ps.points[(ps.npoints-1)*2+1] != start.ptr[1]) { 6792 firstPoint = nvg__pickSceneAddPoints(ps, start.ptr, 1); 6793 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, Command.LineTo, NVGSegmentFlags.Corner); 6794 } 6795 psp.closed = true; 6796 } 6797 6798 while (i < ncommands) { 6799 int cmd = cast(int)commands[i++]; 6800 switch (cmd) { 6801 case Command.MoveTo: // one coordinate pair 6802 const(float)* tfxy = commands+i; 6803 i += 2; 6804 6805 // new starting point 6806 start.ptr[0..2] = tfxy[0..2]; 6807 6808 // start a new path for each sub path to handle sub paths that intersect other sub paths 6809 prev = psp; 6810 psp = nvg__allocPickSubPath(ps); 6811 if (psp is null) { psp = prev; break; } 6812 psp.firstSegment = -1; 6813 psp.winding = NVGSolidity.Solid; 6814 psp.next = prev; 6815 6816 nvg__pickSceneAddPoints(ps, tfxy, 1); 6817 hasPoints = true; 6818 break; 6819 case Command.LineTo: // one coordinate pair 6820 const(float)* tfxy = commands+i; 6821 i += 2; 6822 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 1); 6823 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner); 6824 hasPoints = true; 6825 break; 6826 case Command.BezierTo: // three coordinate pairs 6827 const(float)* tfxy = commands+i; 6828 i += 3*2; 6829 6830 // Split the curve at it's dx==0 or dy==0 inflection points. 6831 // Thus: 6832 // A horizontal line only ever interects the curves once. 6833 // and 6834 // Finding the closest point on any curve converges more reliably. 6835 6836 // NOTE: We could just split on dy==0 here. 6837 6838 memcpy(&points.ptr[0], &ps.points[(ps.npoints-1)*2], float.sizeof*2); 6839 memcpy(&points.ptr[2], tfxy, float.sizeof*2*3); 6840 6841 ninflections = 0; 6842 nvg__bezierInflections(points.ptr, 1, &ninflections, inflections.ptr); 6843 nvg__bezierInflections(points.ptr, 0, &ninflections, inflections.ptr); 6844 6845 if (ninflections) { 6846 float previnfl = 0; 6847 float[8] pointsA = void, pointsB = void; 6848 6849 nvg__smallsort(inflections.ptr, ninflections); 6850 6851 for (int infl = 0; infl < ninflections; ++infl) { 6852 if (nvg__absf(inflections.ptr[infl]-previnfl) < NVGPickEPS) continue; 6853 6854 immutable float t = (inflections.ptr[infl]-previnfl)*(1.0f/(1.0f-previnfl)); 6855 6856 previnfl = inflections.ptr[infl]; 6857 6858 nvg__splitBezier(points.ptr, t, pointsA.ptr, pointsB.ptr); 6859 6860 firstPoint = nvg__pickSceneAddPoints(ps, &pointsA.ptr[2], 3); 6861 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, (infl == 0) ? NVGSegmentFlags.Corner : 0); 6862 6863 memcpy(points.ptr, pointsB.ptr, float.sizeof*8); 6864 } 6865 6866 firstPoint = nvg__pickSceneAddPoints(ps, &pointsB.ptr[2], 3); 6867 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, 0); 6868 } else { 6869 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 3); 6870 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner); 6871 } 6872 hasPoints = true; 6873 break; 6874 case Command.Close: 6875 closeIt(); 6876 break; 6877 case Command.Winding: 6878 psp.winding = cast(short)cast(int)commands[i]; 6879 //if (psp.winding == NVGSolidity.Hole) hasHoles = true; 6880 i += 1; 6881 break; 6882 default: 6883 break; 6884 } 6885 } 6886 6887 // force-close filled paths 6888 if (psp !is null && !forStroke && hasPoints && !psp.closed) closeIt(); 6889 6890 pp.flags = (forStroke ? NVGPathFlags.Stroke : NVGPathFlags.Fill); 6891 pp.subPaths = psp; 6892 pp.strokeWidth = state.strokeWidth*0.5f; 6893 pp.miterLimit = state.miterLimit; 6894 pp.lineCap = cast(short)state.lineCap; 6895 pp.lineJoin = cast(short)state.lineJoin; 6896 pp.evenOddMode = nvg__getState(context).evenOddMode; 6897 6898 nvg__initBounds(totalBounds); 6899 6900 for (curpsp = psp; curpsp; curpsp = curpsp.next) { 6901 if (forStroke) { 6902 nvg__pickSubPathAddStrokeSupports(ps, curpsp, pp.strokeWidth, pp.lineCap, pp.lineJoin, pp.miterLimit); 6903 } else { 6904 nvg__pickSubPathAddFillSupports(ps, curpsp); 6905 } 6906 6907 if (curpsp.firstSegment == -1) continue; 6908 segments = &ps.segments[curpsp.firstSegment]; 6909 nvg__initBounds(curpsp.bounds); 6910 for (int s = 0; s < curpsp.nsegments; ++s) { 6911 seg = &segments[s]; 6912 //NVG_PICK_DEBUG_BOUNDS(seg.bounds); 6913 nvg__unionBounds(curpsp.bounds, seg.bounds); 6914 } 6915 6916 nvg__unionBounds(totalBounds, curpsp.bounds); 6917 } 6918 6919 // Store the scissor rect if present. 6920 if (state.scissor.extent.ptr[0] != -1.0f) { 6921 // Use points storage to store the scissor data 6922 pp.scissor = nvg__pickSceneAddPoints(ps, null, 4); 6923 float* scissor = &ps.points[pp.scissor*2]; 6924 6925 //memcpy(scissor, state.scissor.xform.ptr, 6*float.sizeof); 6926 scissor[0..6] = state.scissor.xform.mat[]; 6927 memcpy(scissor+6, state.scissor.extent.ptr, 2*float.sizeof); 6928 6929 pp.flags |= NVGPathFlags.Scissor; 6930 } 6931 6932 memcpy(pp.bounds.ptr, totalBounds.ptr, float.sizeof*4); 6933 6934 return pp; 6935 } 6936 6937 6938 // Struct management 6939 NVGpickPath* nvg__allocPickPath (NVGpickScene* ps) { 6940 NVGpickPath* pp = ps.freePaths; 6941 if (pp !is null) { 6942 ps.freePaths = pp.next; 6943 } else { 6944 pp = cast(NVGpickPath*)malloc(NVGpickPath.sizeof); 6945 } 6946 memset(pp, 0, NVGpickPath.sizeof); 6947 return pp; 6948 } 6949 6950 // Put a pick path and any sub paths (back) to the free lists. 6951 void nvg__freePickPath (NVGpickScene* ps, NVGpickPath* pp) { 6952 // Add all sub paths to the sub path free list. 6953 // Finds the end of the path sub paths, links that to the current 6954 // sub path free list head and replaces the head ptr with the 6955 // head path sub path entry. 6956 NVGpickSubPath* psp = null; 6957 for (psp = pp.subPaths; psp !is null && psp.next !is null; psp = psp.next) {} 6958 6959 if (psp) { 6960 psp.next = ps.freeSubPaths; 6961 ps.freeSubPaths = pp.subPaths; 6962 } 6963 pp.subPaths = null; 6964 6965 // Add the path to the path freelist 6966 pp.next = ps.freePaths; 6967 ps.freePaths = pp; 6968 if (pp.next is null) ps.lastPath = pp; 6969 } 6970 6971 NVGpickSubPath* nvg__allocPickSubPath (NVGpickScene* ps) { 6972 NVGpickSubPath* psp = ps.freeSubPaths; 6973 if (psp !is null) { 6974 ps.freeSubPaths = psp.next; 6975 } else { 6976 psp = cast(NVGpickSubPath*)malloc(NVGpickSubPath.sizeof); 6977 if (psp is null) return null; 6978 } 6979 memset(psp, 0, NVGpickSubPath.sizeof); 6980 return psp; 6981 } 6982 6983 void nvg__returnPickSubPath (NVGpickScene* ps, NVGpickSubPath* psp) { 6984 psp.next = ps.freeSubPaths; 6985 ps.freeSubPaths = psp; 6986 } 6987 6988 NVGpickScene* nvg__allocPickScene () { 6989 NVGpickScene* ps = cast(NVGpickScene*)malloc(NVGpickScene.sizeof); 6990 if (ps is null) return null; 6991 memset(ps, 0, NVGpickScene.sizeof); 6992 ps.nlevels = 5; 6993 return ps; 6994 } 6995 6996 void nvg__deletePickScene (NVGpickScene* ps) { 6997 NVGpickPath* pp; 6998 NVGpickSubPath* psp; 6999 7000 // Add all paths (and thus sub paths) to the free list(s). 7001 while (ps.paths !is null) { 7002 pp = ps.paths.next; 7003 nvg__freePickPath(ps, ps.paths); 7004 ps.paths = pp; 7005 } 7006 7007 // Delete all paths 7008 while (ps.freePaths !is null) { 7009 pp = ps.freePaths; 7010 ps.freePaths = pp.next; 7011 while (pp.subPaths !is null) { 7012 psp = pp.subPaths; 7013 pp.subPaths = psp.next; 7014 free(psp); 7015 } 7016 free(pp); 7017 } 7018 7019 // Delete all sub paths 7020 while (ps.freeSubPaths !is null) { 7021 psp = ps.freeSubPaths.next; 7022 free(ps.freeSubPaths); 7023 ps.freeSubPaths = psp; 7024 } 7025 7026 ps.npoints = 0; 7027 ps.nsegments = 0; 7028 7029 if (ps.levels !is null) { 7030 free(ps.levels[0]); 7031 free(ps.levels); 7032 } 7033 7034 if (ps.picked !is null) free(ps.picked); 7035 if (ps.points !is null) free(ps.points); 7036 if (ps.segments !is null) free(ps.segments); 7037 7038 free(ps); 7039 } 7040 7041 NVGpickScene* nvg__pickSceneGet (NVGContext ctx) { 7042 if (ctx.pickScene is null) ctx.pickScene = nvg__allocPickScene(); 7043 return ctx.pickScene; 7044 } 7045 7046 7047 // Applies Casteljau's algorithm to a cubic bezier for a given parameter t 7048 // points is 4 points (8 floats) 7049 // lvl1 is 3 points (6 floats) 7050 // lvl2 is 2 points (4 floats) 7051 // lvl3 is 1 point (2 floats) 7052 void nvg__casteljau (const(float)* points, float t, float* lvl1, float* lvl2, float* lvl3) { 7053 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0; 7054 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1; 7055 7056 // Level 1 7057 lvl1[x0] = (points[x1]-points[x0])*t+points[x0]; 7058 lvl1[y0] = (points[y1]-points[y0])*t+points[y0]; 7059 7060 lvl1[x1] = (points[x2]-points[x1])*t+points[x1]; 7061 lvl1[y1] = (points[y2]-points[y1])*t+points[y1]; 7062 7063 lvl1[x2] = (points[x3]-points[x2])*t+points[x2]; 7064 lvl1[y2] = (points[y3]-points[y2])*t+points[y2]; 7065 7066 // Level 2 7067 lvl2[x0] = (lvl1[x1]-lvl1[x0])*t+lvl1[x0]; 7068 lvl2[y0] = (lvl1[y1]-lvl1[y0])*t+lvl1[y0]; 7069 7070 lvl2[x1] = (lvl1[x2]-lvl1[x1])*t+lvl1[x1]; 7071 lvl2[y1] = (lvl1[y2]-lvl1[y1])*t+lvl1[y1]; 7072 7073 // Level 3 7074 lvl3[x0] = (lvl2[x1]-lvl2[x0])*t+lvl2[x0]; 7075 lvl3[y0] = (lvl2[y1]-lvl2[y0])*t+lvl2[y0]; 7076 } 7077 7078 // Calculates a point on a bezier at point t. 7079 void nvg__bezierEval (const(float)* points, float t, ref float[2] tpoint) { 7080 immutable float omt = 1-t; 7081 immutable float omt3 = omt*omt*omt; 7082 immutable float omt2 = omt*omt; 7083 immutable float t3 = t*t*t; 7084 immutable float t2 = t*t; 7085 7086 tpoint.ptr[0] = 7087 points[0]*omt3+ 7088 points[2]*3.0f*omt2*t+ 7089 points[4]*3.0f*omt*t2+ 7090 points[6]*t3; 7091 7092 tpoint.ptr[1] = 7093 points[1]*omt3+ 7094 points[3]*3.0f*omt2*t+ 7095 points[5]*3.0f*omt*t2+ 7096 points[7]*t3; 7097 } 7098 7099 // Splits a cubic bezier curve into two parts at point t. 7100 void nvg__splitBezier (const(float)* points, float t, float* pointsA, float* pointsB) { 7101 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0; 7102 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1; 7103 7104 float[6] lvl1 = void; 7105 float[4] lvl2 = void; 7106 float[2] lvl3 = void; 7107 7108 nvg__casteljau(points, t, lvl1.ptr, lvl2.ptr, lvl3.ptr); 7109 7110 // First half 7111 pointsA[x0] = points[x0]; 7112 pointsA[y0] = points[y0]; 7113 7114 pointsA[x1] = lvl1.ptr[x0]; 7115 pointsA[y1] = lvl1.ptr[y0]; 7116 7117 pointsA[x2] = lvl2.ptr[x0]; 7118 pointsA[y2] = lvl2.ptr[y0]; 7119 7120 pointsA[x3] = lvl3.ptr[x0]; 7121 pointsA[y3] = lvl3.ptr[y0]; 7122 7123 // Second half 7124 pointsB[x0] = lvl3.ptr[x0]; 7125 pointsB[y0] = lvl3.ptr[y0]; 7126 7127 pointsB[x1] = lvl2.ptr[x1]; 7128 pointsB[y1] = lvl2.ptr[y1]; 7129 7130 pointsB[x2] = lvl1.ptr[x2]; 7131 pointsB[y2] = lvl1.ptr[y2]; 7132 7133 pointsB[x3] = points[x3]; 7134 pointsB[y3] = points[y3]; 7135 } 7136 7137 // Calculates the inflection points in coordinate coord (X = 0, Y = 1) of a cubic bezier. 7138 // Appends any found inflection points to the array inflections and increments *ninflections. 7139 // So finds the parameters where dx/dt or dy/dt is 0 7140 void nvg__bezierInflections (const(float)* points, int coord, int* ninflections, float* inflections) { 7141 immutable float v0 = points[0*2+coord], v1 = points[1*2+coord], v2 = points[2*2+coord], v3 = points[3*2+coord]; 7142 float[2] t = void; 7143 int nvalid = *ninflections; 7144 7145 immutable float a = 3.0f*( -v0+3.0f*v1-3.0f*v2+v3 ); 7146 immutable float b = 6.0f*( v0-2.0f*v1+v2 ); 7147 immutable float c = 3.0f*( v1-v0 ); 7148 7149 float d = b*b-4.0f*a*c; 7150 if (nvg__absf(d-0.0f) < NVGPickEPS) { 7151 // Zero or one root 7152 t.ptr[0] = -b/2.0f*a; 7153 if (t.ptr[0] > NVGPickEPS && t.ptr[0] < (1.0f-NVGPickEPS)) { 7154 inflections[nvalid] = t.ptr[0]; 7155 ++nvalid; 7156 } 7157 } else if (d > NVGPickEPS) { 7158 // zero, one or two roots 7159 d = nvg__sqrtf(d); 7160 7161 t.ptr[0] = (-b+d)/(2.0f*a); 7162 t.ptr[1] = (-b-d)/(2.0f*a); 7163 7164 for (int i = 0; i < 2; ++i) { 7165 if (t.ptr[i] > NVGPickEPS && t.ptr[i] < (1.0f-NVGPickEPS)) { 7166 inflections[nvalid] = t.ptr[i]; 7167 ++nvalid; 7168 } 7169 } 7170 } else { 7171 // zero roots 7172 } 7173 7174 *ninflections = nvalid; 7175 } 7176 7177 // Sort a small number of floats in ascending order (0 < n < 6) 7178 void nvg__smallsort (float* values, int n) { 7179 bool bSwapped = true; 7180 for (int j = 0; j < n-1 && bSwapped; ++j) { 7181 bSwapped = false; 7182 for (int i = 0; i < n-1; ++i) { 7183 if (values[i] > values[i+1]) { 7184 auto tmp = values[i]; 7185 values[i] = values[i+1]; 7186 values[i+1] = tmp; 7187 } 7188 } 7189 } 7190 } 7191 7192 // Calculates the bounding rect of a given cubic bezier curve. 7193 void nvg__bezierBounds (const(float)* points, ref float[4] bounds) { 7194 float[4] inflections = void; 7195 int ninflections = 0; 7196 float[2] tpoint = void; 7197 7198 nvg__initBounds(bounds); 7199 7200 // Include start and end points in bounds 7201 nvg__expandBounds(bounds, &points[0], 1); 7202 nvg__expandBounds(bounds, &points[6], 1); 7203 7204 // Calculate dx==0 and dy==0 inflection points and add them to the bounds 7205 7206 nvg__bezierInflections(points, 0, &ninflections, inflections.ptr); 7207 nvg__bezierInflections(points, 1, &ninflections, inflections.ptr); 7208 7209 foreach (immutable int i; 0..ninflections) { 7210 nvg__bezierEval(points, inflections[i], tpoint); 7211 nvg__expandBounds(bounds, tpoint.ptr, 1); 7212 } 7213 } 7214 7215 // Checks to see if a line originating from x,y along the +ve x axis 7216 // intersects the given line (points[0],points[1]) -> (points[2], points[3]). 7217 // Returns `true` on intersection. 7218 // Horizontal lines are never hit. 7219 bool nvg__intersectLine (const(float)* points, float x, float y) { 7220 immutable float x1 = points[0]; 7221 immutable float y1 = points[1]; 7222 immutable float x2 = points[2]; 7223 immutable float y2 = points[3]; 7224 immutable float d = y2-y1; 7225 if (d > NVGPickEPS || d < -NVGPickEPS) { 7226 immutable float s = (x2-x1)/d; 7227 immutable float lineX = x1+(y-y1)*s; 7228 return (lineX > x); 7229 } else { 7230 return false; 7231 } 7232 } 7233 7234 // Checks to see if a line originating from x,y along the +ve x axis intersects the given bezier. 7235 // It is assumed that the line originates from within the bounding box of 7236 // the bezier and that the curve has no dy=0 inflection points. 7237 // Returns the number of intersections found (which is either 1 or 0). 7238 int nvg__intersectBezier (const(float)* points, float x, float y) { 7239 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0]; 7240 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1]; 7241 7242 if (y0 == y1 && y1 == y2 && y2 == y3) return 0; 7243 7244 // Initial t guess 7245 float t = void; 7246 if (y3 != y0) t = (y-y0)/(y3-y0); 7247 else if (x3 != x0) t = (x-x0)/(x3-x0); 7248 else t = 0.5f; 7249 7250 // A few Newton iterations 7251 for (int iter = 0; iter < 6; ++iter) { 7252 immutable float omt = 1-t; 7253 immutable float omt2 = omt*omt; 7254 immutable float t2 = t*t; 7255 immutable float omt3 = omt2*omt; 7256 immutable float t3 = t2*t; 7257 7258 immutable float ty = y0*omt3 + 7259 y1*3.0f*omt2*t + 7260 y2*3.0f*omt*t2 + 7261 y3*t3; 7262 7263 // Newton iteration 7264 immutable float dty = 3.0f*omt2*(y1-y0) + 7265 6.0f*omt*t*(y2-y1) + 7266 3.0f*t2*(y3-y2); 7267 7268 // dty will never == 0 since: 7269 // Either omt, omt2 are zero OR t2 is zero 7270 // y0 != y1 != y2 != y3 (checked above) 7271 t = t-(ty-y)/dty; 7272 } 7273 7274 { 7275 immutable float omt = 1-t; 7276 immutable float omt2 = omt*omt; 7277 immutable float t2 = t*t; 7278 immutable float omt3 = omt2*omt; 7279 immutable float t3 = t2*t; 7280 7281 immutable float tx = 7282 x0*omt3+ 7283 x1*3.0f*omt2*t+ 7284 x2*3.0f*omt*t2+ 7285 x3*t3; 7286 7287 return (tx > x ? 1 : 0); 7288 } 7289 } 7290 7291 // Finds the closest point on a line to a given point 7292 void nvg__closestLine (const(float)* points, float x, float y, float* closest, float* ot) { 7293 immutable float x1 = points[0]; 7294 immutable float y1 = points[1]; 7295 immutable float x2 = points[2]; 7296 immutable float y2 = points[3]; 7297 immutable float pqx = x2-x1; 7298 immutable float pqz = y2-y1; 7299 immutable float dx = x-x1; 7300 immutable float dz = y-y1; 7301 immutable float d = pqx*pqx+pqz*pqz; 7302 float t = pqx*dx+pqz*dz; 7303 if (d > 0) t /= d; 7304 if (t < 0) t = 0; else if (t > 1) t = 1; 7305 closest[0] = x1+t*pqx; 7306 closest[1] = y1+t*pqz; 7307 *ot = t; 7308 } 7309 7310 // Finds the closest point on a curve for a given point (x,y). 7311 // Assumes that the curve has no dx==0 or dy==0 inflection points. 7312 void nvg__closestBezier (const(float)* points, float x, float y, float* closest, float *ot) { 7313 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0]; 7314 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1]; 7315 7316 // This assumes that the curve has no dy=0 inflection points. 7317 7318 // Initial t guess 7319 float t = 0.5f; 7320 7321 // A few Newton iterations 7322 for (int iter = 0; iter < 6; ++iter) { 7323 immutable float omt = 1-t; 7324 immutable float omt2 = omt*omt; 7325 immutable float t2 = t*t; 7326 immutable float omt3 = omt2*omt; 7327 immutable float t3 = t2*t; 7328 7329 immutable float ty = 7330 y0*omt3+ 7331 y1*3.0f*omt2*t+ 7332 y2*3.0f*omt*t2+ 7333 y3*t3; 7334 7335 immutable float tx = 7336 x0*omt3+ 7337 x1*3.0f*omt2*t+ 7338 x2*3.0f*omt*t2+ 7339 x3*t3; 7340 7341 // Newton iteration 7342 immutable float dty = 7343 3.0f*omt2*(y1-y0)+ 7344 6.0f*omt*t*(y2-y1)+ 7345 3.0f*t2*(y3-y2); 7346 7347 immutable float ddty = 7348 6.0f*omt*(y2-2.0f*y1+y0)+ 7349 6.0f*t*(y3-2.0f*y2+y1); 7350 7351 immutable float dtx = 7352 3.0f*omt2*(x1-x0)+ 7353 6.0f*omt*t*(x2-x1)+ 7354 3.0f*t2*(x3-x2); 7355 7356 immutable float ddtx = 7357 6.0f*omt*(x2-2.0f*x1+x0)+ 7358 6.0f*t*(x3-2.0f*x2+x1); 7359 7360 immutable float errorx = tx-x; 7361 immutable float errory = ty-y; 7362 7363 immutable float n = errorx*dtx+errory*dty; 7364 if (n == 0) break; 7365 7366 immutable float d = dtx*dtx+dty*dty+errorx*ddtx+errory*ddty; 7367 if (d != 0) t = t-n/d; else break; 7368 } 7369 7370 t = nvg__max(0, nvg__min(1.0, t)); 7371 *ot = t; 7372 { 7373 immutable float omt = 1-t; 7374 immutable float omt2 = omt*omt; 7375 immutable float t2 = t*t; 7376 immutable float omt3 = omt2*omt; 7377 immutable float t3 = t2*t; 7378 7379 immutable float ty = 7380 y0*omt3+ 7381 y1*3.0f*omt2*t+ 7382 y2*3.0f*omt*t2+ 7383 y3*t3; 7384 7385 immutable float tx = 7386 x0*omt3+ 7387 x1*3.0f*omt2*t+ 7388 x2*3.0f*omt*t2+ 7389 x3*t3; 7390 7391 closest[0] = tx; 7392 closest[1] = ty; 7393 } 7394 } 7395 7396 // Returns: 7397 // 1 If (x,y) is contained by the stroke of the path 7398 // 0 If (x,y) is not contained by the path. 7399 int nvg__pickSubPathStroke (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, float strokeWidth, int lineCap, int lineJoin) { 7400 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0; 7401 if (psp.firstSegment == -1) return 0; 7402 7403 float[2] closest = void; 7404 float[2] d = void; 7405 float t = void; 7406 7407 // trace a line from x,y out along the positive x axis and count the number of intersections 7408 int nsegments = psp.nsegments; 7409 const(NVGsegment)* seg = ps.segments+psp.firstSegment; 7410 const(NVGsegment)* prevseg = (psp.closed ? &ps.segments[psp.firstSegment+nsegments-1] : null); 7411 immutable float strokeWidthSqd = strokeWidth*strokeWidth; 7412 7413 for (int s = 0; s < nsegments; ++s, prevseg = seg, ++seg) { 7414 if (nvg__pointInBounds(x, y, seg.bounds)) { 7415 // Line potentially hits stroke. 7416 switch (seg.type) { 7417 case Command.LineTo: 7418 nvg__closestLine(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t); 7419 break; 7420 case Command.BezierTo: 7421 nvg__closestBezier(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t); 7422 break; 7423 default: 7424 continue; 7425 } 7426 7427 d.ptr[0] = x-closest.ptr[0]; 7428 d.ptr[1] = y-closest.ptr[1]; 7429 7430 if ((t >= NVGPickEPS && t <= 1.0f-NVGPickEPS) || 7431 (seg.flags&(NVGSegmentFlags.Corner|NVGSegmentFlags.Cap|NVGSegmentFlags.Endcap)) == 0 || 7432 (lineJoin == NVGLineCap.Round)) 7433 { 7434 // Closest point is in the middle of the line/curve, at a rounded join/cap 7435 // or at a smooth join 7436 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7437 if (distSqd < strokeWidthSqd) return 1; 7438 } else if ((t > 1.0f-NVGPickEPS && (seg.flags&NVGSegmentFlags.Endcap)) || 7439 (t < NVGPickEPS && (seg.flags&NVGSegmentFlags.Cap))) { 7440 switch (lineCap) { 7441 case NVGLineCap.Butt: 7442 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7443 immutable float dirD = (t < NVGPickEPS ? 7444 -(d.ptr[0]*seg.startDir.ptr[0]+d.ptr[1]*seg.startDir.ptr[1]) : 7445 d.ptr[0]*seg.endDir.ptr[0]+d.ptr[1]*seg.endDir.ptr[1]); 7446 if (dirD < -NVGPickEPS && distSqd < strokeWidthSqd) return 1; 7447 break; 7448 case NVGLineCap.Square: 7449 if (nvg__absf(d.ptr[0]) < strokeWidth && nvg__absf(d.ptr[1]) < strokeWidth) return 1; 7450 break; 7451 case NVGLineCap.Round: 7452 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7453 if (distSqd < strokeWidthSqd) return 1; 7454 break; 7455 default: 7456 break; 7457 } 7458 } else if (seg.flags&NVGSegmentFlags.Corner) { 7459 // Closest point is at a corner 7460 const(NVGsegment)* seg0, seg1; 7461 7462 if (t < NVGPickEPS) { 7463 seg0 = prevseg; 7464 seg1 = seg; 7465 } else { 7466 seg0 = seg; 7467 seg1 = (s == nsegments-1 ? &ps.segments[psp.firstSegment] : seg+1); 7468 } 7469 7470 if (!(seg1.flags&NVGSegmentFlags.Bevel)) { 7471 immutable float prevNDist = -seg0.endDir.ptr[1]*d.ptr[0]+seg0.endDir.ptr[0]*d.ptr[1]; 7472 immutable float curNDist = seg1.startDir.ptr[1]*d.ptr[0]-seg1.startDir.ptr[0]*d.ptr[1]; 7473 if (nvg__absf(prevNDist) < strokeWidth && nvg__absf(curNDist) < strokeWidth) return 1; 7474 } else { 7475 d.ptr[0] -= -seg1.startDir.ptr[1]*strokeWidth; 7476 d.ptr[1] -= +seg1.startDir.ptr[0]*strokeWidth; 7477 if (seg1.miterDir.ptr[0]*d.ptr[0]+seg1.miterDir.ptr[1]*d.ptr[1] < 0) return 1; 7478 } 7479 } 7480 } 7481 } 7482 7483 return 0; 7484 } 7485 7486 // Returns: 7487 // 1 If (x,y) is contained by the path and the path is solid. 7488 // -1 If (x,y) is contained by the path and the path is a hole. 7489 // 0 If (x,y) is not contained by the path. 7490 int nvg__pickSubPath (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, bool evenOddMode) { 7491 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0; 7492 if (psp.firstSegment == -1) return 0; 7493 7494 const(NVGsegment)* seg = &ps.segments[psp.firstSegment]; 7495 int nsegments = psp.nsegments; 7496 int nintersections = 0; 7497 7498 // trace a line from x,y out along the positive x axis and count the number of intersections 7499 for (int s = 0; s < nsegments; ++s, ++seg) { 7500 if ((seg.bounds.ptr[1]-NVGPickEPS) < y && 7501 (seg.bounds.ptr[3]-NVGPickEPS) > y && 7502 seg.bounds.ptr[2] > x) 7503 { 7504 // Line hits the box. 7505 switch (seg.type) { 7506 case Command.LineTo: 7507 if (seg.bounds.ptr[0] > x) { 7508 // line originates outside the box 7509 ++nintersections; 7510 } else { 7511 // line originates inside the box 7512 nintersections += nvg__intersectLine(&ps.points[seg.firstPoint*2], x, y); 7513 } 7514 break; 7515 case Command.BezierTo: 7516 if (seg.bounds.ptr[0] > x) { 7517 // line originates outside the box 7518 ++nintersections; 7519 } else { 7520 // line originates inside the box 7521 nintersections += nvg__intersectBezier(&ps.points[seg.firstPoint*2], x, y); 7522 } 7523 break; 7524 default: 7525 break; 7526 } 7527 } 7528 } 7529 7530 if (evenOddMode) { 7531 return nintersections; 7532 } else { 7533 return (nintersections&1 ? (psp.winding == NVGSolidity.Solid ? 1 : -1) : 0); 7534 } 7535 } 7536 7537 bool nvg__pickPath (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) { 7538 int pickCount = 0; 7539 const(NVGpickSubPath)* psp = pp.subPaths; 7540 while (psp !is null) { 7541 pickCount += nvg__pickSubPath(ps, psp, x, y, pp.evenOddMode); 7542 psp = psp.next; 7543 } 7544 return ((pp.evenOddMode ? pickCount&1 : pickCount) != 0); 7545 } 7546 7547 bool nvg__pickPathStroke (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) { 7548 const(NVGpickSubPath)* psp = pp.subPaths; 7549 while (psp !is null) { 7550 if (nvg__pickSubPathStroke(ps, psp, x, y, pp.strokeWidth, pp.lineCap, pp.lineJoin)) return true; 7551 psp = psp.next; 7552 } 7553 return false; 7554 } 7555 7556 bool nvg__pickPathTestBounds (NVGContext ctx, const NVGpickScene* ps, const NVGpickPath* pp, float x, float y) { 7557 if (nvg__pointInBounds(x, y, pp.bounds)) { 7558 //{ import core.stdc.stdio; printf(" (0): in bounds!\n"); } 7559 if (pp.flags&NVGPathFlags.Scissor) { 7560 const(float)* scissor = &ps.points[pp.scissor*2]; 7561 // untransform scissor translation 7562 float stx = void, sty = void; 7563 ctx.gpuUntransformPoint(&stx, &sty, scissor[4], scissor[5]); 7564 immutable float rx = x-stx; 7565 immutable float ry = y-sty; 7566 //{ 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]); } 7567 if (nvg__absf((scissor[0]*rx)+(scissor[1]*ry)) > scissor[6] || 7568 nvg__absf((scissor[2]*rx)+(scissor[3]*ry)) > scissor[7]) 7569 { 7570 //{ import core.stdc.stdio; printf(" (1): scissor reject!\n"); } 7571 return false; 7572 } 7573 } 7574 return true; 7575 } 7576 return false; 7577 } 7578 7579 int nvg__countBitsUsed (uint v) pure { 7580 pragma(inline, true); 7581 import core.bitop : bsr; 7582 return (v != 0 ? bsr(v)+1 : 0); 7583 } 7584 7585 void nvg__pickSceneInsert (NVGpickScene* ps, NVGpickPath* pp) { 7586 if (ps is null || pp is null) return; 7587 7588 int[4] cellbounds; 7589 int base = ps.nlevels-1; 7590 int level; 7591 int levelwidth; 7592 int levelshift; 7593 int levelx; 7594 int levely; 7595 NVGpickPath** cell = null; 7596 7597 // Bit tricks for inserting into an implicit quadtree. 7598 7599 // Calc bounds of path in cells at the lowest level 7600 cellbounds.ptr[0] = cast(int)(pp.bounds.ptr[0]/ps.xdim); 7601 cellbounds.ptr[1] = cast(int)(pp.bounds.ptr[1]/ps.ydim); 7602 cellbounds.ptr[2] = cast(int)(pp.bounds.ptr[2]/ps.xdim); 7603 cellbounds.ptr[3] = cast(int)(pp.bounds.ptr[3]/ps.ydim); 7604 7605 // Find which bits differ between the min/max x/y coords 7606 cellbounds.ptr[0] ^= cellbounds.ptr[2]; 7607 cellbounds.ptr[1] ^= cellbounds.ptr[3]; 7608 7609 // Use the number of bits used (countBitsUsed(x) == sizeof(int) * 8 - clz(x); 7610 // to calculate the level to insert at (the level at which the bounds fit in a single cell) 7611 level = nvg__min(base-nvg__countBitsUsed(cellbounds.ptr[0]), base-nvg__countBitsUsed(cellbounds.ptr[1])); 7612 if (level < 0) level = 0; 7613 //{ 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]); } 7614 //level = 0; 7615 7616 // Find the correct cell in the chosen level, clamping to the edges. 7617 levelwidth = 1<<level; 7618 levelshift = (ps.nlevels-level)-1; 7619 levelx = nvg__clamp(cellbounds.ptr[2]>>levelshift, 0, levelwidth-1); 7620 levely = nvg__clamp(cellbounds.ptr[3]>>levelshift, 0, levelwidth-1); 7621 7622 // Insert the path into the linked list at that cell. 7623 cell = &ps.levels[level][levely*levelwidth+levelx]; 7624 7625 pp.cellnext = *cell; 7626 *cell = pp; 7627 7628 if (ps.paths is null) ps.lastPath = pp; 7629 pp.next = ps.paths; 7630 ps.paths = pp; 7631 7632 // Store the order (depth) of the path for picking ops. 7633 pp.order = cast(short)ps.npaths; 7634 ++ps.npaths; 7635 } 7636 7637 void nvg__pickBeginFrame (NVGContext ctx, int width, int height) { 7638 NVGpickScene* ps = nvg__pickSceneGet(ctx); 7639 7640 //NVG_PICK_DEBUG_NEWFRAME(); 7641 7642 // Return all paths & sub paths from last frame to the free list 7643 while (ps.paths !is null) { 7644 NVGpickPath* pp = ps.paths.next; 7645 nvg__freePickPath(ps, ps.paths); 7646 ps.paths = pp; 7647 } 7648 7649 ps.paths = null; 7650 ps.npaths = 0; 7651 7652 // Store the screen metrics for the quadtree 7653 ps.width = width; 7654 ps.height = height; 7655 7656 immutable float lowestSubDiv = cast(float)(1<<(ps.nlevels-1)); 7657 ps.xdim = cast(float)width/lowestSubDiv; 7658 ps.ydim = cast(float)height/lowestSubDiv; 7659 7660 // Allocate the quadtree if required. 7661 if (ps.levels is null) { 7662 int ncells = 1; 7663 7664 ps.levels = cast(NVGpickPath***)malloc((NVGpickPath**).sizeof*ps.nlevels); 7665 for (int l = 0; l < ps.nlevels; ++l) { 7666 int leveldim = 1<<l; 7667 ncells += leveldim*leveldim; 7668 } 7669 7670 ps.levels[0] = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ncells); 7671 7672 int cell = 1; 7673 for (int l = 1; l < ps.nlevels; ++l) { 7674 ps.levels[l] = &ps.levels[0][cell]; 7675 int leveldim = 1<<l; 7676 cell += leveldim*leveldim; 7677 } 7678 7679 ps.ncells = ncells; 7680 } 7681 memset(ps.levels[0], 0, ps.ncells*(NVGpickPath*).sizeof); 7682 7683 // Allocate temporary storage for nvgHitTestAll results if required. 7684 if (ps.picked is null) { 7685 ps.cpicked = 16; 7686 ps.picked = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ps.cpicked); 7687 } 7688 7689 ps.npoints = 0; 7690 ps.nsegments = 0; 7691 } 7692 } // nothrow @trusted @nogc 7693 7694 7695 /// Return outline of the current path. Returned outline is not flattened. 7696 /// Group: paths 7697 public NVGPathOutline getCurrPathOutline (NVGContext ctx) nothrow @trusted @nogc { 7698 if (ctx is null || !ctx.contextAlive || ctx.ncommands == 0) return NVGPathOutline.init; 7699 7700 auto res = NVGPathOutline.createNew(); 7701 7702 const(float)[] acommands = ctx.commands[0..ctx.ncommands]; 7703 int ncommands = cast(int)acommands.length; 7704 const(float)* commands = acommands.ptr; 7705 7706 float cx = 0, cy = 0; 7707 float[2] start = void; 7708 float[4] totalBounds = [float.max, float.max, -float.max, -float.max]; 7709 float[8] bcp = void; // bezier curve points; used to calculate bounds 7710 7711 void addToBounds (in float x, in float y) nothrow @trusted @nogc { 7712 totalBounds.ptr[0] = nvg__min(totalBounds.ptr[0], x); 7713 totalBounds.ptr[1] = nvg__min(totalBounds.ptr[1], y); 7714 totalBounds.ptr[2] = nvg__max(totalBounds.ptr[2], x); 7715 totalBounds.ptr[3] = nvg__max(totalBounds.ptr[3], y); 7716 } 7717 7718 bool hasPoints = false; 7719 7720 void closeIt () nothrow @trusted @nogc { 7721 if (!hasPoints) return; 7722 if (cx != start.ptr[0] || cy != start.ptr[1]) { 7723 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo); 7724 res.ds.putArgs(start[]); 7725 cx = start.ptr[0]; 7726 cy = start.ptr[1]; 7727 addToBounds(cx, cy); 7728 } 7729 } 7730 7731 int i = 0; 7732 while (i < ncommands) { 7733 int cmd = cast(int)commands[i++]; 7734 switch (cmd) { 7735 case Command.MoveTo: // one coordinate pair 7736 const(float)* tfxy = commands+i; 7737 i += 2; 7738 // add command 7739 res.ds.putCommand(NVGPathOutline.Command.Kind.MoveTo); 7740 res.ds.putArgs(tfxy[0..2]); 7741 // new starting point 7742 start.ptr[0..2] = tfxy[0..2]; 7743 cx = tfxy[0]; 7744 cy = tfxy[0]; 7745 addToBounds(cx, cy); 7746 hasPoints = true; 7747 break; 7748 case Command.LineTo: // one coordinate pair 7749 const(float)* tfxy = commands+i; 7750 i += 2; 7751 // add command 7752 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo); 7753 res.ds.putArgs(tfxy[0..2]); 7754 cx = tfxy[0]; 7755 cy = tfxy[0]; 7756 addToBounds(cx, cy); 7757 hasPoints = true; 7758 break; 7759 case Command.BezierTo: // three coordinate pairs 7760 const(float)* tfxy = commands+i; 7761 i += 3*2; 7762 // add command 7763 res.ds.putCommand(NVGPathOutline.Command.Kind.BezierTo); 7764 res.ds.putArgs(tfxy[0..6]); 7765 // bounds 7766 bcp.ptr[0] = cx; 7767 bcp.ptr[1] = cy; 7768 bcp.ptr[2..8] = tfxy[0..6]; 7769 nvg__bezierBounds(bcp.ptr, totalBounds); 7770 cx = tfxy[4]; 7771 cy = tfxy[5]; 7772 hasPoints = true; 7773 break; 7774 case Command.Close: 7775 closeIt(); 7776 hasPoints = false; 7777 break; 7778 case Command.Winding: 7779 //psp.winding = cast(short)cast(int)commands[i]; 7780 i += 1; 7781 break; 7782 default: 7783 break; 7784 } 7785 } 7786 7787 res.ds.bounds[] = totalBounds[]; 7788 return res; 7789 } 7790 7791 7792 // ////////////////////////////////////////////////////////////////////////// // 7793 // Text 7794 7795 /** Creates font by loading it from the disk from specified file name. 7796 * Returns handle to the font or FONS_INVALID (aka -1) on error. 7797 * Use "fontname:noaa" as [name] to turn off antialiasing (if font driver supports that). 7798 * 7799 * On POSIX systems it is possible to use fontconfig font names too. 7800 * `:noaa` in font path is still allowed, but it must be the last option. 7801 * 7802 * Group: text_api 7803 */ 7804 public int createFont (NVGContext ctx, const(char)[] name, const(char)[] path) nothrow @trusted { 7805 return ctx.fs.addFont(name, path, ctx.params.fontAA); 7806 } 7807 7808 /** Creates font by loading it from the specified memory chunk. 7809 * Returns handle to the font or FONS_INVALID (aka -1) on error. 7810 * Won't free data on error. 7811 * 7812 * Group: text_api 7813 */ 7814 public int createFontMem (NVGContext ctx, const(char)[] name, ubyte* data, int ndata, bool freeData) nothrow @trusted @nogc { 7815 return ctx.fs.addFontMem(name, data, ndata, freeData, ctx.params.fontAA); 7816 } 7817 7818 /// Add fonts from another context. 7819 /// This is more effective than reloading fonts, 'cause font data will be shared. 7820 /// Group: text_api 7821 public void addFontsFrom (NVGContext ctx, NVGContext source) nothrow @trusted @nogc { 7822 if (ctx is null || source is null) return; 7823 ctx.fs.addFontsFrom(source.fs); 7824 } 7825 7826 /// Finds a loaded font of specified name, and returns handle to it, or FONS_INVALID (aka -1) if the font is not found. 7827 /// Group: text_api 7828 public int findFont (NVGContext ctx, const(char)[] name) nothrow @trusted @nogc { 7829 pragma(inline, true); 7830 return (name.length == 0 ? FONS_INVALID : ctx.fs.getFontByName(name)); 7831 } 7832 7833 /// Sets the font size of current text style. 7834 /// Group: text_api 7835 public void fontSize (NVGContext ctx, float size) nothrow @trusted @nogc { 7836 pragma(inline, true); 7837 nvg__getState(ctx).fontSize = size; 7838 } 7839 7840 /// Gets the font size of current text style. 7841 /// Group: text_api 7842 public float fontSize (NVGContext ctx) nothrow @trusted @nogc { 7843 pragma(inline, true); 7844 return nvg__getState(ctx).fontSize; 7845 } 7846 7847 /// Sets the blur of current text style. 7848 /// Group: text_api 7849 public void fontBlur (NVGContext ctx, float blur) nothrow @trusted @nogc { 7850 pragma(inline, true); 7851 nvg__getState(ctx).fontBlur = blur; 7852 } 7853 7854 /// Gets the blur of current text style. 7855 /// Group: text_api 7856 public float fontBlur (NVGContext ctx) nothrow @trusted @nogc { 7857 pragma(inline, true); 7858 return nvg__getState(ctx).fontBlur; 7859 } 7860 7861 /// Sets the letter spacing of current text style. 7862 /// Group: text_api 7863 public void textLetterSpacing (NVGContext ctx, float spacing) nothrow @trusted @nogc { 7864 pragma(inline, true); 7865 nvg__getState(ctx).letterSpacing = spacing; 7866 } 7867 7868 /// Gets the letter spacing of current text style. 7869 /// Group: text_api 7870 public float textLetterSpacing (NVGContext ctx) nothrow @trusted @nogc { 7871 pragma(inline, true); 7872 return nvg__getState(ctx).letterSpacing; 7873 } 7874 7875 /// Sets the proportional line height of current text style. The line height is specified as multiple of font size. 7876 /// Group: text_api 7877 public void textLineHeight (NVGContext ctx, float lineHeight) nothrow @trusted @nogc { 7878 pragma(inline, true); 7879 nvg__getState(ctx).lineHeight = lineHeight; 7880 } 7881 7882 /// Gets the proportional line height of current text style. The line height is specified as multiple of font size. 7883 /// Group: text_api 7884 public float textLineHeight (NVGContext ctx) nothrow @trusted @nogc { 7885 pragma(inline, true); 7886 return nvg__getState(ctx).lineHeight; 7887 } 7888 7889 /// Sets the text align of current text style, see [NVGTextAlign] for options. 7890 /// Group: text_api 7891 public void textAlign (NVGContext ctx, NVGTextAlign talign) nothrow @trusted @nogc { 7892 pragma(inline, true); 7893 nvg__getState(ctx).textAlign = talign; 7894 } 7895 7896 /// Ditto. 7897 public void textAlign (NVGContext ctx, NVGTextAlign.H h) nothrow @trusted @nogc { 7898 pragma(inline, true); 7899 nvg__getState(ctx).textAlign.horizontal = h; 7900 } 7901 7902 /// Ditto. 7903 public void textAlign (NVGContext ctx, NVGTextAlign.V v) nothrow @trusted @nogc { 7904 pragma(inline, true); 7905 nvg__getState(ctx).textAlign.vertical = v; 7906 } 7907 7908 /// Ditto. 7909 public void textAlign (NVGContext ctx, NVGTextAlign.H h, NVGTextAlign.V v) nothrow @trusted @nogc { 7910 pragma(inline, true); 7911 nvg__getState(ctx).textAlign.reset(h, v); 7912 } 7913 7914 /// Ditto. 7915 public void textAlign (NVGContext ctx, NVGTextAlign.V v, NVGTextAlign.H h) nothrow @trusted @nogc { 7916 pragma(inline, true); 7917 nvg__getState(ctx).textAlign.reset(h, v); 7918 } 7919 7920 /// Gets the text align of current text style, see [NVGTextAlign] for options. 7921 /// Group: text_api 7922 public NVGTextAlign textAlign (NVGContext ctx) nothrow @trusted @nogc { 7923 pragma(inline, true); 7924 return nvg__getState(ctx).textAlign; 7925 } 7926 7927 /// Sets the font face based on specified id of current text style. 7928 /// Group: text_api 7929 public void fontFaceId (NVGContext ctx, int font) nothrow @trusted @nogc { 7930 pragma(inline, true); 7931 nvg__getState(ctx).fontId = font; 7932 } 7933 7934 /// Gets the font face based on specified id of current text style. 7935 /// Group: text_api 7936 public int fontFaceId (NVGContext ctx) nothrow @trusted @nogc { 7937 pragma(inline, true); 7938 return nvg__getState(ctx).fontId; 7939 } 7940 7941 /** Sets the font face based on specified name of current text style. 7942 * 7943 * The underlying implementation is using O(1) data structure to lookup 7944 * font names, so you probably should use this function instead of [fontFaceId] 7945 * to make your code more robust and less error-prone. 7946 * 7947 * Group: text_api 7948 */ 7949 public void fontFace (NVGContext ctx, const(char)[] font) nothrow @trusted @nogc { 7950 pragma(inline, true); 7951 nvg__getState(ctx).fontId = ctx.fs.getFontByName(font); 7952 } 7953 7954 static if (is(typeof(&fons__nvg__toPath))) { 7955 public enum NanoVegaHasCharToPath = true; /// 7956 } else { 7957 public enum NanoVegaHasCharToPath = false; /// 7958 } 7959 7960 /// Adds glyph outlines to the current path. Vertical 0 is baseline. 7961 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead. 7962 /// Returns `false` if there is no such glyph, or current font is not scalable. 7963 /// Group: text_api 7964 public bool charToPath (NVGContext ctx, dchar dch, float[] bounds=null) nothrow @trusted @nogc { 7965 NVGstate* state = nvg__getState(ctx); 7966 ctx.fs.fontId = state.fontId; 7967 return ctx.fs.toPath(ctx, dch, bounds); 7968 } 7969 7970 static if (is(typeof(&fons__nvg__bounds))) { 7971 public enum NanoVegaHasCharPathBounds = true; /// 7972 } else { 7973 public enum NanoVegaHasCharPathBounds = false; /// 7974 } 7975 7976 /// Returns bounds of the glyph outlines. Vertical 0 is baseline. 7977 /// The glyph is not scaled in any way. 7978 /// Returns `false` if there is no such glyph, or current font is not scalable. 7979 /// Group: text_api 7980 public bool charPathBounds (NVGContext ctx, dchar dch, float[] bounds) nothrow @trusted @nogc { 7981 NVGstate* state = nvg__getState(ctx); 7982 ctx.fs.fontId = state.fontId; 7983 return ctx.fs.getPathBounds(dch, bounds); 7984 } 7985 7986 /** [charOutline] will return [NVGPathOutline]. 7987 7988 some usage samples: 7989 7990 --- 7991 float[4] bounds = void; 7992 7993 nvg.scale(0.5, 0.5); 7994 nvg.translate(500, 800); 7995 nvg.evenOddFill; 7996 7997 nvg.newPath(); 7998 nvg.charToPath('&', bounds[]); 7999 conwriteln(bounds[]); 8000 nvg.fillPaint(nvg.linearGradient(0, 0, 600, 600, NVGColor("#f70"), NVGColor("#ff0"))); 8001 nvg.strokeColor(NVGColor("#0f0")); 8002 nvg.strokeWidth = 3; 8003 nvg.fill(); 8004 nvg.stroke(); 8005 // glyph bounds 8006 nvg.newPath(); 8007 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); 8008 nvg.strokeColor(NVGColor("#00f")); 8009 nvg.stroke(); 8010 8011 nvg.newPath(); 8012 nvg.charToPath('g', bounds[]); 8013 conwriteln(bounds[]); 8014 nvg.fill(); 8015 nvg.strokeColor(NVGColor("#0f0")); 8016 nvg.stroke(); 8017 // glyph bounds 8018 nvg.newPath(); 8019 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); 8020 nvg.strokeColor(NVGColor("#00f")); 8021 nvg.stroke(); 8022 8023 nvg.newPath(); 8024 nvg.moveTo(0, 0); 8025 nvg.lineTo(600, 0); 8026 nvg.strokeColor(NVGColor("#0ff")); 8027 nvg.stroke(); 8028 8029 if (auto ol = nvg.charOutline('Q')) { 8030 scope(exit) ol.kill(); 8031 nvg.newPath(); 8032 conwriteln("==== length: ", ol.length, " ===="); 8033 foreach (const ref cmd; ol.commands) { 8034 //conwriteln(" ", cmd.code, ": ", cmd.args[]); 8035 assert(cmd.valid); 8036 final switch (cmd.code) { 8037 case cmd.Kind.MoveTo: nvg.moveTo(cmd.args[0], cmd.args[1]); break; 8038 case cmd.Kind.LineTo: nvg.lineTo(cmd.args[0], cmd.args[1]); break; 8039 case cmd.Kind.QuadTo: nvg.quadTo(cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]); break; 8040 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; 8041 } 8042 } 8043 nvg.strokeColor(NVGColor("#f00")); 8044 nvg.stroke(); 8045 } 8046 --- 8047 8048 Group: text_api 8049 */ 8050 public struct NVGPathOutline { 8051 private nothrow @trusted @nogc: 8052 struct DataStore { 8053 uint rc; // refcount 8054 ubyte* data; 8055 uint used; 8056 uint size; 8057 uint ccount; // number of commands 8058 float[4] bounds = 0; /// outline bounds 8059 nothrow @trusted @nogc: 8060 void putBytes (const(void)[] b) { 8061 if (b.length == 0) return; 8062 if (b.length >= int.max/8) assert(0, "NanoVega: out of memory"); 8063 if (int.max/8-used < b.length) assert(0, "NanoVega: out of memory"); 8064 if (used+cast(uint)b.length > size) { 8065 import core.stdc.stdlib : realloc; 8066 uint newsz = size; 8067 while (newsz < used+cast(uint)b.length) newsz = (newsz == 0 ? 1024 : newsz < 32768 ? newsz*2 : newsz+8192); 8068 assert(used+cast(uint)b.length <= newsz); 8069 data = cast(ubyte*)realloc(data, newsz); 8070 if (data is null) assert(0, "NanoVega: out of memory"); 8071 size = newsz; 8072 } 8073 import core.stdc.string : memcpy; 8074 memcpy(data+used, b.ptr, b.length); 8075 used += cast(uint)b.length; 8076 } 8077 void putCommand (ubyte cmd) { pragma(inline, true); ++ccount; putBytes((&cmd)[0..1]); } 8078 void putArgs (const(float)[] f...) { pragma(inline, true); putBytes(f[]); } 8079 } 8080 8081 static void incRef (DataStore* ds) { 8082 pragma(inline, true); 8083 if (ds !is null) { 8084 ++ds.rc; 8085 //{ import core.stdc.stdio; printf("ods(%p): incref: newrc=%u\n", ds, ds.rc); } 8086 } 8087 } 8088 8089 static void decRef (DataStore* ds) { 8090 version(aliced) pragma(inline, true); 8091 if (ds !is null) { 8092 //{ import core.stdc.stdio; printf("ods(%p): decref: newrc=%u\n", ds, ds.rc-1); } 8093 if (--ds.rc == 0) { 8094 import core.stdc.stdlib : free; 8095 import core.stdc.string : memset; 8096 if (ds.data !is null) free(ds.data); 8097 memset(ds, 0, DataStore.sizeof); // just in case 8098 free(ds); 8099 //{ import core.stdc.stdio; printf(" ods(%p): killed.\n"); } 8100 } 8101 } 8102 } 8103 8104 private: 8105 static NVGPathOutline createNew () { 8106 import core.stdc.stdlib : malloc; 8107 import core.stdc.string : memset; 8108 auto ds = cast(DataStore*)malloc(DataStore.sizeof); 8109 if (ds is null) assert(0, "NanoVega: out of memory"); 8110 memset(ds, 0, DataStore.sizeof); 8111 ds.rc = 1; 8112 NVGPathOutline res; 8113 res.dsaddr = cast(usize)ds; 8114 return res; 8115 } 8116 8117 private: 8118 usize dsaddr; // fool GC 8119 8120 @property inout(DataStore)* ds () inout pure { pragma(inline, true); return cast(DataStore*)dsaddr; } 8121 8122 public: 8123 /// commands 8124 static struct Command { 8125 /// 8126 enum Kind : ubyte { 8127 MoveTo, /// 8128 LineTo, /// 8129 QuadTo, /// 8130 BezierTo, /// 8131 End, /// no more commands (this command is not `valid`!) 8132 8133 } 8134 Kind code; /// 8135 const(float)[] args; /// 8136 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (code >= Kind.min && code < Kind.End && args.length >= 2); } /// 8137 8138 static uint arglen (Kind code) pure nothrow @safe @nogc { 8139 pragma(inline, true); 8140 return 8141 code == Kind.MoveTo || code == Kind.LineTo ? 2 : 8142 code == Kind.QuadTo ? 4 : 8143 code == Kind.BezierTo ? 6 : 8144 0; 8145 } 8146 8147 /// perform NanoVega command with stored data. 8148 void perform (NVGContext ctx) const nothrow @trusted @nogc { 8149 if (ctx is null) return; 8150 final switch (code) { 8151 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(args.ptr[0..2]); break; 8152 case Kind.LineTo: if (args.length > 1) ctx.lineTo(args.ptr[0..2]); break; 8153 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(args.ptr[0..4]); break; 8154 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(args.ptr[0..6]); break; 8155 case Kind.End: break; 8156 } 8157 } 8158 8159 /// perform NanoVega command with stored data, transforming points with [xform] transformation matrix. 8160 void perform() (NVGContext ctx, in auto ref NVGMatrix xform) const nothrow @trusted @nogc { 8161 if (ctx is null || !valid) return; 8162 float[6] pts = void; 8163 pts[0..args.length] = args[]; 8164 foreach (immutable pidx; 0..args.length/2) xform.point(pts.ptr[pidx*2+0], pts.ptr[pidx*2+1]); 8165 final switch (code) { 8166 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(pts.ptr[0..2]); break; 8167 case Kind.LineTo: if (args.length > 1) ctx.lineTo(pts.ptr[0..2]); break; 8168 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(pts.ptr[0..4]); break; 8169 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(pts.ptr[0..6]); break; 8170 case Kind.End: break; 8171 } 8172 } 8173 } 8174 8175 public: 8176 /// Create new path with quadratic bezier (first command is MoveTo, second command is QuadTo). 8177 static NVGPathOutline createNewQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) { 8178 auto res = createNew(); 8179 res.ds.putCommand(Command.Kind.MoveTo); 8180 res.ds.putArgs(x0, y0); 8181 res.ds.putCommand(Command.Kind.QuadTo); 8182 res.ds.putArgs(cx, cy, x, y); 8183 return res; 8184 } 8185 8186 /// Create new path with cubic bezier (first command is MoveTo, second command is BezierTo). 8187 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) { 8188 auto res = createNew(); 8189 res.ds.putCommand(Command.Kind.MoveTo); 8190 res.ds.putArgs(x1, y1); 8191 res.ds.putCommand(Command.Kind.BezierTo); 8192 res.ds.putArgs(x2, y2, x3, y3, x4, y4); 8193 return res; 8194 } 8195 8196 public: 8197 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); } 8198 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); } 8199 8200 void opAssign() (in auto ref NVGPathOutline a) { 8201 incRef(cast(DataStore*)a.dsaddr); 8202 decRef(cast(DataStore*)dsaddr); 8203 dsaddr = a.dsaddr; 8204 } 8205 8206 /// Clear storage. 8207 void clear () { 8208 pragma(inline, true); 8209 decRef(ds); 8210 dsaddr = 0; 8211 } 8212 8213 /// Is this outline empty? 8214 @property empty () const pure { pragma(inline, true); return (dsaddr == 0 || ds.ccount == 0); } 8215 8216 /// Returns number of commands in outline. 8217 @property int length () const pure { pragma(inline, true); return (dsaddr ? ds.ccount : 0); } 8218 8219 /// Returns "flattened" path. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8220 NVGPathOutline flatten () const { pragma(inline, true); return flattenInternal(null); } 8221 8222 /// Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8223 NVGPathOutline flatten() (in auto ref NVGMatrix mt) const { pragma(inline, true); return flattenInternal(&mt); } 8224 8225 // Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8226 private NVGPathOutline flattenInternal (scope NVGMatrix* tfm) const { 8227 import core.stdc.string : memset; 8228 8229 NVGPathOutline res; 8230 if (dsaddr == 0 || ds.ccount == 0) { res = this; return res; } // nothing to do 8231 8232 // check if we need to flatten the path 8233 if (tfm is null) { 8234 bool dowork = false; 8235 foreach (const ref cs; commands) { 8236 if (cs.code != Command.Kind.MoveTo && cs.code != Command.Kind.LineTo) { 8237 dowork = true; 8238 break; 8239 } 8240 } 8241 if (!dowork) { res = this; return res; } // nothing to do 8242 } 8243 8244 NVGcontextinternal ctx; 8245 memset(&ctx, 0, ctx.sizeof); 8246 ctx.cache = nvg__allocPathCache(); 8247 scope(exit) { 8248 import core.stdc.stdlib : free; 8249 nvg__deletePathCache(ctx.cache); 8250 } 8251 8252 ctx.tessTol = 0.25f; 8253 ctx.angleTol = 0; // 0 -- angle tolerance for McSeem Bezier rasterizer 8254 ctx.cuspLimit = 0; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps) 8255 ctx.distTol = 0.01f; 8256 ctx.tesselatortype = NVGTesselation.DeCasteljau; 8257 8258 nvg__addPath(&ctx); // we need this for `nvg__addPoint()` 8259 8260 // has some curves or transformations, convert path 8261 res = createNew(); 8262 float[8] args = void; 8263 8264 res.ds.bounds = [float.max, float.max, -float.max, -float.max]; 8265 8266 float lastX = float.max, lastY = float.max; 8267 bool lastWasMove = false; 8268 8269 void addPoint (float x, float y, Command.Kind cmd=Command.Kind.LineTo) nothrow @trusted @nogc { 8270 if (tfm !is null) tfm.point(x, y); 8271 bool isMove = (cmd == Command.Kind.MoveTo); 8272 if (isMove) { 8273 // moveto 8274 if (lastWasMove && nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return; 8275 } else { 8276 // lineto 8277 if (nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return; 8278 } 8279 lastWasMove = isMove; 8280 lastX = x; 8281 lastY = y; 8282 res.ds.putCommand(cmd); 8283 res.ds.putArgs(x, y); 8284 res.ds.bounds.ptr[0] = nvg__min(res.ds.bounds.ptr[0], x); 8285 res.ds.bounds.ptr[1] = nvg__min(res.ds.bounds.ptr[1], y); 8286 res.ds.bounds.ptr[2] = nvg__max(res.ds.bounds.ptr[2], x); 8287 res.ds.bounds.ptr[3] = nvg__max(res.ds.bounds.ptr[3], y); 8288 } 8289 8290 // sorry for this pasta 8291 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 { 8292 ctx.cache.npoints = 0; 8293 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) { 8294 nvg__tesselateBezier(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner); 8295 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) { 8296 nvg__tesselateBezierMcSeem(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner); 8297 } else { 8298 nvg__tesselateBezierAFD(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, PointFlag.Corner); 8299 } 8300 // add generated points 8301 foreach (const ref pt; ctx.cache.points[0..ctx.cache.npoints]) addPoint(pt.x, pt.y); 8302 } 8303 8304 void flattenQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) { 8305 flattenBezier( 8306 x0, y0, 8307 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 8308 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 8309 x, y, 8310 0, 8311 ); 8312 } 8313 8314 float cx = 0, cy = 0; 8315 foreach (const ref cs; commands) { 8316 switch (cs.code) { 8317 case Command.Kind.LineTo: 8318 case Command.Kind.MoveTo: 8319 addPoint(cs.args[0], cs.args[1], cs.code); 8320 cx = cs.args[0]; 8321 cy = cs.args[1]; 8322 break; 8323 case Command.Kind.QuadTo: 8324 flattenQuad(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3]); 8325 cx = cs.args[2]; 8326 cy = cs.args[3]; 8327 break; 8328 case Command.Kind.BezierTo: 8329 flattenBezier(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3], cs.args[4], cs.args[5], 0); 8330 cx = cs.args[4]; 8331 cy = cs.args[5]; 8332 break; 8333 default: 8334 break; 8335 } 8336 } 8337 8338 return res; 8339 } 8340 8341 /// Returns forward range with all glyph commands. 8342 auto commands () const nothrow @trusted @nogc { 8343 static struct Range { 8344 private nothrow @trusted @nogc: 8345 usize dsaddr; 8346 uint cpos; // current position in data 8347 uint cleft; // number of commands left 8348 @property const(ubyte)* data () inout pure { pragma(inline, true); return (dsaddr ? (cast(DataStore*)dsaddr).data : null); } 8349 public: 8350 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); } 8351 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); } 8352 void opAssign() (in auto ref Range a) { 8353 incRef(cast(DataStore*)a.dsaddr); 8354 decRef(cast(DataStore*)dsaddr); 8355 dsaddr = a.dsaddr; 8356 cpos = a.cpos; 8357 cleft = a.cleft; 8358 } 8359 float[4] bounds () const pure { float[4] res = 0; pragma(inline, true); if (dsaddr) res[] = (cast(DataStore*)dsaddr).bounds[]; return res; } /// outline bounds 8360 @property bool empty () const pure { pragma(inline, true); return (cleft == 0); } 8361 @property int length () const pure { pragma(inline, true); return cleft; } 8362 @property Range save () const { pragma(inline, true); Range res = this; return res; } 8363 @property Command front () const { 8364 Command res = void; 8365 if (cleft > 0) { 8366 res.code = cast(Command.Kind)data[cpos]; 8367 switch (res.code) { 8368 case Command.Kind.MoveTo: 8369 case Command.Kind.LineTo: 8370 res.args = (cast(const(float*))(data+cpos+1))[0..1*2]; 8371 break; 8372 case Command.Kind.QuadTo: 8373 res.args = (cast(const(float*))(data+cpos+1))[0..2*2]; 8374 break; 8375 case Command.Kind.BezierTo: 8376 res.args = (cast(const(float*))(data+cpos+1))[0..3*2]; 8377 break; 8378 default: 8379 res.code = Command.Kind.End; 8380 res.args = null; 8381 break; 8382 } 8383 } else { 8384 res.code = Command.Kind.End; 8385 res.args = null; 8386 } 8387 return res; 8388 } 8389 void popFront () { 8390 if (cleft <= 1) { cleft = 0; return; } // don't waste time skipping last command 8391 --cleft; 8392 switch (data[cpos]) { 8393 case Command.Kind.MoveTo: 8394 case Command.Kind.LineTo: 8395 cpos += 1+1*2*cast(uint)float.sizeof; 8396 break; 8397 case Command.Kind.QuadTo: 8398 cpos += 1+2*2*cast(uint)float.sizeof; 8399 break; 8400 case Command.Kind.BezierTo: 8401 cpos += 1+3*2*cast(uint)float.sizeof; 8402 break; 8403 default: 8404 cleft = 0; 8405 break; 8406 } 8407 } 8408 } 8409 if (dsaddr) { 8410 incRef(cast(DataStore*)dsaddr); // range anchors it 8411 return Range(dsaddr, 0, ds.ccount); 8412 } else { 8413 return Range.init; 8414 } 8415 } 8416 } 8417 8418 public alias NVGGlyphOutline = NVGPathOutline; /// For backwards compatibility. 8419 8420 /// Destroy glyph outiline and free allocated memory. 8421 /// Group: text_api 8422 public void kill (ref NVGPathOutline ol) nothrow @trusted @nogc { 8423 pragma(inline, true); 8424 ol.clear(); 8425 } 8426 8427 static if (is(typeof(&fons__nvg__toOutline))) { 8428 public enum NanoVegaHasCharOutline = true; /// 8429 } else { 8430 public enum NanoVegaHasCharOutline = false; /// 8431 } 8432 8433 /// Returns glyph outlines as array of commands. Vertical 0 is baseline. 8434 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead. 8435 /// Returns `null` if there is no such glyph, or current font is not scalable. 8436 /// Group: text_api 8437 public NVGPathOutline charOutline (NVGContext ctx, dchar dch) nothrow @trusted @nogc { 8438 import core.stdc.stdlib : malloc; 8439 import core.stdc.string : memcpy; 8440 NVGstate* state = nvg__getState(ctx); 8441 ctx.fs.fontId = state.fontId; 8442 auto oline = NVGPathOutline.createNew(); 8443 if (!ctx.fs.toOutline(dch, oline.ds)) oline.clear(); 8444 return oline; 8445 } 8446 8447 8448 float nvg__quantize (float a, float d) pure nothrow @safe @nogc { 8449 pragma(inline, true); 8450 return (cast(int)(a/d+0.5f))*d; 8451 } 8452 8453 float nvg__getFontScale (NVGstate* state) /*pure*/ nothrow @safe @nogc { 8454 pragma(inline, true); 8455 return nvg__min(nvg__quantize(nvg__getAverageScale(state.xform), 0.01f), 4.0f); 8456 } 8457 8458 void nvg__flushTextTexture (NVGContext ctx) nothrow @trusted @nogc { 8459 int[4] dirty = void; 8460 if (ctx.fs.validateTexture(dirty.ptr)) { 8461 auto fontImage = &ctx.fontImages[ctx.fontImageIdx]; 8462 // Update texture 8463 if (fontImage.valid) { 8464 int iw, ih; 8465 const(ubyte)* data = ctx.fs.getTextureData(&iw, &ih); 8466 int x = dirty[0]; 8467 int y = dirty[1]; 8468 int w = dirty[2]-dirty[0]; 8469 int h = dirty[3]-dirty[1]; 8470 ctx.params.renderUpdateTexture(ctx.params.userPtr, fontImage.id, x, y, w, h, data); 8471 } 8472 } 8473 } 8474 8475 bool nvg__allocTextAtlas (NVGContext ctx) nothrow @trusted @nogc { 8476 int iw, ih; 8477 nvg__flushTextTexture(ctx); 8478 if (ctx.fontImageIdx >= NVG_MAX_FONTIMAGES-1) return false; 8479 // if next fontImage already have a texture 8480 if (ctx.fontImages[ctx.fontImageIdx+1].valid) { 8481 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx+1], iw, ih); 8482 } else { 8483 // calculate the new font image size and create it 8484 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx], iw, ih); 8485 if (iw > ih) ih *= 2; else iw *= 2; 8486 if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE; 8487 ctx.fontImages[ctx.fontImageIdx+1].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, iw, ih, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); 8488 if (ctx.fontImages[ctx.fontImageIdx+1].id > 0) { 8489 ctx.fontImages[ctx.fontImageIdx+1].ctx = ctx; 8490 ctx.nvg__imageIncRef(ctx.fontImages[ctx.fontImageIdx+1].id, false); // don't increment driver refcount 8491 } 8492 } 8493 ++ctx.fontImageIdx; 8494 ctx.fs.resetAtlas(iw, ih); 8495 return true; 8496 } 8497 8498 void nvg__renderText (NVGContext ctx, NVGVertex* verts, int nverts) nothrow @trusted @nogc { 8499 NVGstate* state = nvg__getState(ctx); 8500 NVGPaint paint = state.fill; 8501 8502 // Render triangles. 8503 paint.image = ctx.fontImages[ctx.fontImageIdx]; 8504 8505 // Apply global alpha 8506 paint.innerColor.a *= state.alpha; 8507 paint.middleColor.a *= state.alpha; 8508 paint.outerColor.a *= state.alpha; 8509 8510 ctx.params.renderTriangles(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &paint, &state.scissor, verts, nverts); 8511 8512 ++ctx.drawCallCount; 8513 ctx.textTriCount += nverts/3; 8514 } 8515 8516 /// Draws text string at specified location. Returns next x position. 8517 /// Group: text_api 8518 public float text(T) (NVGContext ctx, float x, float y, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 8519 NVGstate* state = nvg__getState(ctx); 8520 FONSTextIter!T iter, prevIter; 8521 FONSQuad q; 8522 NVGVertex* verts; 8523 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8524 float invscale = 1.0f/scale; 8525 int cverts = 0; 8526 int nverts = 0; 8527 8528 if (state.fontId == FONS_INVALID) return x; 8529 if (str.length == 0) return x; 8530 8531 ctx.fs.size = state.fontSize*scale; 8532 ctx.fs.spacing = state.letterSpacing*scale; 8533 ctx.fs.blur = state.fontBlur*scale; 8534 ctx.fs.textAlign = state.textAlign; 8535 ctx.fs.fontId = state.fontId; 8536 8537 cverts = nvg__max(2, cast(int)(str.length))*6; // conservative estimate 8538 verts = nvg__allocTempVerts(ctx, cverts); 8539 if (verts is null) return x; 8540 8541 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Required)) return x; 8542 prevIter = iter; 8543 while (iter.next(q)) { 8544 float[4*2] c = void; 8545 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8546 if (nverts != 0) { 8547 // TODO: add back-end bit to do this just once per frame 8548 nvg__flushTextTexture(ctx); 8549 nvg__renderText(ctx, verts, nverts); 8550 nverts = 0; 8551 } 8552 if (!nvg__allocTextAtlas(ctx)) break; // no memory :( 8553 iter = prevIter; 8554 iter.next(q); // try again 8555 if (iter.prevGlyphIndex < 0) { 8556 // still can not find glyph, try replacement 8557 iter = prevIter; 8558 if (!iter.getDummyChar(q)) break; 8559 } 8560 } 8561 prevIter = iter; 8562 // transform corners 8563 state.xform.point(&c[0], &c[1], q.x0*invscale, q.y0*invscale); 8564 state.xform.point(&c[2], &c[3], q.x1*invscale, q.y0*invscale); 8565 state.xform.point(&c[4], &c[5], q.x1*invscale, q.y1*invscale); 8566 state.xform.point(&c[6], &c[7], q.x0*invscale, q.y1*invscale); 8567 // create triangles 8568 if (nverts+6 <= cverts) { 8569 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts; 8570 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts; 8571 nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); ++nverts; 8572 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts; 8573 nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); ++nverts; 8574 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts; 8575 } 8576 } 8577 8578 // TODO: add back-end bit to do this just once per frame 8579 if (nverts > 0) { 8580 nvg__flushTextTexture(ctx); 8581 nvg__renderText(ctx, verts, nverts); 8582 } 8583 8584 return iter.nextx/scale; 8585 } 8586 8587 /** Draws multi-line text string at specified location wrapped at the specified width. 8588 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8589 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8590 * 8591 * Group: text_api 8592 */ 8593 public void textBox(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 8594 NVGstate* state = nvg__getState(ctx); 8595 if (state.fontId == FONS_INVALID) return; 8596 8597 NVGTextRow!T[2] rows; 8598 auto oldAlign = state.textAlign; 8599 scope(exit) state.textAlign = oldAlign; 8600 auto halign = state.textAlign.horizontal; 8601 float lineh = 0; 8602 8603 ctx.textMetrics(null, null, &lineh); 8604 state.textAlign.horizontal = NVGTextAlign.H.Left; 8605 for (;;) { 8606 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]); 8607 //{ import core.stdc.stdio : printf; printf("slen=%u; rlen=%u; bw=%f\n", cast(uint)str.length, cast(uint)rres.length, cast(double)breakRowWidth); } 8608 if (rres.length == 0) break; 8609 foreach (ref row; rres) { 8610 final switch (halign) { 8611 case NVGTextAlign.H.Left: ctx.text(x, y, row.row); break; 8612 case NVGTextAlign.H.Center: ctx.text(x+breakRowWidth*0.5f-row.width*0.5f, y, row.row); break; 8613 case NVGTextAlign.H.Right: ctx.text(x+breakRowWidth-row.width, y, row.row); break; 8614 } 8615 y += lineh*state.lineHeight; 8616 } 8617 str = rres[$-1].rest; 8618 } 8619 } 8620 8621 private template isGoodPositionDelegate(DG) { 8622 private DG dg; 8623 static if (is(typeof({ NVGGlyphPosition pos; bool res = dg(pos); })) || 8624 is(typeof({ NVGGlyphPosition pos; dg(pos); }))) 8625 enum isGoodPositionDelegate = true; 8626 else 8627 enum isGoodPositionDelegate = false; 8628 } 8629 8630 /** Calculates the glyph x positions of the specified text. 8631 * Measured values are returned in local coordinate space. 8632 * 8633 * Group: text_api 8634 */ 8635 public NVGGlyphPosition[] textGlyphPositions(T) (NVGContext ctx, float x, float y, const(T)[] str, NVGGlyphPosition[] positions) nothrow @trusted @nogc 8636 if (isAnyCharType!T) 8637 { 8638 if (str.length == 0 || positions.length == 0) return positions[0..0]; 8639 usize posnum; 8640 auto len = ctx.textGlyphPositions(x, y, str, (in ref NVGGlyphPosition pos) { 8641 positions.ptr[posnum++] = pos; 8642 return (posnum < positions.length); 8643 }); 8644 return positions[0..len]; 8645 } 8646 8647 /// Ditto. 8648 public int textGlyphPositions(T, DG) (NVGContext ctx, float x, float y, const(T)[] str, scope DG dg) 8649 if (isAnyCharType!T && isGoodPositionDelegate!DG) 8650 { 8651 import std.traits : ReturnType; 8652 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true; 8653 8654 NVGstate* state = nvg__getState(ctx); 8655 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8656 float invscale = 1.0f/scale; 8657 FONSTextIter!T iter, prevIter; 8658 FONSQuad q; 8659 int npos = 0; 8660 8661 if (str.length == 0) return 0; 8662 8663 ctx.fs.size = state.fontSize*scale; 8664 ctx.fs.spacing = state.letterSpacing*scale; 8665 ctx.fs.blur = state.fontBlur*scale; 8666 ctx.fs.textAlign = state.textAlign; 8667 ctx.fs.fontId = state.fontId; 8668 8669 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Optional)) return npos; 8670 prevIter = iter; 8671 while (iter.next(q)) { 8672 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8673 if (!nvg__allocTextAtlas(ctx)) break; // no memory 8674 iter = prevIter; 8675 iter.next(q); // try again 8676 if (iter.prevGlyphIndex < 0) { 8677 // still can not find glyph, try replacement 8678 iter = prevIter; 8679 if (!iter.getDummyChar(q)) break; 8680 } 8681 } 8682 prevIter = iter; 8683 NVGGlyphPosition position = void; //WARNING! 8684 position.strpos = cast(usize)(iter.stringp-str.ptr); 8685 position.x = iter.x*invscale; 8686 position.minx = nvg__min(iter.x, q.x0)*invscale; 8687 position.maxx = nvg__max(iter.nextx, q.x1)*invscale; 8688 ++npos; 8689 static if (RetBool) { if (!dg(position)) return npos; } else dg(position); 8690 } 8691 8692 return npos; 8693 } 8694 8695 private template isGoodRowDelegate(CT, DG) { 8696 private DG dg; 8697 static if (is(typeof({ NVGTextRow!CT row; bool res = dg(row); })) || 8698 is(typeof({ NVGTextRow!CT row; dg(row); }))) 8699 enum isGoodRowDelegate = true; 8700 else 8701 enum isGoodRowDelegate = false; 8702 } 8703 8704 /** Breaks the specified text into lines. 8705 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8706 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8707 * 8708 * Group: text_api 8709 */ 8710 public NVGTextRow!T[] textBreakLines(T) (NVGContext ctx, const(T)[] str, float breakRowWidth, NVGTextRow!T[] rows) nothrow @trusted @nogc 8711 if (isAnyCharType!T) 8712 { 8713 if (rows.length == 0) return rows; 8714 if (rows.length > int.max-1) rows = rows[0..int.max-1]; 8715 int nrow = 0; 8716 auto count = ctx.textBreakLines(str, breakRowWidth, (in ref NVGTextRow!T row) { 8717 rows[nrow++] = row; 8718 return (nrow < rows.length); 8719 }); 8720 return rows[0..count]; 8721 } 8722 8723 /** Breaks the specified text into lines. 8724 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8725 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8726 * Returns number of rows. 8727 * 8728 * Group: text_api 8729 */ 8730 public int textBreakLines(T, DG) (NVGContext ctx, const(T)[] str, float breakRowWidth, scope DG dg) 8731 if (isAnyCharType!T && isGoodRowDelegate!(T, DG)) 8732 { 8733 import std.traits : ReturnType; 8734 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true; 8735 8736 enum NVGcodepointType : int { 8737 Space, 8738 NewLine, 8739 Char, 8740 } 8741 8742 NVGstate* state = nvg__getState(ctx); 8743 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8744 float invscale = 1.0f/scale; 8745 FONSTextIter!T iter, prevIter; 8746 FONSQuad q; 8747 int nrows = 0; 8748 float rowStartX = 0; 8749 float rowWidth = 0; 8750 float rowMinX = 0; 8751 float rowMaxX = 0; 8752 int rowStart = 0; 8753 int rowEnd = 0; 8754 int wordStart = 0; 8755 float wordStartX = 0; 8756 float wordMinX = 0; 8757 int breakEnd = 0; 8758 float breakWidth = 0; 8759 float breakMaxX = 0; 8760 int type = NVGcodepointType.Space, ptype = NVGcodepointType.Space; 8761 uint pcodepoint = 0; 8762 8763 if (state.fontId == FONS_INVALID) return 0; 8764 if (str.length == 0 || dg is null) return 0; 8765 8766 ctx.fs.size = state.fontSize*scale; 8767 ctx.fs.spacing = state.letterSpacing*scale; 8768 ctx.fs.blur = state.fontBlur*scale; 8769 ctx.fs.textAlign = state.textAlign; 8770 ctx.fs.fontId = state.fontId; 8771 8772 breakRowWidth *= scale; 8773 8774 enum Phase { 8775 Normal, // searching for breaking point 8776 SkipBlanks, // skip leading blanks 8777 } 8778 Phase phase = Phase.SkipBlanks; // don't skip blanks on first line 8779 8780 if (!iter.setup(ctx.fs, 0, 0, str, FONSBitmapFlag.Optional)) return 0; 8781 prevIter = iter; 8782 while (iter.next(q)) { 8783 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8784 if (!nvg__allocTextAtlas(ctx)) break; // no memory 8785 iter = prevIter; 8786 iter.next(q); // try again 8787 if (iter.prevGlyphIndex < 0) { 8788 // still can not find glyph, try replacement 8789 iter = prevIter; 8790 if (!iter.getDummyChar(q)) break; 8791 } 8792 } 8793 prevIter = iter; 8794 switch (iter.codepoint) { 8795 case 9: // \t 8796 case 11: // \v 8797 case 12: // \f 8798 case 32: // space 8799 case 0x00a0: // NBSP 8800 type = NVGcodepointType.Space; 8801 break; 8802 case 10: // \n 8803 type = (pcodepoint == 13 ? NVGcodepointType.Space : NVGcodepointType.NewLine); 8804 break; 8805 case 13: // \r 8806 type = (pcodepoint == 10 ? NVGcodepointType.Space : NVGcodepointType.NewLine); 8807 break; 8808 case 0x0085: // NEL 8809 case 0x2028: // Line Separator 8810 case 0x2029: // Paragraph Separator 8811 type = NVGcodepointType.NewLine; 8812 break; 8813 default: 8814 type = NVGcodepointType.Char; 8815 break; 8816 } 8817 if (phase == Phase.SkipBlanks) { 8818 // fix row start 8819 rowStart = cast(int)(iter.stringp-str.ptr); 8820 rowEnd = rowStart; 8821 rowStartX = iter.x; 8822 rowWidth = iter.nextx-rowStartX; // q.x1-rowStartX; 8823 rowMinX = q.x0-rowStartX; 8824 rowMaxX = q.x1-rowStartX; 8825 wordStart = rowStart; 8826 wordStartX = iter.x; 8827 wordMinX = q.x0-rowStartX; 8828 breakEnd = rowStart; 8829 breakWidth = 0.0; 8830 breakMaxX = 0.0; 8831 if (type == NVGcodepointType.Space) continue; 8832 phase = Phase.Normal; 8833 } 8834 8835 if (type == NVGcodepointType.NewLine) { 8836 // always handle new lines 8837 NVGTextRow!T row; 8838 row.string = str; 8839 row.start = rowStart; 8840 row.end = rowEnd; 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 phase = Phase.SkipBlanks; 8847 } else { 8848 float nextWidth = iter.nextx-rowStartX; 8849 // track last non-white space character 8850 if (type == NVGcodepointType.Char) { 8851 rowEnd = cast(int)(iter.nextp-str.ptr); 8852 rowWidth = iter.nextx-rowStartX; 8853 rowMaxX = q.x1-rowStartX; 8854 } 8855 // track last end of a word 8856 if (ptype == NVGcodepointType.Char && type == NVGcodepointType.Space) { 8857 breakEnd = cast(int)(iter.stringp-str.ptr); 8858 breakWidth = rowWidth; 8859 breakMaxX = rowMaxX; 8860 } 8861 // track last beginning of a word 8862 if (ptype == NVGcodepointType.Space && type == NVGcodepointType.Char) { 8863 wordStart = cast(int)(iter.stringp-str.ptr); 8864 wordStartX = iter.x; 8865 wordMinX = q.x0-rowStartX; 8866 } 8867 // break to new line when a character is beyond break width 8868 if (type == NVGcodepointType.Char && nextWidth > breakRowWidth) { 8869 // the run length is too long, need to break to new line 8870 NVGTextRow!T row; 8871 row.string = str; 8872 if (breakEnd == rowStart) { 8873 // the current word is longer than the row length, just break it from here 8874 row.start = rowStart; 8875 row.end = cast(int)(iter.stringp-str.ptr); 8876 row.width = rowWidth*invscale; 8877 row.minx = rowMinX*invscale; 8878 row.maxx = rowMaxX*invscale; 8879 ++nrows; 8880 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8881 rowStartX = iter.x; 8882 rowStart = cast(int)(iter.stringp-str.ptr); 8883 rowEnd = cast(int)(iter.nextp-str.ptr); 8884 rowWidth = iter.nextx-rowStartX; 8885 rowMinX = q.x0-rowStartX; 8886 rowMaxX = q.x1-rowStartX; 8887 wordStart = rowStart; 8888 wordStartX = iter.x; 8889 wordMinX = q.x0-rowStartX; 8890 } else { 8891 // break the line from the end of the last word, and start new line from the beginning of the new 8892 //{ import core.stdc.stdio : printf; printf("rowStart=%u; rowEnd=%u; breakEnd=%u; len=%u\n", rowStart, rowEnd, breakEnd, cast(uint)str.length); } 8893 row.start = rowStart; 8894 row.end = breakEnd; 8895 row.width = breakWidth*invscale; 8896 row.minx = rowMinX*invscale; 8897 row.maxx = breakMaxX*invscale; 8898 ++nrows; 8899 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8900 rowStartX = wordStartX; 8901 rowStart = wordStart; 8902 rowEnd = cast(int)(iter.nextp-str.ptr); 8903 rowWidth = iter.nextx-rowStartX; 8904 rowMinX = wordMinX; 8905 rowMaxX = q.x1-rowStartX; 8906 // no change to the word start 8907 } 8908 // set null break point 8909 breakEnd = rowStart; 8910 breakWidth = 0.0; 8911 breakMaxX = 0.0; 8912 } 8913 } 8914 8915 pcodepoint = iter.codepoint; 8916 ptype = type; 8917 } 8918 8919 // break the line from the end of the last word, and start new line from the beginning of the new 8920 if (phase != Phase.SkipBlanks && rowStart < str.length) { 8921 //{ import core.stdc.stdio : printf; printf(" rowStart=%u; len=%u\n", rowStart, cast(uint)str.length); } 8922 NVGTextRow!T row; 8923 row.string = str; 8924 row.start = rowStart; 8925 row.end = cast(int)str.length; 8926 row.width = rowWidth*invscale; 8927 row.minx = rowMinX*invscale; 8928 row.maxx = rowMaxX*invscale; 8929 ++nrows; 8930 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8931 } 8932 8933 return nrows; 8934 } 8935 8936 /** Returns iterator which you can use to calculate text bounds and advancement. 8937 * This is usable when you need to do some text layouting with wrapping, to avoid 8938 * guesswork ("will advancement for this space stay the same?"), and Schlemiel's 8939 * algorithm. Note that you can copy the returned struct to save iterator state. 8940 * 8941 * You can check if iterator is valid with [valid] property, put new chars with 8942 * [put] method, get current advance with [advance] property, and current 8943 * bounds with `getBounds(ref float[4] bounds)` method. 8944 * 8945 * $(WARNING Don't change font parameters while iterating! Or use [restoreFont] method.) 8946 * 8947 * Group: text_api 8948 */ 8949 public struct TextBoundsIterator { 8950 private: 8951 NVGContext ctx; 8952 FONSTextBoundsIterator fsiter; // fontstash iterator 8953 float scale, invscale, xscaled, yscaled; 8954 // font settings 8955 float fsSize, fsSpacing, fsBlur; 8956 int fsFontId; 8957 NVGTextAlign fsAlign; 8958 8959 public: 8960 /// Setups iteration. Takes current font parameters from the given NanoVega context. 8961 this (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { reset(actx, ax, ay); } 8962 8963 /// Resets iteration. Takes current font parameters from the given NanoVega context. 8964 void reset (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { 8965 fsiter = fsiter.init; 8966 this = this.init; 8967 if (actx is null) return; 8968 NVGstate* state = nvg__getState(actx); 8969 if (state is null) return; 8970 if (state.fontId == FONS_INVALID) { ctx = null; return; } 8971 8972 ctx = actx; 8973 scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8974 invscale = 1.0f/scale; 8975 8976 fsSize = state.fontSize*scale; 8977 fsSpacing = state.letterSpacing*scale; 8978 fsBlur = state.fontBlur*scale; 8979 fsAlign = state.textAlign; 8980 fsFontId = state.fontId; 8981 restoreFont(); 8982 8983 xscaled = ax*scale; 8984 yscaled = ay*scale; 8985 fsiter.reset(ctx.fs, xscaled, yscaled); 8986 } 8987 8988 /// Restart iteration. Will not restore font. 8989 void restart () nothrow @trusted @nogc { 8990 if (ctx !is null) fsiter.reset(ctx.fs, xscaled, yscaled); 8991 } 8992 8993 /// Restore font settings for the context. 8994 void restoreFont () nothrow @trusted @nogc { 8995 if (ctx !is null) { 8996 ctx.fs.size = fsSize; 8997 ctx.fs.spacing = fsSpacing; 8998 ctx.fs.blur = fsBlur; 8999 ctx.fs.textAlign = fsAlign; 9000 ctx.fs.fontId = fsFontId; 9001 } 9002 } 9003 9004 /// Is this iterator valid? 9005 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null); } 9006 9007 /// Add chars. 9008 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { pragma(inline, true); if (ctx !is null) fsiter.put(str[]); } 9009 9010 /// Returns current advance 9011 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null ? fsiter.advance*invscale : 0); } 9012 9013 /// Returns current text bounds. 9014 void getBounds (ref float[4] bounds) nothrow @trusted @nogc { 9015 if (ctx !is null) { 9016 fsiter.getBounds(bounds); 9017 ctx.fs.getLineBounds(yscaled, &bounds[1], &bounds[3]); 9018 bounds[0] *= invscale; 9019 bounds[1] *= invscale; 9020 bounds[2] *= invscale; 9021 bounds[3] *= invscale; 9022 } else { 9023 bounds[] = 0; 9024 } 9025 } 9026 9027 /// Returns current horizontal text bounds. 9028 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc { 9029 if (ctx !is null) { 9030 fsiter.getHBounds(xmin, xmax); 9031 xmin *= invscale; 9032 xmax *= invscale; 9033 } 9034 } 9035 9036 /// Returns current vertical text bounds. 9037 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc { 9038 if (ctx !is null) { 9039 //fsiter.getVBounds(ymin, ymax); 9040 ctx.fs.getLineBounds(yscaled, &ymin, &ymax); 9041 ymin *= invscale; 9042 ymax *= invscale; 9043 } 9044 } 9045 } 9046 9047 /// Returns font line height (without line spacing), measured in local coordinate space. 9048 /// Group: text_api 9049 public float textFontHeight (NVGContext ctx) nothrow @trusted @nogc { 9050 float res = void; 9051 ctx.textMetrics(null, null, &res); 9052 return res; 9053 } 9054 9055 /// Returns font ascender (positive), measured in local coordinate space. 9056 /// Group: text_api 9057 public float textFontAscender (NVGContext ctx) nothrow @trusted @nogc { 9058 float res = void; 9059 ctx.textMetrics(&res, null, null); 9060 return res; 9061 } 9062 9063 /// Returns font descender (negative), measured in local coordinate space. 9064 /// Group: text_api 9065 public float textFontDescender (NVGContext ctx) nothrow @trusted @nogc { 9066 float res = void; 9067 ctx.textMetrics(null, &res, null); 9068 return res; 9069 } 9070 9071 /** Measures the specified text string. Returns horizontal and vertical sizes of the measured text. 9072 * Measured values are returned in local coordinate space. 9073 * 9074 * Group: text_api 9075 */ 9076 public void textExtents(T) (NVGContext ctx, const(T)[] str, float *w, float *h) nothrow @trusted @nogc if (isAnyCharType!T) { 9077 float[4] bnd = void; 9078 ctx.textBounds(0, 0, str, bnd[]); 9079 if (!ctx.fs.getFontAA(nvg__getState(ctx).fontId)) { 9080 if (w !is null) *w = nvg__lrintf(bnd.ptr[2]-bnd.ptr[0]); 9081 if (h !is null) *h = nvg__lrintf(bnd.ptr[3]-bnd.ptr[1]); 9082 } else { 9083 if (w !is null) *w = bnd.ptr[2]-bnd.ptr[0]; 9084 if (h !is null) *h = bnd.ptr[3]-bnd.ptr[1]; 9085 } 9086 } 9087 9088 /** Measures the specified text string. Returns horizontal size of the measured text. 9089 * Measured values are returned in local coordinate space. 9090 * 9091 * Group: text_api 9092 */ 9093 public float textWidth(T) (NVGContext ctx, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 9094 float w = void; 9095 ctx.textExtents(str, &w, null); 9096 return w; 9097 } 9098 9099 /** Measures the specified text string. Parameter bounds should be a float[4], 9100 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax] 9101 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn). 9102 * Measured values are returned in local coordinate space. 9103 * 9104 * Group: text_api 9105 */ 9106 public float textBounds(T) (NVGContext ctx, float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc 9107 if (isAnyCharType!T) 9108 { 9109 NVGstate* state = nvg__getState(ctx); 9110 9111 if (state.fontId == FONS_INVALID) { 9112 bounds[] = 0; 9113 return 0; 9114 } 9115 9116 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9117 ctx.fs.size = state.fontSize*scale; 9118 ctx.fs.spacing = state.letterSpacing*scale; 9119 ctx.fs.blur = state.fontBlur*scale; 9120 ctx.fs.textAlign = state.textAlign; 9121 ctx.fs.fontId = state.fontId; 9122 9123 float[4] b = void; 9124 immutable float width = ctx.fs.getTextBounds(x*scale, y*scale, str, b[]); 9125 immutable float invscale = 1.0f/scale; 9126 if (bounds.length) { 9127 // use line bounds for height 9128 ctx.fs.getLineBounds(y*scale, b.ptr+1, b.ptr+3); 9129 if (bounds.length > 0) bounds.ptr[0] = b.ptr[0]*invscale; 9130 if (bounds.length > 1) bounds.ptr[1] = b.ptr[1]*invscale; 9131 if (bounds.length > 2) bounds.ptr[2] = b.ptr[2]*invscale; 9132 if (bounds.length > 3) bounds.ptr[3] = b.ptr[3]*invscale; 9133 } 9134 return width*invscale; 9135 } 9136 9137 /// Ditto. 9138 public void textBoxBounds(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str, float[] bounds) if (isAnyCharType!T) { 9139 NVGstate* state = nvg__getState(ctx); 9140 NVGTextRow!T[2] rows; 9141 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9142 float invscale = 1.0f/scale; 9143 float lineh = 0, rminy = 0, rmaxy = 0; 9144 float minx, miny, maxx, maxy; 9145 9146 if (state.fontId == FONS_INVALID) { 9147 bounds[] = 0; 9148 return; 9149 } 9150 9151 auto oldAlign = state.textAlign; 9152 scope(exit) state.textAlign = oldAlign; 9153 auto halign = state.textAlign.horizontal; 9154 9155 ctx.textMetrics(null, null, &lineh); 9156 state.textAlign.horizontal = NVGTextAlign.H.Left; 9157 9158 minx = maxx = x; 9159 miny = maxy = y; 9160 9161 ctx.fs.size = state.fontSize*scale; 9162 ctx.fs.spacing = state.letterSpacing*scale; 9163 ctx.fs.blur = state.fontBlur*scale; 9164 ctx.fs.textAlign = state.textAlign; 9165 ctx.fs.fontId = state.fontId; 9166 ctx.fs.getLineBounds(0, &rminy, &rmaxy); 9167 rminy *= invscale; 9168 rmaxy *= invscale; 9169 9170 for (;;) { 9171 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]); 9172 if (rres.length == 0) break; 9173 foreach (ref row; rres) { 9174 float rminx, rmaxx, dx = 0; 9175 // horizontal bounds 9176 final switch (halign) { 9177 case NVGTextAlign.H.Left: dx = 0; break; 9178 case NVGTextAlign.H.Center: dx = breakRowWidth*0.5f-row.width*0.5f; break; 9179 case NVGTextAlign.H.Right: dx = breakRowWidth-row.width; break; 9180 } 9181 rminx = x+row.minx+dx; 9182 rmaxx = x+row.maxx+dx; 9183 minx = nvg__min(minx, rminx); 9184 maxx = nvg__max(maxx, rmaxx); 9185 // vertical bounds 9186 miny = nvg__min(miny, y+rminy); 9187 maxy = nvg__max(maxy, y+rmaxy); 9188 y += lineh*state.lineHeight; 9189 } 9190 str = rres[$-1].rest; 9191 } 9192 9193 if (bounds.length) { 9194 if (bounds.length > 0) bounds.ptr[0] = minx; 9195 if (bounds.length > 1) bounds.ptr[1] = miny; 9196 if (bounds.length > 2) bounds.ptr[2] = maxx; 9197 if (bounds.length > 3) bounds.ptr[3] = maxy; 9198 } 9199 } 9200 9201 /// Returns the vertical metrics based on the current text style. Measured values are returned in local coordinate space. 9202 /// Group: text_api 9203 public void textMetrics (NVGContext ctx, float* ascender, float* descender, float* lineh) nothrow @trusted @nogc { 9204 NVGstate* state = nvg__getState(ctx); 9205 9206 if (state.fontId == FONS_INVALID) { 9207 if (ascender !is null) *ascender *= 0; 9208 if (descender !is null) *descender *= 0; 9209 if (lineh !is null) *lineh *= 0; 9210 return; 9211 } 9212 9213 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9214 immutable float invscale = 1.0f/scale; 9215 9216 ctx.fs.size = state.fontSize*scale; 9217 ctx.fs.spacing = state.letterSpacing*scale; 9218 ctx.fs.blur = state.fontBlur*scale; 9219 ctx.fs.textAlign = state.textAlign; 9220 ctx.fs.fontId = state.fontId; 9221 9222 ctx.fs.getVertMetrics(ascender, descender, lineh); 9223 if (ascender !is null) *ascender *= invscale; 9224 if (descender !is null) *descender *= invscale; 9225 if (lineh !is null) *lineh *= invscale; 9226 } 9227 9228 9229 // ////////////////////////////////////////////////////////////////////////// // 9230 // fontstash 9231 // ////////////////////////////////////////////////////////////////////////// // 9232 import core.stdc.stdlib : malloc, realloc, free; 9233 import core.stdc.string : memset, memcpy, strncpy, strcmp, strlen; 9234 import core.stdc.stdio : FILE, fopen, fclose, fseek, ftell, fread, SEEK_END, SEEK_SET; 9235 9236 public: 9237 // welcome to version hell! 9238 version(nanovg_force_stb_ttf) { 9239 } else { 9240 version(nanovg_force_detect) {} else version(nanovg_use_freetype) { version = nanovg_use_freetype_ii; } 9241 } 9242 version(nanovg_ignore_iv_stb_ttf) enum nanovg_ignore_iv_stb_ttf = true; else enum nanovg_ignore_iv_stb_ttf = false; 9243 //version(nanovg_ignore_mono); 9244 9245 version(nanovg_force_stb_ttf) { 9246 private enum NanoVegaForceFreeType = false; 9247 } else { 9248 version (nanovg_builtin_freetype_bindings) { 9249 version(Posix) { 9250 private enum NanoVegaForceFreeType = true; 9251 } else { 9252 private enum NanoVegaForceFreeType = false; 9253 } 9254 } else { 9255 version(Posix) { 9256 private enum NanoVegaForceFreeType = true; 9257 } else { 9258 private enum NanoVegaForceFreeType = false; 9259 } 9260 } 9261 } 9262 9263 version(nanovg_use_freetype_ii) { 9264 enum NanoVegaIsUsingSTBTTF = false; 9265 //pragma(msg, "iv.freetype: forced"); 9266 } else { 9267 static if (NanoVegaForceFreeType) { 9268 enum NanoVegaIsUsingSTBTTF = false; 9269 } else { 9270 static if (!nanovg_ignore_iv_stb_ttf && __traits(compiles, { import iv.stb.ttf; })) { 9271 import iv.stb.ttf; 9272 enum NanoVegaIsUsingSTBTTF = true; 9273 version(nanovg_report_stb_ttf) pragma(msg, "iv.stb.ttf"); 9274 } else static if (__traits(compiles, { import arsd.ttf; })) { 9275 import arsd.ttf; 9276 enum NanoVegaIsUsingSTBTTF = true; 9277 version(nanovg_report_stb_ttf) pragma(msg, "arsd.ttf"); 9278 } else static if (__traits(compiles, { import stb_truetype; })) { 9279 import stb_truetype; 9280 enum NanoVegaIsUsingSTBTTF = true; 9281 version(nanovg_report_stb_ttf) pragma(msg, "stb_truetype"); 9282 } else static if (__traits(compiles, { import iv.freetype; })) { 9283 version (nanovg_builtin_freetype_bindings) { 9284 enum NanoVegaIsUsingSTBTTF = false; 9285 version = nanovg_builtin_freetype_bindings; 9286 } else { 9287 import iv.freetype; 9288 enum NanoVegaIsUsingSTBTTF = false; 9289 } 9290 version(nanovg_report_stb_ttf) pragma(msg, "freetype"); 9291 } else { 9292 static assert(0, "no stb_ttf/iv.freetype found!"); 9293 } 9294 } 9295 } 9296 9297 9298 // ////////////////////////////////////////////////////////////////////////// // 9299 //version = nanovg_ft_mono; 9300 9301 /// Invald font id. 9302 /// Group: font_stash 9303 public enum FONS_INVALID = -1; 9304 9305 public enum FONSBitmapFlag : uint { 9306 Required = 0, 9307 Optional = 1, 9308 } 9309 9310 public enum FONSError : int { 9311 NoError = 0, 9312 AtlasFull = 1, // Font atlas is full. 9313 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. 9314 StatesOverflow = 3, // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES. 9315 StatesUnderflow = 4, // Trying to pop too many states fonsPopState(). 9316 } 9317 9318 /// Initial parameters for new FontStash. 9319 /// Group: font_stash 9320 public struct FONSParams { 9321 enum Flag : uint { 9322 ZeroTopLeft = 0U, // default 9323 ZeroBottomLeft = 1U, 9324 } 9325 int width, height; 9326 Flag flags = Flag.ZeroTopLeft; 9327 void* userPtr; 9328 bool function (void* uptr, int width, int height) nothrow @trusted @nogc renderCreate; 9329 int function (void* uptr, int width, int height) nothrow @trusted @nogc renderResize; 9330 void function (void* uptr, int* rect, const(ubyte)* data) nothrow @trusted @nogc renderUpdate; 9331 void function (void* uptr) nothrow @trusted @nogc renderDelete; 9332 @property bool isZeroTopLeft () const pure nothrow @trusted @nogc { pragma(inline, true); return ((flags&Flag.ZeroBottomLeft) == 0); } 9333 } 9334 9335 //TODO: document this 9336 public struct FONSQuad { 9337 float x0=0, y0=0, s0=0, t0=0; 9338 float x1=0, y1=0, s1=0, t1=0; 9339 } 9340 9341 //TODO: document this 9342 public struct FONSTextIter(CT) if (isAnyCharType!CT) { 9343 alias CharType = CT; 9344 float x=0, y=0, nextx=0, nexty=0, scale=0, spacing=0; 9345 uint codepoint; 9346 short isize, iblur; 9347 FONSContext stash; 9348 FONSfont* font; 9349 int prevGlyphIndex; 9350 const(CT)* s; // string 9351 const(CT)* n; // next 9352 const(CT)* e; // end 9353 FONSBitmapFlag bitmapOption; 9354 static if (is(CT == char)) { 9355 uint utf8state; 9356 } 9357 9358 this (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { setup(astash, ax, ay, astr, abitmapOption); } 9359 ~this () nothrow @trusted @nogc { pragma(inline, true); static if (is(CT == char)) utf8state = 0; s = n = e = null; } 9360 9361 @property const(CT)* stringp () const pure nothrow @trusted @nogc { pragma(inline, true); return s; } 9362 @property const(CT)* nextp () const pure nothrow @trusted @nogc { pragma(inline, true); return n; } 9363 @property const(CT)* endp () const pure nothrow @trusted @nogc { pragma(inline, true); return e; } 9364 9365 bool setup (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { 9366 import core.stdc.string : memset; 9367 9368 memset(&this, 0, this.sizeof); 9369 if (astash is null) return false; 9370 9371 FONSstate* state = astash.getState; 9372 9373 if (state.font < 0 || state.font >= astash.nfonts) return false; 9374 font = astash.fonts[state.font]; 9375 if (font is null || font.fdata is null) return false; 9376 9377 isize = cast(short)(state.size*10.0f); 9378 iblur = cast(short)state.blur; 9379 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 9380 9381 // align horizontally 9382 if (state.talign.left) { 9383 // empty 9384 } else if (state.talign.right) { 9385 immutable float width = astash.getTextBounds(ax, ay, astr, null); 9386 ax -= width; 9387 } else if (state.talign.center) { 9388 immutable float width = astash.getTextBounds(ax, ay, astr, null); 9389 ax -= width*0.5f; 9390 } 9391 9392 // align vertically 9393 ay += astash.getVertAlign(font, state.talign, isize); 9394 9395 x = nextx = ax; 9396 y = nexty = ay; 9397 spacing = state.spacing; 9398 9399 if (astr.ptr is null) { 9400 static if (is(CharType == char)) astr = ""; 9401 else static if (is(CharType == wchar)) astr = ""w; 9402 else static if (is(CharType == dchar)) astr = ""d; 9403 else static assert(0, "wtf?!"); 9404 } 9405 s = astr.ptr; 9406 n = astr.ptr; 9407 e = astr.ptr+astr.length; 9408 9409 codepoint = 0; 9410 prevGlyphIndex = -1; 9411 bitmapOption = abitmapOption; 9412 stash = astash; 9413 9414 return true; 9415 } 9416 9417 bool getDummyChar (ref FONSQuad quad) nothrow @trusted @nogc { 9418 if (stash is null || font is null) return false; 9419 // get glyph and quad 9420 x = nextx; 9421 y = nexty; 9422 FONSglyph* glyph = stash.getGlyph(font, 0xFFFD, isize, iblur, bitmapOption); 9423 if (glyph !is null) { 9424 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9425 prevGlyphIndex = glyph.index; 9426 return true; 9427 } else { 9428 prevGlyphIndex = -1; 9429 return false; 9430 } 9431 } 9432 9433 bool next (ref FONSQuad quad) nothrow @trusted @nogc { 9434 if (stash is null || font is null) return false; 9435 FONSglyph* glyph = null; 9436 static if (is(CharType == char)) { 9437 const(char)* str = this.n; 9438 this.s = this.n; 9439 if (str is this.e) return false; 9440 const(char)* e = this.e; 9441 for (; str !is e; ++str) { 9442 /*if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;*/ 9443 mixin(DecUtfMixin!("this.utf8state", "this.codepoint", "*cast(const(ubyte)*)str")); 9444 if (utf8state) continue; 9445 ++str; // 'cause we'll break anyway 9446 // get glyph and quad 9447 x = nextx; 9448 y = nexty; 9449 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption); 9450 if (glyph !is null) { 9451 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9452 prevGlyphIndex = glyph.index; 9453 } else { 9454 prevGlyphIndex = -1; 9455 } 9456 break; 9457 } 9458 this.n = str; 9459 } else { 9460 const(CharType)* str = this.n; 9461 this.s = this.n; 9462 if (str is this.e) return false; 9463 codepoint = cast(uint)(*str++); 9464 if (codepoint > dchar.max) codepoint = 0xFFFD; 9465 // get glyph and quad 9466 x = nextx; 9467 y = nexty; 9468 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption); 9469 if (glyph !is null) { 9470 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9471 prevGlyphIndex = glyph.index; 9472 } else { 9473 prevGlyphIndex = -1; 9474 } 9475 this.n = str; 9476 } 9477 return true; 9478 } 9479 } 9480 9481 9482 // ////////////////////////////////////////////////////////////////////////// // 9483 //static if (!HasAST) version = nanovg_use_freetype_ii_x; 9484 9485 /*version(nanovg_use_freetype_ii_x)*/ static if (!NanoVegaIsUsingSTBTTF) { 9486 version(nanovg_builtin_freetype_bindings) { 9487 pragma(lib, "freetype"); 9488 private extern(C) nothrow @trusted @nogc { 9489 private import core.stdc.config : c_long, c_ulong; 9490 alias FT_Pos = c_long; 9491 // config/ftconfig.h 9492 alias FT_Int16 = short; 9493 alias FT_UInt16 = ushort; 9494 alias FT_Int32 = int; 9495 alias FT_UInt32 = uint; 9496 alias FT_Fast = int; 9497 alias FT_UFast = uint; 9498 alias FT_Int64 = long; 9499 alias FT_Uint64 = ulong; 9500 // fttypes.h 9501 alias FT_Bool = ubyte; 9502 alias FT_FWord = short; 9503 alias FT_UFWord = ushort; 9504 alias FT_Char = char; 9505 alias FT_Byte = ubyte; 9506 alias FT_Bytes = FT_Byte*; 9507 alias FT_Tag = FT_UInt32; 9508 alias FT_String = char; 9509 alias FT_Short = short; 9510 alias FT_UShort = ushort; 9511 alias FT_Int = int; 9512 alias FT_UInt = uint; 9513 alias FT_Long = c_long; 9514 alias FT_ULong = c_ulong; 9515 alias FT_F2Dot14 = short; 9516 alias FT_F26Dot6 = c_long; 9517 alias FT_Fixed = c_long; 9518 alias FT_Error = int; 9519 alias FT_Pointer = void*; 9520 alias FT_Offset = usize; 9521 alias FT_PtrDist = ptrdiff_t; 9522 9523 struct FT_UnitVector { 9524 FT_F2Dot14 x; 9525 FT_F2Dot14 y; 9526 } 9527 9528 struct FT_Matrix { 9529 FT_Fixed xx, xy; 9530 FT_Fixed yx, yy; 9531 } 9532 9533 struct FT_Data { 9534 const(FT_Byte)* pointer; 9535 FT_Int length; 9536 } 9537 alias FT_Face = FT_FaceRec*; 9538 struct FT_FaceRec { 9539 FT_Long num_faces; 9540 FT_Long face_index; 9541 FT_Long face_flags; 9542 FT_Long style_flags; 9543 FT_Long num_glyphs; 9544 FT_String* family_name; 9545 FT_String* style_name; 9546 FT_Int num_fixed_sizes; 9547 FT_Bitmap_Size* available_sizes; 9548 FT_Int num_charmaps; 9549 FT_CharMap* charmaps; 9550 FT_Generic generic; 9551 FT_BBox bbox; 9552 FT_UShort units_per_EM; 9553 FT_Short ascender; 9554 FT_Short descender; 9555 FT_Short height; 9556 FT_Short max_advance_width; 9557 FT_Short max_advance_height; 9558 FT_Short underline_position; 9559 FT_Short underline_thickness; 9560 FT_GlyphSlot glyph; 9561 FT_Size size; 9562 FT_CharMap charmap; 9563 FT_Driver driver; 9564 FT_Memory memory; 9565 FT_Stream stream; 9566 FT_ListRec sizes_list; 9567 FT_Generic autohint; 9568 void* extensions; 9569 FT_Face_Internal internal; 9570 } 9571 struct FT_Bitmap_Size { 9572 FT_Short height; 9573 FT_Short width; 9574 FT_Pos size; 9575 FT_Pos x_ppem; 9576 FT_Pos y_ppem; 9577 } 9578 alias FT_CharMap = FT_CharMapRec*; 9579 struct FT_CharMapRec { 9580 FT_Face face; 9581 FT_Encoding encoding; 9582 FT_UShort platform_id; 9583 FT_UShort encoding_id; 9584 } 9585 extern(C) nothrow @nogc { alias FT_Generic_Finalizer = void function (void* object); } 9586 struct FT_Generic { 9587 void* data; 9588 FT_Generic_Finalizer finalizer; 9589 } 9590 struct FT_Vector { 9591 FT_Pos x; 9592 FT_Pos y; 9593 } 9594 struct FT_BBox { 9595 FT_Pos xMin, yMin; 9596 FT_Pos xMax, yMax; 9597 } 9598 alias FT_Pixel_Mode = int; 9599 enum { 9600 FT_PIXEL_MODE_NONE = 0, 9601 FT_PIXEL_MODE_MONO, 9602 FT_PIXEL_MODE_GRAY, 9603 FT_PIXEL_MODE_GRAY2, 9604 FT_PIXEL_MODE_GRAY4, 9605 FT_PIXEL_MODE_LCD, 9606 FT_PIXEL_MODE_LCD_V, 9607 FT_PIXEL_MODE_MAX 9608 } 9609 struct FT_Bitmap { 9610 uint rows; 9611 uint width; 9612 int pitch; 9613 ubyte* buffer; 9614 ushort num_grays; 9615 ubyte pixel_mode; 9616 ubyte palette_mode; 9617 void* palette; 9618 } 9619 struct FT_Outline { 9620 short n_contours; 9621 short n_points; 9622 FT_Vector* points; 9623 byte* tags; 9624 short* contours; 9625 int flags; 9626 } 9627 alias FT_GlyphSlot = FT_GlyphSlotRec*; 9628 struct FT_GlyphSlotRec { 9629 FT_Library library; 9630 FT_Face face; 9631 FT_GlyphSlot next; 9632 FT_UInt reserved; 9633 FT_Generic generic; 9634 FT_Glyph_Metrics metrics; 9635 FT_Fixed linearHoriAdvance; 9636 FT_Fixed linearVertAdvance; 9637 FT_Vector advance; 9638 FT_Glyph_Format format; 9639 FT_Bitmap bitmap; 9640 FT_Int bitmap_left; 9641 FT_Int bitmap_top; 9642 FT_Outline outline; 9643 FT_UInt num_subglyphs; 9644 FT_SubGlyph subglyphs; 9645 void* control_data; 9646 c_long control_len; 9647 FT_Pos lsb_delta; 9648 FT_Pos rsb_delta; 9649 void* other; 9650 FT_Slot_Internal internal; 9651 } 9652 alias FT_Size = FT_SizeRec*; 9653 struct FT_SizeRec { 9654 FT_Face face; 9655 FT_Generic generic; 9656 FT_Size_Metrics metrics; 9657 FT_Size_Internal internal; 9658 } 9659 alias FT_Encoding = FT_Tag; 9660 alias FT_Face_Internal = void*; 9661 alias FT_Driver = void*; 9662 alias FT_Memory = void*; 9663 alias FT_Stream = void*; 9664 alias FT_Library = void*; 9665 alias FT_SubGlyph = void*; 9666 alias FT_Slot_Internal = void*; 9667 alias FT_Size_Internal = void*; 9668 alias FT_ListNode = FT_ListNodeRec*; 9669 alias FT_List = FT_ListRec*; 9670 struct FT_ListNodeRec { 9671 FT_ListNode prev; 9672 FT_ListNode next; 9673 void* data; 9674 } 9675 struct FT_ListRec { 9676 FT_ListNode head; 9677 FT_ListNode tail; 9678 } 9679 struct FT_Glyph_Metrics { 9680 FT_Pos width; 9681 FT_Pos height; 9682 FT_Pos horiBearingX; 9683 FT_Pos horiBearingY; 9684 FT_Pos horiAdvance; 9685 FT_Pos vertBearingX; 9686 FT_Pos vertBearingY; 9687 FT_Pos vertAdvance; 9688 } 9689 alias FT_Glyph_Format = FT_Tag; 9690 FT_Tag FT_MAKE_TAG (char x1, char x2, char x3, char x4) pure nothrow @safe @nogc { 9691 pragma(inline, true); 9692 return cast(FT_UInt32)((x1<<24)|(x2<<16)|(x3<<8)|x4); 9693 } 9694 enum : FT_Tag { 9695 FT_GLYPH_FORMAT_NONE = 0, 9696 FT_GLYPH_FORMAT_COMPOSITE = FT_MAKE_TAG('c','o','m','p'), 9697 FT_GLYPH_FORMAT_BITMAP = FT_MAKE_TAG('b','i','t','s'), 9698 FT_GLYPH_FORMAT_OUTLINE = FT_MAKE_TAG('o','u','t','l'), 9699 FT_GLYPH_FORMAT_PLOTTER = FT_MAKE_TAG('p','l','o','t'), 9700 } 9701 struct FT_Size_Metrics { 9702 FT_UShort x_ppem; 9703 FT_UShort y_ppem; 9704 9705 FT_Fixed x_scale; 9706 FT_Fixed y_scale; 9707 9708 FT_Pos ascender; 9709 FT_Pos descender; 9710 FT_Pos height; 9711 FT_Pos max_advance; 9712 } 9713 enum FT_LOAD_DEFAULT = 0x0U; 9714 enum FT_LOAD_NO_SCALE = 1U<<0; 9715 enum FT_LOAD_NO_HINTING = 1U<<1; 9716 enum FT_LOAD_RENDER = 1U<<2; 9717 enum FT_LOAD_NO_BITMAP = 1U<<3; 9718 enum FT_LOAD_VERTICAL_LAYOUT = 1U<<4; 9719 enum FT_LOAD_FORCE_AUTOHINT = 1U<<5; 9720 enum FT_LOAD_CROP_BITMAP = 1U<<6; 9721 enum FT_LOAD_PEDANTIC = 1U<<7; 9722 enum FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1U<<9; 9723 enum FT_LOAD_NO_RECURSE = 1U<<10; 9724 enum FT_LOAD_IGNORE_TRANSFORM = 1U<<11; 9725 enum FT_LOAD_MONOCHROME = 1U<<12; 9726 enum FT_LOAD_LINEAR_DESIGN = 1U<<13; 9727 enum FT_LOAD_NO_AUTOHINT = 1U<<15; 9728 enum FT_LOAD_COLOR = 1U<<20; 9729 enum FT_LOAD_COMPUTE_METRICS = 1U<<21; 9730 enum FT_FACE_FLAG_KERNING = 1U<<6; 9731 alias FT_Kerning_Mode = int; 9732 enum /*FT_Kerning_Mode*/ { 9733 FT_KERNING_DEFAULT = 0, 9734 FT_KERNING_UNFITTED, 9735 FT_KERNING_UNSCALED 9736 } 9737 extern(C) nothrow @nogc { 9738 alias FT_Outline_MoveToFunc = int function (const(FT_Vector)*, void*); 9739 alias FT_Outline_LineToFunc = int function (const(FT_Vector)*, void*); 9740 alias FT_Outline_ConicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, void*); 9741 alias FT_Outline_CubicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, const(FT_Vector)*, void*); 9742 } 9743 struct FT_Outline_Funcs { 9744 FT_Outline_MoveToFunc move_to; 9745 FT_Outline_LineToFunc line_to; 9746 FT_Outline_ConicToFunc conic_to; 9747 FT_Outline_CubicToFunc cubic_to; 9748 int shift; 9749 FT_Pos delta; 9750 } 9751 9752 FT_Error FT_Init_FreeType (FT_Library*); 9753 FT_Error FT_New_Memory_Face (FT_Library, const(FT_Byte)*, FT_Long, FT_Long, FT_Face*); 9754 FT_UInt FT_Get_Char_Index (FT_Face, FT_ULong); 9755 FT_Error FT_Set_Pixel_Sizes (FT_Face, FT_UInt, FT_UInt); 9756 FT_Error FT_Load_Glyph (FT_Face, FT_UInt, FT_Int32); 9757 FT_Error FT_Get_Advance (FT_Face, FT_UInt, FT_Int32, FT_Fixed*); 9758 FT_Error FT_Get_Kerning (FT_Face, FT_UInt, FT_UInt, FT_UInt, FT_Vector*); 9759 void FT_Outline_Get_CBox (const(FT_Outline)*, FT_BBox*); 9760 FT_Error FT_Outline_Decompose (FT_Outline*, const(FT_Outline_Funcs)*, void*); 9761 } 9762 } else version(bindbc) { 9763 import bindbc.freetype; 9764 alias FT_KERNING_DEFAULT = FT_Kerning_Mode.FT_KERNING_DEFAULT; 9765 alias FT_KERNING_UNFITTED = FT_Kerning_Mode.FT_KERNING_UNFITTED; 9766 alias FT_KERNING_UNSCALED = FT_Kerning_Mode.FT_KERNING_UNSCALED; 9767 } else { 9768 import iv.freetype; 9769 } 9770 9771 struct FONSttFontImpl { 9772 FT_Face font; 9773 bool mono; // no aa? 9774 } 9775 9776 __gshared FT_Library ftLibrary; 9777 9778 int fons__tt_init (FONSContext context) nothrow @trusted @nogc { 9779 FT_Error ftError; 9780 //FONS_NOTUSED(context); 9781 ftError = FT_Init_FreeType(&ftLibrary); 9782 return (ftError == 0); 9783 } 9784 9785 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc { 9786 font.mono = v; 9787 } 9788 9789 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc { 9790 return font.mono; 9791 } 9792 9793 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc { 9794 FT_Error ftError; 9795 //font.font.userdata = stash; 9796 ftError = FT_New_Memory_Face(ftLibrary, cast(const(FT_Byte)*)data, dataSize, 0, &font.font); 9797 return ftError == 0; 9798 } 9799 9800 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc { 9801 *ascent = font.font.ascender; 9802 *descent = font.font.descender; 9803 *lineGap = font.font.height-(*ascent - *descent); 9804 } 9805 9806 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc { 9807 return size/(font.font.ascender-font.font.descender); 9808 } 9809 9810 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc { 9811 return FT_Get_Char_Index(font.font, codepoint); 9812 } 9813 9814 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 { 9815 FT_Error ftError; 9816 FT_GlyphSlot ftGlyph; 9817 //version(nanovg_ignore_mono) enum exflags = 0; 9818 //else version(nanovg_ft_mono) enum exflags = FT_LOAD_MONOCHROME; else enum exflags = 0; 9819 uint exflags = (font.mono ? FT_LOAD_MONOCHROME : 0); 9820 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))); 9821 if (ftError) return 0; 9822 ftError = FT_Load_Glyph(font.font, glyph, FT_LOAD_RENDER|/*FT_LOAD_NO_AUTOHINT|*/exflags); 9823 if (ftError) return 0; 9824 ftError = FT_Get_Advance(font.font, glyph, FT_LOAD_NO_SCALE|/*FT_LOAD_NO_AUTOHINT|*/exflags, cast(FT_Fixed*)advance); 9825 if (ftError) return 0; 9826 ftGlyph = font.font.glyph; 9827 *lsb = cast(int)ftGlyph.metrics.horiBearingX; 9828 *x0 = ftGlyph.bitmap_left; 9829 *x1 = *x0+ftGlyph.bitmap.width; 9830 *y0 = -ftGlyph.bitmap_top; 9831 *y1 = *y0+ftGlyph.bitmap.rows; 9832 return 1; 9833 } 9834 9835 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc { 9836 FT_GlyphSlot ftGlyph = font.font.glyph; 9837 //FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap 9838 //version(nanovg_ignore_mono) enum RenderAA = true; 9839 //else version(nanovg_ft_mono) enum RenderAA = false; 9840 //else enum RenderAA = true; 9841 if (font.mono) { 9842 auto src = ftGlyph.bitmap.buffer; 9843 auto dst = output; 9844 auto spt = ftGlyph.bitmap.pitch; 9845 if (spt < 0) spt = -spt; 9846 foreach (int y; 0..ftGlyph.bitmap.rows) { 9847 ubyte count = 0, b = 0; 9848 auto s = src; 9849 auto d = dst; 9850 foreach (int x; 0..ftGlyph.bitmap.width) { 9851 if (count-- == 0) { count = 7; b = *s++; } else b <<= 1; 9852 *d++ = (b&0x80 ? 255 : 0); 9853 } 9854 src += spt; 9855 dst += outStride; 9856 } 9857 } else { 9858 auto src = ftGlyph.bitmap.buffer; 9859 auto dst = output; 9860 auto spt = ftGlyph.bitmap.pitch; 9861 if (spt < 0) spt = -spt; 9862 foreach (int y; 0..ftGlyph.bitmap.rows) { 9863 import core.stdc.string : memcpy; 9864 //dst[0..ftGlyph.bitmap.width] = src[0..ftGlyph.bitmap.width]; 9865 memcpy(dst, src, ftGlyph.bitmap.width); 9866 src += spt; 9867 dst += outStride; 9868 } 9869 } 9870 } 9871 9872 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc { 9873 FT_Vector ftKerning; 9874 version(none) { 9875 // fitted kerning 9876 FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning); 9877 //{ import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d\n", glyph1, glyph2, ftKerning.x, ftKerning.y); } 9878 return cast(int)ftKerning.x; // round up and convert to integer 9879 } else { 9880 // unfitted kerning 9881 //FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNFITTED, &ftKerning); 9882 if (glyph1 <= 0 || glyph2 <= 0 || (font.font.face_flags&FT_FACE_FLAG_KERNING) == 0) return 0; 9883 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; 9884 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning)) return 0; 9885 version(none) { 9886 if (ftKerning.x) { 9887 //{ import core.stdc.stdio : printf; printf("has kerning: %u\n", cast(uint)(font.font.face_flags&FT_FACE_FLAG_KERNING)); } 9888 { import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d (size=%g)\n", glyph1, glyph2, ftKerning.x, ftKerning.y, cast(double)size); } 9889 } 9890 } 9891 version(none) { 9892 FT_Vector kk; 9893 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNSCALED, &kk)) assert(0, "wtf?!"); 9894 auto kadvfrac = FT_MulFix(kk.x, font.font.size.metrics.x_scale); // 1/64 of pixel 9895 //return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6); 9896 //assert(ftKerning.x == kadvfrac); 9897 if (ftKerning.x || kadvfrac) { 9898 { 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); } 9899 } 9900 //return cast(int)(kadvfrac+(kadvfrac < 0 ? -31 : 32)>>6); // round up and convert to integer 9901 return kadvfrac/64.0f; 9902 } 9903 //return cast(int)(ftKerning.x+(ftKerning.x < 0 ? -31 : 32)>>6); // round up and convert to integer 9904 return ftKerning.x/64.0f; 9905 } 9906 } 9907 9908 extern(C) nothrow @trusted @nogc { 9909 static struct OutlinerData { 9910 @disable this (this); 9911 void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); } 9912 NVGContext vg; 9913 NVGPathOutline.DataStore* ol; 9914 FT_BBox outlineBBox; 9915 nothrow @trusted @nogc: 9916 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; } 9917 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 9918 } 9919 9920 int fons__nvg__moveto_cb (const(FT_Vector)* to, void* user) { 9921 auto odata = cast(OutlinerData*)user; 9922 if (odata.vg !is null) odata.vg.moveTo(odata.transx(to.x), odata.transy(to.y)); 9923 if (odata.ol !is null) { 9924 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo); 9925 odata.ol.putArgs(odata.transx(to.x)); 9926 odata.ol.putArgs(odata.transy(to.y)); 9927 } 9928 return 0; 9929 } 9930 9931 int fons__nvg__lineto_cb (const(FT_Vector)* to, void* user) { 9932 auto odata = cast(OutlinerData*)user; 9933 if (odata.vg !is null) odata.vg.lineTo(odata.transx(to.x), odata.transy(to.y)); 9934 if (odata.ol !is null) { 9935 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo); 9936 odata.ol.putArgs(odata.transx(to.x)); 9937 odata.ol.putArgs(odata.transy(to.y)); 9938 } 9939 return 0; 9940 } 9941 9942 int fons__nvg__quadto_cb (const(FT_Vector)* c1, const(FT_Vector)* to, void* user) { 9943 auto odata = cast(OutlinerData*)user; 9944 if (odata.vg !is null) odata.vg.quadTo(odata.transx(c1.x), odata.transy(c1.y), odata.transx(to.x), odata.transy(to.y)); 9945 if (odata.ol !is null) { 9946 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo); 9947 odata.ol.putArgs(odata.transx(c1.x)); 9948 odata.ol.putArgs(odata.transy(c1.y)); 9949 odata.ol.putArgs(odata.transx(to.x)); 9950 odata.ol.putArgs(odata.transy(to.y)); 9951 } 9952 return 0; 9953 } 9954 9955 int fons__nvg__cubicto_cb (const(FT_Vector)* c1, const(FT_Vector)* c2, const(FT_Vector)* to, void* user) { 9956 auto odata = cast(OutlinerData*)user; 9957 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)); 9958 if (odata.ol !is null) { 9959 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo); 9960 odata.ol.putArgs(odata.transx(c1.x)); 9961 odata.ol.putArgs(odata.transy(c1.y)); 9962 odata.ol.putArgs(odata.transx(c2.x)); 9963 odata.ol.putArgs(odata.transy(c2.y)); 9964 odata.ol.putArgs(odata.transx(to.x)); 9965 odata.ol.putArgs(odata.transy(to.y)); 9966 } 9967 return 0; 9968 } 9969 } 9970 9971 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc { 9972 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 9973 9974 FT_Outline_Funcs funcs; 9975 funcs.move_to = &fons__nvg__moveto_cb; 9976 funcs.line_to = &fons__nvg__lineto_cb; 9977 funcs.conic_to = &fons__nvg__quadto_cb; 9978 funcs.cubic_to = &fons__nvg__cubicto_cb; 9979 9980 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 9981 if (err) { bounds[] = 0; return false; } 9982 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; } 9983 9984 FT_Outline outline = font.font.glyph.outline; 9985 9986 OutlinerData odata; 9987 odata.vg = vg; 9988 FT_Outline_Get_CBox(&outline, &odata.outlineBBox); 9989 9990 err = FT_Outline_Decompose(&outline, &funcs, &odata); 9991 if (err) { bounds[] = 0; return false; } 9992 if (bounds.length > 0) bounds.ptr[0] = odata.outlineBBox.xMin; 9993 if (bounds.length > 1) bounds.ptr[1] = -odata.outlineBBox.yMax; 9994 if (bounds.length > 2) bounds.ptr[2] = odata.outlineBBox.xMax; 9995 if (bounds.length > 3) bounds.ptr[3] = -odata.outlineBBox.yMin; 9996 return true; 9997 } 9998 9999 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 10000 FT_Outline_Funcs funcs; 10001 funcs.move_to = &fons__nvg__moveto_cb; 10002 funcs.line_to = &fons__nvg__lineto_cb; 10003 funcs.conic_to = &fons__nvg__quadto_cb; 10004 funcs.cubic_to = &fons__nvg__cubicto_cb; 10005 10006 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 10007 if (err) return false; 10008 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) return false; 10009 10010 FT_Outline outline = font.font.glyph.outline; 10011 10012 OutlinerData odata; 10013 odata.ol = ol; 10014 FT_Outline_Get_CBox(&outline, &odata.outlineBBox); 10015 10016 err = FT_Outline_Decompose(&outline, &funcs, &odata); 10017 if (err) return false; 10018 ol.bounds.ptr[0] = odata.outlineBBox.xMin; 10019 ol.bounds.ptr[1] = -odata.outlineBBox.yMax; 10020 ol.bounds.ptr[2] = odata.outlineBBox.xMax; 10021 ol.bounds.ptr[3] = -odata.outlineBBox.yMin; 10022 return true; 10023 } 10024 10025 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc { 10026 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10027 10028 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 10029 if (err) return false; 10030 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; } 10031 10032 FT_Outline outline = font.font.glyph.outline; 10033 FT_BBox outlineBBox; 10034 FT_Outline_Get_CBox(&outline, &outlineBBox); 10035 if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin; 10036 if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax; 10037 if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax; 10038 if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin; 10039 return true; 10040 } 10041 10042 10043 } else { 10044 // ////////////////////////////////////////////////////////////////////////// // 10045 // sorry 10046 import std.traits : isFunctionPointer, isDelegate; 10047 private auto assumeNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) { 10048 import std.traits; 10049 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_; 10050 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 10051 } 10052 10053 private auto forceNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) { 10054 try { 10055 return assumeNoThrowNoGC(t)(); 10056 } catch (Exception e) { 10057 assert(0, "OOPS!"); 10058 } 10059 } 10060 10061 struct FONSttFontImpl { 10062 stbtt_fontinfo font; 10063 bool mono; // no aa? 10064 } 10065 10066 int fons__tt_init (FONSContext context) nothrow @trusted @nogc { 10067 return 1; 10068 } 10069 10070 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc { 10071 font.mono = v; 10072 } 10073 10074 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc { 10075 return font.mono; 10076 } 10077 10078 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc { 10079 int stbError; 10080 font.font.userdata = context; 10081 forceNoThrowNoGC({ stbError = stbtt_InitFont(&font.font, data, 0); }); 10082 return stbError; 10083 } 10084 10085 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc { 10086 forceNoThrowNoGC({ stbtt_GetFontVMetrics(&font.font, ascent, descent, lineGap); }); 10087 } 10088 10089 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc { 10090 float res = void; 10091 forceNoThrowNoGC({ res = stbtt_ScaleForPixelHeight(&font.font, size); }); 10092 return res; 10093 } 10094 10095 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc { 10096 int res; 10097 forceNoThrowNoGC({ res = stbtt_FindGlyphIndex(&font.font, codepoint); }); 10098 return res; 10099 } 10100 10101 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 { 10102 forceNoThrowNoGC({ stbtt_GetGlyphHMetrics(&font.font, glyph, advance, lsb); }); 10103 forceNoThrowNoGC({ stbtt_GetGlyphBitmapBox(&font.font, glyph, scale, scale, x0, y0, x1, y1); }); 10104 return 1; 10105 } 10106 10107 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc { 10108 forceNoThrowNoGC({ stbtt_MakeGlyphBitmap(&font.font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); }); 10109 } 10110 10111 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc { 10112 // FUnits -> pixels: pointSize * resolution / (72 points per inch * units_per_em) 10113 // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#converting 10114 float res = void; 10115 forceNoThrowNoGC({ 10116 res = stbtt_GetGlyphKernAdvance(&font.font, glyph1, glyph2); 10117 res *= stbtt_ScaleForPixelHeight(&font.font, size); 10118 }); 10119 /* 10120 if (res != 0) { 10121 { 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)); } 10122 } 10123 */ 10124 //k8: dunno if this is right; i guess it isn't but... 10125 return res; 10126 } 10127 10128 // old arsd.ttf sux! ;-) 10129 static if (is(typeof(STBTT_vcubic))) { 10130 10131 static struct OutlinerData { 10132 @disable this (this); 10133 void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); } 10134 NVGPathOutline.DataStore* ol; 10135 nothrow @trusted @nogc: 10136 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; } 10137 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 10138 } 10139 10140 10141 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc { 10142 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10143 10144 bool okflag = false; 10145 10146 forceNoThrowNoGC({ 10147 int x0, y0, x1, y1; 10148 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10149 bounds[] = 0; 10150 return; 10151 } 10152 10153 if (bounds.length > 0) bounds.ptr[0] = x0; 10154 if (bounds.length > 1) bounds.ptr[1] = -y1; 10155 if (bounds.length > 2) bounds.ptr[2] = x1; 10156 if (bounds.length > 3) bounds.ptr[3] = -y0; 10157 10158 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 10159 10160 stbtt_vertex* verts = null; 10161 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); } 10162 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts); 10163 if (vcount < 1) return; 10164 10165 foreach (const ref vt; verts[0..vcount]) { 10166 switch (vt.type) { 10167 case STBTT_vmove: vg.moveTo(vt.x, transy(vt.y)); break; 10168 case STBTT_vline: vg.lineTo(vt.x, transy(vt.y)); break; 10169 case STBTT_vcurve: vg.quadTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy)); break; 10170 case STBTT_vcubic: vg.bezierTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy), vt.cx1, transy(vt.cy1)); break; 10171 default: 10172 } 10173 } 10174 10175 okflag = true; 10176 }); 10177 10178 return okflag; 10179 } 10180 10181 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 10182 bool okflag = false; 10183 10184 forceNoThrowNoGC({ 10185 int x0, y0, x1, y1; 10186 10187 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10188 ol.bounds[] = 0; 10189 return; 10190 } 10191 10192 ol.bounds.ptr[0] = x0; 10193 ol.bounds.ptr[1] = -y1; 10194 ol.bounds.ptr[2] = x1; 10195 ol.bounds.ptr[3] = -y0; 10196 10197 stbtt_vertex* verts = null; 10198 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); } 10199 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts); 10200 if (vcount < 1) return; 10201 10202 OutlinerData odata; 10203 odata.ol = ol; 10204 10205 foreach (const ref vt; verts[0..vcount]) { 10206 switch (vt.type) { 10207 case STBTT_vmove: 10208 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo); 10209 odata.ol.putArgs(odata.transx(vt.x)); 10210 odata.ol.putArgs(odata.transy(vt.y)); 10211 break; 10212 case STBTT_vline: 10213 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo); 10214 odata.ol.putArgs(odata.transx(vt.x)); 10215 odata.ol.putArgs(odata.transy(vt.y)); 10216 break; 10217 case STBTT_vcurve: 10218 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo); 10219 odata.ol.putArgs(odata.transx(vt.x)); 10220 odata.ol.putArgs(odata.transy(vt.y)); 10221 odata.ol.putArgs(odata.transx(vt.cx)); 10222 odata.ol.putArgs(odata.transy(vt.cy)); 10223 break; 10224 case STBTT_vcubic: 10225 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo); 10226 odata.ol.putArgs(odata.transx(vt.x)); 10227 odata.ol.putArgs(odata.transy(vt.y)); 10228 odata.ol.putArgs(odata.transx(vt.cx)); 10229 odata.ol.putArgs(odata.transy(vt.cy)); 10230 odata.ol.putArgs(odata.transx(vt.cx1)); 10231 odata.ol.putArgs(odata.transy(vt.cy1)); 10232 break; 10233 default: 10234 } 10235 } 10236 10237 okflag = true; 10238 }); 10239 10240 return okflag; 10241 } 10242 10243 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc { 10244 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10245 10246 bool okflag = false; 10247 10248 forceNoThrowNoGC({ 10249 int x0, y0, x1, y1; 10250 if (stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10251 if (bounds.length > 0) bounds.ptr[0] = x0; 10252 if (bounds.length > 1) bounds.ptr[1] = -y1; 10253 if (bounds.length > 2) bounds.ptr[2] = x1; 10254 if (bounds.length > 3) bounds.ptr[3] = -y0; 10255 okflag = true; 10256 } else { 10257 bounds[] = 0; 10258 } 10259 }); 10260 10261 return okflag; 10262 } 10263 10264 } // check for old stb_ttf 10265 10266 10267 } // version 10268 10269 10270 // ////////////////////////////////////////////////////////////////////////// // 10271 private: 10272 enum FONS_SCRATCH_BUF_SIZE = 64000; 10273 enum FONS_HASH_LUT_SIZE = 256; 10274 enum FONS_INIT_FONTS = 4; 10275 enum FONS_INIT_GLYPHS = 256; 10276 enum FONS_INIT_ATLAS_NODES = 256; 10277 enum FONS_VERTEX_COUNT = 1024; 10278 enum FONS_MAX_STATES = 20; 10279 enum FONS_MAX_FALLBACKS = 20; 10280 10281 10282 struct FONSglyph { 10283 uint codepoint; 10284 int index; 10285 int next; 10286 short size, blur; 10287 short x0, y0, x1, y1; 10288 short xadv, xoff, yoff; 10289 } 10290 10291 // refcounted 10292 struct FONSfontData { 10293 ubyte* data; 10294 int dataSize; 10295 bool freeData; 10296 int rc; 10297 10298 @disable this (this); // no copies 10299 void opAssign() (in auto ref FONSfontData a) { static assert(0, "no copies!"); } 10300 } 10301 10302 // won't set rc to 1 10303 FONSfontData* fons__createFontData (ubyte* adata, int asize, bool afree) nothrow @trusted @nogc { 10304 import core.stdc.stdlib : malloc; 10305 assert(adata !is null); 10306 assert(asize > 0); 10307 auto res = cast(FONSfontData*)malloc(FONSfontData.sizeof); 10308 if (res is null) assert(0, "FONS: out of memory"); 10309 res.data = adata; 10310 res.dataSize = asize; 10311 res.freeData = afree; 10312 res.rc = 0; 10313 return res; 10314 } 10315 10316 void incref (FONSfontData* fd) pure nothrow @trusted @nogc { 10317 pragma(inline, true); 10318 if (fd !is null) ++fd.rc; 10319 } 10320 10321 void decref (ref FONSfontData* fd) nothrow @trusted @nogc { 10322 if (fd !is null) { 10323 if (--fd.rc == 0) { 10324 import core.stdc.stdlib : free; 10325 if (fd.freeData && fd.data !is null) { 10326 free(fd.data); 10327 fd.data = null; 10328 } 10329 free(fd); 10330 fd = null; 10331 } 10332 } 10333 } 10334 10335 // as creating and destroying fonts is a rare operation, malloc some data 10336 struct FONSfont { 10337 FONSttFontImpl font; 10338 char* name; // malloced, strz, always lowercase 10339 uint namelen; 10340 uint namehash; 10341 char* path; // malloced, strz 10342 FONSfontData* fdata; 10343 float ascender; 10344 float descender; 10345 float lineh; 10346 FONSglyph* glyphs; 10347 int cglyphs; 10348 int nglyphs; 10349 int[FONS_HASH_LUT_SIZE] lut; 10350 int[FONS_MAX_FALLBACKS] fallbacks; 10351 int nfallbacks; 10352 10353 @disable this (this); 10354 void opAssign() (in auto ref FONSfont a) { static assert(0, "no copies"); } 10355 10356 static uint djbhash (const(void)[] s) pure nothrow @safe @nogc { 10357 uint hash = 5381; 10358 foreach (ubyte b; cast(const(ubyte)[])s) { 10359 if (b >= 'A' && b <= 'Z') b += 32; // poor man's tolower 10360 hash = ((hash<<5)+hash)+b; 10361 } 10362 return hash; 10363 } 10364 10365 // except glyphs 10366 void freeMemory () nothrow @trusted @nogc { 10367 import core.stdc.stdlib : free; 10368 if (name !is null) { free(name); name = null; } 10369 namelen = namehash = 0; 10370 if (path !is null) { free(path); path = null; } 10371 fdata.decref(); 10372 } 10373 10374 // this also calcs name hash 10375 void setName (const(char)[] aname) nothrow @trusted @nogc { 10376 //{ import core.stdc.stdio; printf("setname: [%.*s]\n", cast(uint)aname.length, aname.ptr); } 10377 import core.stdc.stdlib : realloc; 10378 if (aname.length > int.max/32) assert(0, "FONS: invalid font name"); 10379 namelen = cast(uint)aname.length; 10380 name = cast(char*)realloc(name, namelen+1); 10381 if (name is null) assert(0, "FONS: out of memory"); 10382 if (aname.length) name[0..aname.length] = aname[]; 10383 name[namelen] = 0; 10384 // lowercase it 10385 foreach (ref char ch; name[0..namelen]) if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower 10386 namehash = djbhash(name[0..namelen]); 10387 //{ import core.stdc.stdio; printf(" [%s] [%.*s] [0x%08x]\n", name, namelen, name, namehash); } 10388 } 10389 10390 void setPath (const(char)[] apath) nothrow @trusted @nogc { 10391 import core.stdc.stdlib : realloc; 10392 if (apath.length > int.max/32) assert(0, "FONS: invalid font path"); 10393 path = cast(char*)realloc(path, apath.length+1); 10394 if (path is null) assert(0, "FONS: out of memory"); 10395 if (apath.length) path[0..apath.length] = apath[]; 10396 path[apath.length] = 0; 10397 } 10398 10399 // this won't check hash 10400 bool nameEqu (const(char)[] aname) const pure nothrow @trusted @nogc { 10401 //{ import core.stdc.stdio; printf("nameEqu: aname=[%.*s]; namelen=%u; aslen=%u\n", cast(uint)aname.length, aname.ptr, namelen, cast(uint)aname.length); } 10402 if (namelen != aname.length) return false; 10403 const(char)* ns = name; 10404 // name part 10405 foreach (char ch; aname) { 10406 if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower 10407 if (ch != *ns++) return false; 10408 } 10409 // done (length was checked earlier) 10410 return true; 10411 } 10412 10413 void clear () nothrow @trusted @nogc { 10414 import core.stdc.stdlib : free; 10415 import core.stdc.string : memset; 10416 if (glyphs !is null) free(glyphs); 10417 freeMemory(); 10418 memset(&this, 0, this.sizeof); 10419 } 10420 10421 FONSglyph* allocGlyph () nothrow @trusted @nogc { 10422 if (nglyphs+1 > cglyphs) { 10423 import core.stdc.stdlib : realloc; 10424 cglyphs = (cglyphs == 0 ? 8 : cglyphs*2); 10425 glyphs = cast(FONSglyph*)realloc(glyphs, FONSglyph.sizeof*cglyphs); 10426 if (glyphs is null) assert(0, "FontStash: out of memory"); 10427 } 10428 ++nglyphs; 10429 return &glyphs[nglyphs-1]; 10430 } 10431 } 10432 10433 void kill (ref FONSfont* font) nothrow @trusted @nogc { 10434 if (font !is null) { 10435 import core.stdc.stdlib : free; 10436 font.clear(); 10437 free(font); 10438 font = null; 10439 } 10440 } 10441 10442 10443 // ////////////////////////////////////////////////////////////////////////// // 10444 struct FONSstate { 10445 int font; 10446 NVGTextAlign talign; 10447 float size = 0; 10448 float blur = 0; 10449 float spacing = 0; 10450 } 10451 10452 10453 // ////////////////////////////////////////////////////////////////////////// // 10454 // atlas based on Skyline Bin Packer by Jukka Jylänki 10455 alias FONSAtlas = FONSatlasInternal*; 10456 10457 struct FONSatlasInternal { 10458 static struct Node { 10459 short x, y, width; 10460 } 10461 10462 int width, height; 10463 Node* nodes; 10464 int nnodes; 10465 int cnodes; 10466 10467 @disable this (this); 10468 void opAssign() (in auto ref FONSatlasInternal a) { static assert(0, "no copies"); } 10469 10470 nothrow @trusted @nogc: 10471 static FONSAtlas create (int w, int h, int nnodes) { 10472 import core.stdc.stdlib : malloc; 10473 import core.stdc.string : memset; 10474 10475 FONSAtlas atlas = cast(FONSAtlas)malloc(FONSatlasInternal.sizeof); 10476 if (atlas is null) assert(0, "FontStash: out of memory"); 10477 memset(atlas, 0, FONSatlasInternal.sizeof); 10478 10479 atlas.width = w; 10480 atlas.height = h; 10481 10482 // allocate space for skyline nodes 10483 atlas.nodes = cast(Node*)malloc(Node.sizeof*nnodes); 10484 if (atlas.nodes is null) assert(0, "FontStash: out of memory"); 10485 memset(atlas.nodes, 0, Node.sizeof*nnodes); 10486 atlas.nnodes = 0; 10487 atlas.cnodes = nnodes; 10488 10489 // init root node 10490 atlas.nodes[0].x = 0; 10491 atlas.nodes[0].y = 0; 10492 atlas.nodes[0].width = cast(short)w; 10493 ++atlas.nnodes; 10494 10495 return atlas; 10496 } 10497 10498 void clear () { 10499 import core.stdc.stdlib : free; 10500 import core.stdc.string : memset; 10501 10502 if (nodes !is null) free(nodes); 10503 memset(&this, 0, this.sizeof); 10504 } 10505 10506 void insertNode (int idx, int x, int y, int w) { 10507 if (nnodes+1 > cnodes) { 10508 import core.stdc.stdlib : realloc; 10509 cnodes = (cnodes == 0 ? 8 : cnodes*2); 10510 nodes = cast(Node*)realloc(nodes, Node.sizeof*cnodes); 10511 if (nodes is null) assert(0, "FontStash: out of memory"); 10512 } 10513 for (int i = nnodes; i > idx; --i) nodes[i] = nodes[i-1]; 10514 nodes[idx].x = cast(short)x; 10515 nodes[idx].y = cast(short)y; 10516 nodes[idx].width = cast(short)w; 10517 ++nnodes; 10518 } 10519 10520 void removeNode (int idx) { 10521 if (nnodes == 0) return; 10522 foreach (immutable int i; idx+1..nnodes) nodes[i-1] = nodes[i]; 10523 --nnodes; 10524 } 10525 10526 // insert node for empty space 10527 void expand (int w, int h) { 10528 if (w > width) insertNode(nnodes, width, 0, w-width); 10529 width = w; 10530 height = h; 10531 } 10532 10533 void reset (int w, int h) { 10534 width = w; 10535 height = h; 10536 nnodes = 0; 10537 // init root node 10538 nodes[0].x = 0; 10539 nodes[0].y = 0; 10540 nodes[0].width = cast(short)w; 10541 ++nnodes; 10542 } 10543 10544 void addSkylineLevel (int idx, int x, int y, int w, int h) { 10545 insertNode(idx, x, y+h, w); 10546 10547 // delete skyline segments that fall under the shadow of the new segment 10548 for (int i = idx+1; i < nnodes; ++i) { 10549 if (nodes[i].x < nodes[i-1].x+nodes[i-1].width) { 10550 int shrink = nodes[i-1].x+nodes[i-1].width-nodes[i].x; 10551 nodes[i].x += cast(short)shrink; 10552 nodes[i].width -= cast(short)shrink; 10553 if (nodes[i].width <= 0) { 10554 removeNode(i); 10555 --i; 10556 } else { 10557 break; 10558 } 10559 } else { 10560 break; 10561 } 10562 } 10563 10564 // Merge same height skyline segments that are next to each other 10565 for (int i = 0; i < nnodes-1; ++i) { 10566 if (nodes[i].y == nodes[i+1].y) { 10567 nodes[i].width += nodes[i+1].width; 10568 removeNode(i+1); 10569 --i; 10570 } 10571 } 10572 } 10573 10574 // checks if there is enough space at the location of skyline span 'i', 10575 // and return the max height of all skyline spans under that at that location, 10576 // (think tetris block being dropped at that position); or -1 if no space found 10577 int rectFits (int i, int w, int h) { 10578 int x = nodes[i].x; 10579 int y = nodes[i].y; 10580 if (x+w > width) return -1; 10581 int spaceLeft = w; 10582 while (spaceLeft > 0) { 10583 if (i == nnodes) return -1; 10584 y = nvg__max(y, nodes[i].y); 10585 if (y+h > height) return -1; 10586 spaceLeft -= nodes[i].width; 10587 ++i; 10588 } 10589 return y; 10590 } 10591 10592 bool addRect (int rw, int rh, int* rx, int* ry) { 10593 int besth = height, bestw = width, besti = -1; 10594 int bestx = -1, besty = -1; 10595 10596 // Bottom left fit heuristic. 10597 for (int i = 0; i < nnodes; ++i) { 10598 int y = rectFits(i, rw, rh); 10599 if (y != -1) { 10600 if (y+rh < besth || (y+rh == besth && nodes[i].width < bestw)) { 10601 besti = i; 10602 bestw = nodes[i].width; 10603 besth = y+rh; 10604 bestx = nodes[i].x; 10605 besty = y; 10606 } 10607 } 10608 } 10609 10610 if (besti == -1) return false; 10611 10612 // perform the actual packing 10613 addSkylineLevel(besti, bestx, besty, rw, rh); 10614 10615 *rx = bestx; 10616 *ry = besty; 10617 10618 return true; 10619 } 10620 } 10621 10622 void kill (ref FONSAtlas atlas) nothrow @trusted @nogc { 10623 if (atlas !is null) { 10624 import core.stdc.stdlib : free; 10625 atlas.clear(); 10626 free(atlas); 10627 atlas = null; 10628 } 10629 } 10630 10631 10632 // ////////////////////////////////////////////////////////////////////////// // 10633 /// FontStash context (internal definition). Don't use it derectly, it was made public only to generate documentation. 10634 /// Group: font_stash 10635 public struct FONScontextInternal { 10636 private: 10637 FONSParams params; 10638 float itw, ith; 10639 ubyte* texData; 10640 int[4] dirtyRect; 10641 FONSfont** fonts; // actually, a simple hash table; can't grow yet 10642 int cfonts; // allocated 10643 int nfonts; // used (so we can track hash table stats) 10644 int* hashidx; // [hsize] items; holds indicies in [fonts] array 10645 int hused, hsize;// used items and total items in [hashidx] 10646 FONSAtlas atlas; 10647 ubyte* scratch; 10648 int nscratch; 10649 FONSstate[FONS_MAX_STATES] states; 10650 int nstates; 10651 10652 void delegate (FONSError error, int val) nothrow @trusted @nogc handleError; 10653 10654 @disable this (this); 10655 void opAssign() (in auto ref FONScontextInternal ctx) { static assert(0, "FONS copying is not allowed"); } 10656 10657 private: 10658 static bool strequci (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 10659 if (s0.length != s1.length) return false; 10660 const(char)* sp0 = s0.ptr; 10661 const(char)* sp1 = s1.ptr; 10662 foreach (immutable _; 0..s0.length) { 10663 char c0 = *sp0++; 10664 char c1 = *sp1++; 10665 if (c0 != c1) { 10666 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man tolower 10667 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man tolower 10668 if (c0 != c1) return false; 10669 } 10670 } 10671 return true; 10672 } 10673 10674 inout(FONSstate)* getState () inout pure nothrow @trusted @nogc return { 10675 pragma(inline, true); 10676 return cast(inout)(&states[(nstates > 0 ? nstates-1 : 0)]); 10677 } 10678 10679 // simple linear probing; returns [FONS_INVALID] if not found 10680 int findNameInHash (const(char)[] name) const pure nothrow @trusted @nogc { 10681 if (nfonts == 0) return FONS_INVALID; 10682 auto nhash = FONSfont.djbhash(name); 10683 //{ import core.stdc.stdio; printf("findinhash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10684 auto res = nhash%hsize; 10685 // hash will never be 100% full, so this loop is safe 10686 for (;;) { 10687 int idx = hashidx[res]; 10688 if (idx == -1) break; 10689 auto font = fonts[idx]; 10690 if (font is null) assert(0, "FONS internal error"); 10691 if (font.namehash == nhash && font.nameEqu(name)) return idx; 10692 //{ import core.stdc.stdio; printf("findinhash chained: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10693 res = (res+1)%hsize; 10694 } 10695 return FONS_INVALID; 10696 } 10697 10698 // should be called $(B before) freeing `fonts[fidx]` 10699 void removeIndexFromHash (int fidx) nothrow @trusted @nogc { 10700 if (fidx < 0 || fidx >= nfonts) assert(0, "FONS internal error"); 10701 if (fonts[fidx] is null) assert(0, "FONS internal error"); 10702 if (hused != nfonts) assert(0, "FONS internal error"); 10703 auto nhash = fonts[fidx].namehash; 10704 auto res = nhash%hsize; 10705 // hash will never be 100% full, so this loop is safe 10706 for (;;) { 10707 int idx = hashidx[res]; 10708 if (idx == -1) assert(0, "FONS INTERNAL ERROR"); 10709 if (idx == fidx) { 10710 // i found her! copy rest here 10711 int nidx = (res+1)%hsize; 10712 for (;;) { 10713 if ((hashidx[res] = hashidx[nidx]) == -1) break; // so it will copy `-1` too 10714 res = nidx; 10715 nidx = (nidx+1)%hsize; 10716 } 10717 return; 10718 } 10719 res = (res+1)%hsize; 10720 } 10721 } 10722 10723 // add font with the given index to hash 10724 // prerequisite: font should not exists in hash 10725 void addIndexToHash (int idx) nothrow @trusted @nogc { 10726 if (idx < 0 || idx >= nfonts) assert(0, "FONS internal error"); 10727 if (fonts[idx] is null) assert(0, "FONS internal error"); 10728 import core.stdc.stdlib : realloc; 10729 auto nhash = fonts[idx].namehash; 10730 //{ import core.stdc.stdio; printf("addtohash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10731 // allocate new hash table if there was none 10732 if (hsize == 0) { 10733 enum InitSize = 256; 10734 auto newlist = cast(int*)realloc(null, InitSize*hashidx[0].sizeof); 10735 if (newlist is null) assert(0, "FONS: out of memory"); 10736 newlist[0..InitSize] = -1; 10737 hsize = InitSize; 10738 hused = 0; 10739 hashidx = newlist; 10740 } 10741 int res = cast(int)(nhash%hsize); 10742 // need to rehash? we want our hash table 50% full at max 10743 if (hashidx[res] != -1 && hused >= hsize/2) { 10744 uint nsz = hsize*2; 10745 if (nsz > 1024*1024) assert(0, "FONS: out of memory for fonts"); 10746 auto newlist = cast(int*)realloc(fonts, nsz*hashidx[0].sizeof); 10747 if (newlist is null) assert(0, "FONS: out of memory"); 10748 newlist[0..nsz] = -1; 10749 hused = 0; 10750 // rehash 10751 foreach (immutable fidx, FONSfont* ff; fonts[0..nfonts]) { 10752 if (ff is null) continue; 10753 // find slot for this font (guaranteed to have one) 10754 uint newslot = ff.namehash%nsz; 10755 while (newlist[newslot] != -1) newslot = (newslot+1)%nsz; 10756 newlist[newslot] = cast(int)fidx; 10757 ++hused; 10758 } 10759 hsize = nsz; 10760 hashidx = newlist; 10761 // we added everything, including [idx], so nothing more to do here 10762 } else { 10763 // find slot (guaranteed to have one) 10764 while (hashidx[res] != -1) res = (res+1)%hsize; 10765 // i found her! 10766 hashidx[res] = idx; 10767 ++hused; 10768 } 10769 } 10770 10771 void addWhiteRect (int w, int h) nothrow @trusted @nogc { 10772 int gx, gy; 10773 ubyte* dst; 10774 10775 if (!atlas.addRect(w, h, &gx, &gy)) return; 10776 10777 // Rasterize 10778 dst = &texData[gx+gy*params.width]; 10779 foreach (int y; 0..h) { 10780 foreach (int x; 0..w) { 10781 dst[x] = 0xff; 10782 } 10783 dst += params.width; 10784 } 10785 10786 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], gx); 10787 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], gy); 10788 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], gx+w); 10789 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], gy+h); 10790 } 10791 10792 // returns fid, not hash slot 10793 int allocFontAt (int atidx) nothrow @trusted @nogc { 10794 if (atidx >= 0 && atidx >= nfonts) assert(0, "internal NanoVega fontstash error"); 10795 10796 if (atidx < 0) { 10797 if (nfonts >= cfonts) { 10798 import core.stdc.stdlib : realloc; 10799 import core.stdc.string : memset; 10800 assert(nfonts == cfonts); 10801 int newsz = cfonts+64; 10802 if (newsz > 65535) assert(0, "FONS: too many fonts"); 10803 auto newlist = cast(FONSfont**)realloc(fonts, newsz*(FONSfont*).sizeof); 10804 if (newlist is null) assert(0, "FONS: out of memory"); 10805 memset(newlist+cfonts, 0, (newsz-cfonts)*(FONSfont*).sizeof); 10806 fonts = newlist; 10807 cfonts = newsz; 10808 } 10809 assert(nfonts < cfonts); 10810 } 10811 10812 FONSfont* font = cast(FONSfont*)malloc(FONSfont.sizeof); 10813 if (font is null) assert(0, "FONS: out of memory"); 10814 memset(font, 0, FONSfont.sizeof); 10815 10816 font.glyphs = cast(FONSglyph*)malloc(FONSglyph.sizeof*FONS_INIT_GLYPHS); 10817 if (font.glyphs is null) assert(0, "FONS: out of memory"); 10818 font.cglyphs = FONS_INIT_GLYPHS; 10819 font.nglyphs = 0; 10820 10821 if (atidx < 0) { 10822 fonts[nfonts] = font; 10823 return nfonts++; 10824 } else { 10825 fonts[atidx] = font; 10826 return atidx; 10827 } 10828 } 10829 10830 // 0: ooops 10831 int findGlyphForCP (FONSfont *font, dchar dch, FONSfont** renderfont) nothrow @trusted @nogc { 10832 if (renderfont !is null) *renderfont = font; 10833 if (font is null || font.fdata is null) return 0; 10834 auto g = fons__tt_getGlyphIndex(&font.font, cast(uint)dch); 10835 // try to find the glyph in fallback fonts 10836 if (g == 0) { 10837 foreach (immutable i; 0..font.nfallbacks) { 10838 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]]; 10839 if (fallbackFont !is null) { 10840 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, cast(uint)dch); 10841 if (fallbackIndex != 0) { 10842 if (renderfont !is null) *renderfont = fallbackFont; 10843 return g; 10844 } 10845 } 10846 } 10847 // no char, try to find replacement one 10848 if (dch != 0xFFFD) { 10849 g = fons__tt_getGlyphIndex(&font.font, 0xFFFD); 10850 if (g == 0) { 10851 foreach (immutable i; 0..font.nfallbacks) { 10852 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]]; 10853 if (fallbackFont !is null) { 10854 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, 0xFFFD); 10855 if (fallbackIndex != 0) { 10856 if (renderfont !is null) *renderfont = fallbackFont; 10857 return g; 10858 } 10859 } 10860 } 10861 } 10862 } 10863 } 10864 return g; 10865 } 10866 10867 void clear () nothrow @trusted @nogc { 10868 import core.stdc.stdlib : free; 10869 10870 if (params.renderDelete !is null) params.renderDelete(params.userPtr); 10871 foreach (immutable int i; 0..nfonts) fonts[i].kill(); 10872 10873 if (atlas !is null) atlas.kill(); 10874 if (fonts !is null) free(fonts); 10875 if (texData !is null) free(texData); 10876 if (scratch !is null) free(scratch); 10877 if (hashidx !is null) free(hashidx); 10878 } 10879 10880 // add font from another fontstash 10881 int addCookedFont (FONSfont* font) nothrow @trusted @nogc { 10882 if (font is null || font.fdata is null) return FONS_INVALID; 10883 font.fdata.incref(); 10884 auto res = addFontWithData(font.name[0..font.namelen], font.fdata, !font.font.mono); 10885 if (res == FONS_INVALID) font.fdata.decref(); // oops 10886 return res; 10887 } 10888 10889 // fdata refcount must be already increased; it won't be changed 10890 int addFontWithData (const(char)[] name, FONSfontData* fdata, bool defAA) nothrow @trusted @nogc { 10891 int i, ascent, descent, fh, lineGap; 10892 10893 if (name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID; 10894 if (name.length > 32767) return FONS_INVALID; 10895 if (fdata is null) return FONS_INVALID; 10896 10897 // find a font with the given name 10898 int newidx; 10899 FONSfont* oldfont = null; 10900 int oldidx = findNameInHash(name); 10901 if (oldidx != FONS_INVALID) { 10902 // replacement font 10903 oldfont = fonts[oldidx]; 10904 newidx = oldidx; 10905 } else { 10906 // new font, allocate new bucket 10907 newidx = -1; 10908 } 10909 10910 newidx = allocFontAt(newidx); 10911 FONSfont* font = fonts[newidx]; 10912 font.setName(name); 10913 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; // init hash lookup 10914 font.fdata = fdata; // set the font data (don't change reference count) 10915 fons__tt_setMono(&this, &font.font, !defAA); 10916 10917 // init font 10918 nscratch = 0; 10919 if (!fons__tt_loadFont(&this, &font.font, fdata.data, fdata.dataSize)) { 10920 // we promised to not free data on error, so just clear the data store (it will be freed by the caller) 10921 font.fdata = null; 10922 font.kill(); 10923 if (oldidx != FONS_INVALID) { 10924 assert(oldidx == newidx); 10925 fonts[oldidx] = oldfont; 10926 } else { 10927 assert(newidx == nfonts-1); 10928 fonts[newidx] = null; 10929 --nfonts; 10930 } 10931 return FONS_INVALID; 10932 } else { 10933 // free old font data, if any 10934 if (oldfont !is null) oldfont.kill(); 10935 } 10936 10937 // add font to name hash 10938 if (oldidx == FONS_INVALID) addIndexToHash(newidx); 10939 10940 // store normalized line height 10941 // the real line height is got by multiplying the lineh by font size 10942 fons__tt_getFontVMetrics(&font.font, &ascent, &descent, &lineGap); 10943 fh = ascent-descent; 10944 font.ascender = cast(float)ascent/cast(float)fh; 10945 font.descender = cast(float)descent/cast(float)fh; 10946 font.lineh = cast(float)(fh+lineGap)/cast(float)fh; 10947 10948 //{ import core.stdc.stdio; printf("created font [%.*s] (idx=%d)...\n", cast(uint)name.length, name.ptr, idx); } 10949 return newidx; 10950 } 10951 10952 // isize: size*10 10953 float getVertAlign (FONSfont* font, NVGTextAlign talign, short isize) pure nothrow @trusted @nogc { 10954 if (params.isZeroTopLeft) { 10955 final switch (talign.vertical) { 10956 case NVGTextAlign.V.Top: return font.ascender*cast(float)isize/10.0f; 10957 case NVGTextAlign.V.Middle: return (font.ascender+font.descender)/2.0f*cast(float)isize/10.0f; 10958 case NVGTextAlign.V.Baseline: return 0.0f; 10959 case NVGTextAlign.V.Bottom: return font.descender*cast(float)isize/10.0f; 10960 } 10961 } else { 10962 final switch (talign.vertical) { 10963 case NVGTextAlign.V.Top: return -font.ascender*cast(float)isize/10.0f; 10964 case NVGTextAlign.V.Middle: return -(font.ascender+font.descender)/2.0f*cast(float)isize/10.0f; 10965 case NVGTextAlign.V.Baseline: return 0.0f; 10966 case NVGTextAlign.V.Bottom: return -font.descender*cast(float)isize/10.0f; 10967 } 10968 } 10969 assert(0); 10970 } 10971 10972 public: 10973 /** Create new FontStash context. It can be destroyed with `fs.kill()` later. 10974 * 10975 * Note that if you don't plan to rasterize glyphs (i.e. you will use created 10976 * FontStash only to measure text), you can simply pass `FONSParams.init`). 10977 */ 10978 static FONSContext create() (in auto ref FONSParams params) nothrow @trusted @nogc { 10979 import core.stdc.string : memcpy; 10980 10981 FONSContext stash = null; 10982 10983 // allocate memory for the font stash 10984 stash = cast(FONSContext)malloc(FONScontextInternal.sizeof); 10985 if (stash is null) goto error; 10986 memset(stash, 0, FONScontextInternal.sizeof); 10987 10988 memcpy(&stash.params, ¶ms, params.sizeof); 10989 if (stash.params.width < 1) stash.params.width = 32; 10990 if (stash.params.height < 1) stash.params.height = 32; 10991 10992 // allocate scratch buffer 10993 stash.scratch = cast(ubyte*)malloc(FONS_SCRATCH_BUF_SIZE); 10994 if (stash.scratch is null) goto error; 10995 10996 // initialize implementation library 10997 if (!fons__tt_init(stash)) goto error; 10998 10999 if (stash.params.renderCreate !is null) { 11000 if (!stash.params.renderCreate(stash.params.userPtr, stash.params.width, stash.params.height)) goto error; 11001 } 11002 11003 stash.atlas = FONSAtlas.create(stash.params.width, stash.params.height, FONS_INIT_ATLAS_NODES); 11004 if (stash.atlas is null) goto error; 11005 11006 // don't allocate space for fonts: hash manager will do that for us later 11007 //stash.cfonts = 0; 11008 //stash.nfonts = 0; 11009 11010 // create texture for the cache 11011 stash.itw = 1.0f/stash.params.width; 11012 stash.ith = 1.0f/stash.params.height; 11013 stash.texData = cast(ubyte*)malloc(stash.params.width*stash.params.height); 11014 if (stash.texData is null) goto error; 11015 memset(stash.texData, 0, stash.params.width*stash.params.height); 11016 11017 stash.dirtyRect.ptr[0] = stash.params.width; 11018 stash.dirtyRect.ptr[1] = stash.params.height; 11019 stash.dirtyRect.ptr[2] = 0; 11020 stash.dirtyRect.ptr[3] = 0; 11021 11022 // add white rect at 0, 0 for debug drawing 11023 stash.addWhiteRect(2, 2); 11024 11025 stash.pushState(); 11026 stash.clearState(); 11027 11028 return stash; 11029 11030 error: 11031 stash.kill(); 11032 return null; 11033 } 11034 11035 public: 11036 /// Add fallback font (FontStash will try to find missing glyph in all fallback fonts before giving up). 11037 bool addFallbackFont (int base, int fallback) nothrow @trusted @nogc { 11038 FONSfont* baseFont = fonts[base]; 11039 if (baseFont !is null && baseFont.nfallbacks < FONS_MAX_FALLBACKS) { 11040 baseFont.fallbacks.ptr[baseFont.nfallbacks++] = fallback; 11041 return true; 11042 } 11043 return false; 11044 } 11045 11046 @property void size (float size) nothrow @trusted @nogc { pragma(inline, true); getState.size = size; } /// Set current font size. 11047 @property float size () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.size; } /// Get current font size. 11048 11049 @property void spacing (float spacing) nothrow @trusted @nogc { pragma(inline, true); getState.spacing = spacing; } /// Set current letter spacing. 11050 @property float spacing () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.spacing; } /// Get current letter spacing. 11051 11052 @property void blur (float blur) nothrow @trusted @nogc { pragma(inline, true); getState.blur = blur; } /// Set current letter blur. 11053 @property float blur () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.blur; } /// Get current letter blur. 11054 11055 @property void textAlign (NVGTextAlign talign) nothrow @trusted @nogc { pragma(inline, true); getState.talign = talign; } /// Set current text align. 11056 @property NVGTextAlign textAlign () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.talign; } /// Get current text align. 11057 11058 @property void fontId (int font) nothrow @trusted @nogc { pragma(inline, true); getState.font = font; } /// Set current font id. 11059 @property int fontId () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.font; } /// Get current font id. 11060 11061 @property void fontId (const(char)[] name) nothrow @trusted @nogc { pragma(inline, true); getState.font = getFontByName(name); } /// Set current font using its name. 11062 11063 /// Check if FontStash has a font with the given name loaded. 11064 bool hasFont (const(char)[] name) const pure nothrow @trusted @nogc { pragma(inline, true); return (getFontByName(name) >= 0); } 11065 11066 /// Get AA for the current font, or for the specified font. 11067 bool getFontAA (int font=-1) nothrow @trusted @nogc { 11068 FONSstate* state = getState; 11069 if (font < 0) font = state.font; 11070 if (font < 0 || font >= nfonts) return false; 11071 FONSfont* f = fonts[font]; 11072 return (f !is null ? !f.font.mono : false); 11073 } 11074 11075 /// Push current state. Returns `false` if state stack overflowed. 11076 bool pushState () nothrow @trusted @nogc { 11077 if (nstates >= FONS_MAX_STATES) { 11078 if (handleError !is null) handleError(FONSError.StatesOverflow, 0); 11079 return false; 11080 } 11081 if (nstates > 0) { 11082 import core.stdc.string : memcpy; 11083 memcpy(&states[nstates], &states[nstates-1], FONSstate.sizeof); 11084 } 11085 ++nstates; 11086 return true; 11087 } 11088 11089 /// Pop current state. Returns `false` if state stack underflowed. 11090 bool popState () nothrow @trusted @nogc { 11091 if (nstates <= 1) { 11092 if (handleError !is null) handleError(FONSError.StatesUnderflow, 0); 11093 return false; 11094 } 11095 --nstates; 11096 return true; 11097 } 11098 11099 /// Clear current state (i.e. set it to some sane defaults). 11100 void clearState () nothrow @trusted @nogc { 11101 FONSstate* state = getState; 11102 state.size = 12.0f; 11103 state.font = 0; 11104 state.blur = 0; 11105 state.spacing = 0; 11106 state.talign.reset; 11107 } 11108 11109 private enum NoAlias = ":noaa"; 11110 11111 /** Add font to FontStash. 11112 * 11113 * Load scalable font from disk, and add it to FontStash. If you will try to load a font 11114 * with same name and path several times, FontStash will load it only once. Also, you can 11115 * load new disk font for any existing logical font. 11116 * 11117 * Params: 11118 * name = logical font name, that will be used to select this font later. 11119 * path = path to disk file with your font. 11120 * defAA = should FontStash use antialiased font rasterizer? 11121 * 11122 * Returns: 11123 * font id or [FONS_INVALID]. 11124 */ 11125 int addFont (const(char)[] name, const(char)[] path, bool defAA=false) nothrow @trusted { 11126 if (path.length == 0 || name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID; 11127 if (path.length > 32768) return FONS_INVALID; // arbitrary limit 11128 11129 // if font path ends with ":noaa", turn off antialiasing 11130 if (path.length >= NoAlias.length && strequci(path[$-NoAlias.length..$], NoAlias)) { 11131 path = path[0..$-NoAlias.length]; 11132 if (path.length == 0) return FONS_INVALID; 11133 defAA = false; 11134 } 11135 11136 // if font name ends with ":noaa", turn off antialiasing 11137 if (name.length > NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) { 11138 name = name[0..$-NoAlias.length]; 11139 defAA = false; 11140 } 11141 11142 // find a font with the given name 11143 int fidx = findNameInHash(name); 11144 //{ import core.stdc.stdio; printf("loading font '%.*s' [%s] (fidx=%d)...\n", cast(uint)path.length, path.ptr, fontnamebuf.ptr, fidx); } 11145 11146 int loadFontFile (const(char)[] path) { 11147 // check if existing font (if any) has the same path 11148 if (fidx >= 0) { 11149 import core.stdc.string : strlen; 11150 auto plen = (fonts[fidx].path !is null ? strlen(fonts[fidx].path) : 0); 11151 version(Posix) { 11152 //{ 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); } 11153 if (plen == path.length && fonts[fidx].path[0..plen] == path) { 11154 //{ import core.stdc.stdio; printf("*** font [%.*s] already loaded from [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)plen, path.ptr); } 11155 // i found her! 11156 return fidx; 11157 } 11158 } else { 11159 if (plen == path.length && strequci(fonts[fidx].path[0..plen], path)) { 11160 // i found her! 11161 return fidx; 11162 } 11163 } 11164 } 11165 version(Windows) { 11166 // special shitdows check: this will reject fontconfig font names (but still allow things like "c:myfont") 11167 foreach (immutable char ch; path[(path.length >= 2 && path[1] == ':' ? 2 : 0)..$]) if (ch == ':') return FONS_INVALID; 11168 } 11169 // either no such font, or different path 11170 //{ import core.stdc.stdio; printf("trying font [%.*s] from file [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)path.length, path.ptr); } 11171 int xres = FONS_INVALID; 11172 try { 11173 import core.stdc.stdlib : free, malloc; 11174 static if (NanoVegaHasIVVFS) { 11175 auto fl = VFile(path); 11176 auto dataSize = fl.size; 11177 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID; 11178 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize); 11179 if (data is null) assert(0, "out of memory in NanoVega fontstash"); 11180 scope(failure) free(data); // oops 11181 fl.rawReadExact(data[0..cast(uint)dataSize]); 11182 fl.close(); 11183 } else { 11184 import core.stdc.stdio : FILE, fopen, fclose, fread, ftell, fseek; 11185 import std.internal.cstring : tempCString; 11186 auto fl = fopen(path.tempCString, "rb"); 11187 if (fl is null) return FONS_INVALID; 11188 scope(exit) fclose(fl); 11189 if (fseek(fl, 0, 2/*SEEK_END*/) != 0) return FONS_INVALID; 11190 auto dataSize = ftell(fl); 11191 if (fseek(fl, 0, 0/*SEEK_SET*/) != 0) return FONS_INVALID; 11192 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID; 11193 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize); 11194 if (data is null) assert(0, "out of memory in NanoVega fontstash"); 11195 scope(failure) free(data); // oops 11196 ubyte* dptr = data; 11197 auto left = cast(uint)dataSize; 11198 while (left > 0) { 11199 auto rd = fread(dptr, 1, left, fl); 11200 if (rd == 0) { free(data); return FONS_INVALID; } // unexpected EOF or reading error, it doesn't matter 11201 dptr += rd; 11202 left -= rd; 11203 } 11204 } 11205 scope(failure) free(data); // oops 11206 // create font data 11207 FONSfontData* fdata = fons__createFontData(data, cast(int)dataSize, true); // free data 11208 fdata.incref(); 11209 xres = addFontWithData(name, fdata, defAA); 11210 if (xres == FONS_INVALID) { 11211 fdata.decref(); // this will free [data] and [fdata] 11212 } else { 11213 // remember path 11214 fonts[xres].setPath(path); 11215 } 11216 } catch (Exception e) { 11217 // oops; sorry 11218 } 11219 return xres; 11220 } 11221 11222 // first try direct path 11223 auto res = loadFontFile(path); 11224 // if loading failed, try fontconfig (if fontconfig is available) 11225 static if (NanoVegaHasFontConfig) { 11226 if (res == FONS_INVALID && fontconfigAvailable) { 11227 // idiotic fontconfig NEVER fails; let's skip it if `path` looks like a path 11228 bool ok = true; 11229 if (path.length > 4 && (path[$-4..$] == ".ttf" || path[$-4..$] == ".ttc")) ok = false; 11230 if (ok) { foreach (immutable char ch; path) if (ch == '/') { ok = false; break; } } 11231 if (ok) { 11232 import std.internal.cstring : tempCString; 11233 FcPattern* pat = FcNameParse(path.tempCString); 11234 if (pat !is null) { 11235 scope(exit) FcPatternDestroy(pat); 11236 if (FcConfigSubstitute(null, pat, FcMatchPattern)) { 11237 FcDefaultSubstitute(pat); 11238 // find the font 11239 FcResult result; 11240 FcPattern* font = FcFontMatch(null, pat, &result); 11241 if (font !is null) { 11242 scope(exit) FcPatternDestroy(font); 11243 char* file = null; 11244 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) { 11245 if (file !is null && file[0]) { 11246 import core.stdc.string : strlen; 11247 res = loadFontFile(file[0..strlen(file)]); 11248 } 11249 } 11250 } 11251 } 11252 } 11253 } 11254 } 11255 } 11256 return res; 11257 } 11258 11259 /** Add font to FontStash, using data from memory. 11260 * 11261 * And already loaded font to FontStash. You can replace existing logical fonts. 11262 * But note that you can't remove logical font by passing "empty" data. 11263 * 11264 * $(WARNING If [FONS_INVALID] returned, `data` won't be freed even if `freeData` is `true`.) 11265 * 11266 * Params: 11267 * name = logical font name, that will be used to select this font later. 11268 * data = font data. 11269 * dataSize = font data size. 11270 * freeData = should FontStash take ownership of the font data? 11271 * defAA = should FontStash use antialiased font rasterizer? 11272 * 11273 * Returns: 11274 * font id or [FONS_INVALID]. 11275 */ 11276 int addFontMem (const(char)[] name, ubyte* data, int dataSize, bool freeData, bool defAA=false) nothrow @trusted @nogc { 11277 if (data is null || dataSize < 16) return FONS_INVALID; 11278 FONSfontData* fdata = fons__createFontData(data, dataSize, freeData); 11279 fdata.incref(); 11280 auto res = addFontWithData(name, fdata, defAA); 11281 if (res == FONS_INVALID) { 11282 // we promised to not free data on error 11283 fdata.freeData = false; 11284 fdata.decref(); // this will free [fdata] 11285 } 11286 return res; 11287 } 11288 11289 /** Add fonts from another FontStash. 11290 * 11291 * This is more effective (and faster) than reloading fonts, because internally font data 11292 * is reference counted. 11293 */ 11294 void addFontsFrom (FONSContext source) nothrow @trusted @nogc { 11295 if (source is null) return; 11296 foreach (FONSfont* font; source.fonts[0..source.nfonts]) { 11297 if (font !is null) { 11298 auto newidx = addCookedFont(font); 11299 FONSfont* newfont = fonts[newidx]; 11300 assert(newfont !is null); 11301 assert(newfont.path is null); 11302 // copy path 11303 if (font.path !is null && font.path[0]) { 11304 import core.stdc.stdlib : malloc; 11305 import core.stdc.string : strcpy, strlen; 11306 newfont.path = cast(char*)malloc(strlen(font.path)+1); 11307 if (newfont.path is null) assert(0, "FONS: out of memory"); 11308 strcpy(newfont.path, font.path); 11309 } 11310 } 11311 } 11312 } 11313 11314 /// Returns logical font name corresponding to the given font id, or `null`. 11315 /// $(WARNING Copy returned name, as name buffer can be invalidated by next FontStash API call!) 11316 const(char)[] getNameById (int idx) const pure nothrow @trusted @nogc { 11317 if (idx < 0 || idx >= nfonts || fonts[idx] is null) return null; 11318 return fonts[idx].name[0..fonts[idx].namelen]; 11319 } 11320 11321 /// Returns font id corresponding to the given logical font name, or [FONS_INVALID]. 11322 int getFontByName (const(char)[] name) const pure nothrow @trusted @nogc { 11323 //{ import core.stdc.stdio; printf("fonsGetFontByName: [%.*s]\n", cast(uint)name.length, name.ptr); } 11324 // remove ":noaa" suffix 11325 if (name.length >= NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) { 11326 name = name[0..$-NoAlias.length]; 11327 } 11328 if (name.length == 0) return FONS_INVALID; 11329 return findNameInHash(name); 11330 } 11331 11332 /** Measures the specified text string. Parameter bounds should be a float[4], 11333 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax] 11334 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn). 11335 */ 11336 float getTextBounds(T) (float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc if (isAnyCharType!T) { 11337 FONSstate* state = getState; 11338 uint codepoint; 11339 uint utf8state = 0; 11340 FONSQuad q; 11341 FONSglyph* glyph = null; 11342 int prevGlyphIndex = -1; 11343 short isize = cast(short)(state.size*10.0f); 11344 short iblur = cast(short)state.blur; 11345 FONSfont* font; 11346 11347 if (state.font < 0 || state.font >= nfonts) return 0; 11348 font = fonts[state.font]; 11349 if (font is null || font.fdata is null) return 0; 11350 11351 float scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 11352 11353 // Align vertically. 11354 y += getVertAlign(font, state.talign, isize); 11355 11356 float minx = x, maxx = x; 11357 float miny = y, maxy = y; 11358 float startx = x; 11359 11360 foreach (T ch; str) { 11361 static if (T.sizeof == 1) { 11362 //if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue; 11363 mixin(DecUtfMixin!("utf8state", "codepoint", "(cast(ubyte)ch)")); 11364 if (utf8state) continue; 11365 } else { 11366 static if (T.sizeof == 4) { 11367 if (ch > dchar.max) ch = 0xFFFD; 11368 } 11369 codepoint = cast(uint)ch; 11370 } 11371 glyph = getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional); 11372 if (glyph !is null) { 11373 getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q); 11374 if (q.x0 < minx) minx = q.x0; 11375 if (q.x1 > maxx) maxx = q.x1; 11376 if (params.isZeroTopLeft) { 11377 if (q.y0 < miny) miny = q.y0; 11378 if (q.y1 > maxy) maxy = q.y1; 11379 } else { 11380 if (q.y1 < miny) miny = q.y1; 11381 if (q.y0 > maxy) maxy = q.y0; 11382 } 11383 prevGlyphIndex = glyph.index; 11384 } else { 11385 //{ import core.stdc.stdio; printf("NO GLYPH FOR 0x%04x\n", cast(uint)codepoint); } 11386 prevGlyphIndex = -1; 11387 } 11388 } 11389 11390 float advance = x-startx; 11391 //{ import core.stdc.stdio; printf("***: x=%g; startx=%g; advance=%g\n", cast(double)x, cast(double)startx, cast(double)advance); } 11392 11393 // Align horizontally 11394 if (state.talign.left) { 11395 // empty 11396 } else if (state.talign.right) { 11397 minx -= advance; 11398 maxx -= advance; 11399 } else if (state.talign.center) { 11400 minx -= advance*0.5f; 11401 maxx -= advance*0.5f; 11402 } 11403 11404 if (bounds.length) { 11405 if (bounds.length > 0) bounds.ptr[0] = minx; 11406 if (bounds.length > 1) bounds.ptr[1] = miny; 11407 if (bounds.length > 2) bounds.ptr[2] = maxx; 11408 if (bounds.length > 3) bounds.ptr[3] = maxy; 11409 } 11410 11411 return advance; 11412 } 11413 11414 /// Returns various font metrics. Any argument can be `null` if you aren't interested in its value. 11415 void getVertMetrics (float* ascender, float* descender, float* lineh) nothrow @trusted @nogc { 11416 FONSstate* state = getState; 11417 if (state.font < 0 || state.font >= nfonts) { 11418 if (ascender !is null) *ascender = 0; 11419 if (descender !is null) *descender = 0; 11420 if (lineh !is null) *lineh = 0; 11421 } else { 11422 FONSfont* font = fonts[state.font]; 11423 if (font is null || font.fdata is null) { 11424 if (ascender !is null) *ascender = 0; 11425 if (descender !is null) *descender = 0; 11426 if (lineh !is null) *lineh = 0; 11427 } else { 11428 short isize = cast(short)(state.size*10.0f); 11429 if (ascender !is null) *ascender = font.ascender*isize/10.0f; 11430 if (descender !is null) *descender = font.descender*isize/10.0f; 11431 if (lineh !is null) *lineh = font.lineh*isize/10.0f; 11432 } 11433 } 11434 } 11435 11436 /// Returns line bounds. Any argument can be `null` if you aren't interested in its value. 11437 void getLineBounds (float y, float* minyp, float* maxyp) nothrow @trusted @nogc { 11438 FONSfont* font; 11439 FONSstate* state = getState; 11440 short isize; 11441 11442 if (minyp !is null) *minyp = 0; 11443 if (maxyp !is null) *maxyp = 0; 11444 11445 if (state.font < 0 || state.font >= nfonts) return; 11446 font = fonts[state.font]; 11447 isize = cast(short)(state.size*10.0f); 11448 if (font is null || font.fdata is null) return; 11449 11450 y += getVertAlign(font, state.talign, isize); 11451 11452 if (params.isZeroTopLeft) { 11453 immutable float miny = y-font.ascender*cast(float)isize/10.0f; 11454 immutable float maxy = miny+font.lineh*isize/10.0f; 11455 if (minyp !is null) *minyp = miny; 11456 if (maxyp !is null) *maxyp = maxy; 11457 } else { 11458 immutable float maxy = y+font.descender*cast(float)isize/10.0f; 11459 immutable float miny = maxy-font.lineh*isize/10.0f; 11460 if (minyp !is null) *minyp = miny; 11461 if (maxyp !is null) *maxyp = maxy; 11462 } 11463 } 11464 11465 /// Returns font line height. 11466 float fontHeight () nothrow @trusted @nogc { 11467 float res = void; 11468 getVertMetrics(null, null, &res); 11469 return res; 11470 } 11471 11472 /// Returns font ascender (positive). 11473 float fontAscender () nothrow @trusted @nogc { 11474 float res = void; 11475 getVertMetrics(&res, null, null); 11476 return res; 11477 } 11478 11479 /// Returns font descender (negative). 11480 float fontDescender () nothrow @trusted @nogc { 11481 float res = void; 11482 getVertMetrics(null, &res, null); 11483 return res; 11484 } 11485 11486 //TODO: document this 11487 const(ubyte)* getTextureData (int* width, int* height) nothrow @trusted @nogc { 11488 if (width !is null) *width = params.width; 11489 if (height !is null) *height = params.height; 11490 return texData; 11491 } 11492 11493 //TODO: document this 11494 bool validateTexture (int* dirty) nothrow @trusted @nogc { 11495 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) { 11496 dirty[0] = dirtyRect.ptr[0]; 11497 dirty[1] = dirtyRect.ptr[1]; 11498 dirty[2] = dirtyRect.ptr[2]; 11499 dirty[3] = dirtyRect.ptr[3]; 11500 // reset dirty rect 11501 dirtyRect.ptr[0] = params.width; 11502 dirtyRect.ptr[1] = params.height; 11503 dirtyRect.ptr[2] = 0; 11504 dirtyRect.ptr[3] = 0; 11505 return true; 11506 } 11507 return false; 11508 } 11509 11510 //TODO: document this 11511 void errorCallback (void delegate (FONSError error, int val) nothrow @trusted @nogc callback) nothrow @trusted @nogc { 11512 handleError = callback; 11513 } 11514 11515 //TODO: document this 11516 void getAtlasSize (int* width, int* height) const pure nothrow @trusted @nogc { 11517 if (width !is null) *width = params.width; 11518 if (height !is null) *height = params.height; 11519 } 11520 11521 //TODO: document this 11522 bool expandAtlas (int width, int height) nothrow @trusted @nogc { 11523 import core.stdc.stdlib : free; 11524 import core.stdc.string : memcpy, memset; 11525 11526 int maxy = 0; 11527 ubyte* data = null; 11528 11529 width = nvg__max(width, params.width); 11530 height = nvg__max(height, params.height); 11531 11532 if (width == params.width && height == params.height) return true; 11533 11534 // Flush pending glyphs. 11535 flush(); 11536 11537 // Create new texture 11538 if (params.renderResize !is null) { 11539 if (params.renderResize(params.userPtr, width, height) == 0) return false; 11540 } 11541 // Copy old texture data over. 11542 data = cast(ubyte*)malloc(width*height); 11543 if (data is null) return 0; 11544 foreach (immutable int i; 0..params.height) { 11545 ubyte* dst = &data[i*width]; 11546 ubyte* src = &texData[i*params.width]; 11547 memcpy(dst, src, params.width); 11548 if (width > params.width) memset(dst+params.width, 0, width-params.width); 11549 } 11550 if (height > params.height) memset(&data[params.height*width], 0, (height-params.height)*width); 11551 11552 free(texData); 11553 texData = data; 11554 11555 // Increase atlas size 11556 atlas.expand(width, height); 11557 11558 // Add existing data as dirty. 11559 foreach (immutable int i; 0..atlas.nnodes) maxy = nvg__max(maxy, atlas.nodes[i].y); 11560 dirtyRect.ptr[0] = 0; 11561 dirtyRect.ptr[1] = 0; 11562 dirtyRect.ptr[2] = params.width; 11563 dirtyRect.ptr[3] = maxy; 11564 11565 params.width = width; 11566 params.height = height; 11567 itw = 1.0f/params.width; 11568 ith = 1.0f/params.height; 11569 11570 return true; 11571 } 11572 11573 //TODO: document this 11574 bool resetAtlas (int width, int height) nothrow @trusted @nogc { 11575 import core.stdc.stdlib : realloc; 11576 import core.stdc.string : memcpy, memset; 11577 11578 // flush pending glyphs 11579 flush(); 11580 11581 // create new texture 11582 if (params.renderResize !is null) { 11583 if (params.renderResize(params.userPtr, width, height) == 0) return false; 11584 } 11585 11586 // reset atlas 11587 atlas.reset(width, height); 11588 11589 // clear texture data 11590 texData = cast(ubyte*)realloc(texData, width*height); 11591 if (texData is null) assert(0, "FONS: out of memory"); 11592 memset(texData, 0, width*height); 11593 11594 // reset dirty rect 11595 dirtyRect.ptr[0] = width; 11596 dirtyRect.ptr[1] = height; 11597 dirtyRect.ptr[2] = 0; 11598 dirtyRect.ptr[3] = 0; 11599 11600 // Reset cached glyphs 11601 foreach (FONSfont* font; fonts[0..nfonts]) { 11602 if (font !is null) { 11603 font.nglyphs = 0; 11604 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; 11605 } 11606 } 11607 11608 params.width = width; 11609 params.height = height; 11610 itw = 1.0f/params.width; 11611 ith = 1.0f/params.height; 11612 11613 // Add white rect at 0, 0 for debug drawing. 11614 addWhiteRect(2, 2); 11615 11616 return true; 11617 } 11618 11619 //TODO: document this 11620 bool getPathBounds (dchar dch, float[] bounds) nothrow @trusted @nogc { 11621 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 11622 static if (is(typeof(&fons__nvg__bounds))) { 11623 FONSstate* state = getState; 11624 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; } 11625 FONSfont* font; 11626 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11627 if (g == 0) { bounds[] = 0; return false; } 11628 assert(font !is null); 11629 return fons__nvg__bounds(&font.font, g, bounds); 11630 } else { 11631 bounds[] = 0; 11632 return false; 11633 } 11634 } 11635 11636 //TODO: document this 11637 bool toPath() (NVGContext vg, dchar dch, float[] bounds=null) nothrow @trusted @nogc { 11638 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 11639 static if (is(typeof(&fons__nvg__toPath))) { 11640 if (vg is null) { bounds[] = 0; return false; } 11641 FONSstate* state = getState; 11642 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; } 11643 FONSfont* font; 11644 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11645 if (g == 0) { bounds[] = 0; return false; } 11646 assert(font !is null); 11647 return fons__nvg__toPath(vg, &font.font, g, bounds); 11648 } else { 11649 bounds[] = 0; 11650 return false; 11651 } 11652 } 11653 11654 //TODO: document this 11655 bool toOutline (dchar dch, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 11656 if (ol is null) return false; 11657 static if (is(typeof(&fons__nvg__toOutline))) { 11658 FONSstate* state = getState; 11659 if (state.font < 0 || state.font >= nfonts) return false; 11660 FONSfont* font; 11661 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11662 if (g == 0) return false; 11663 assert(font !is null); 11664 return fons__nvg__toOutline(&font.font, g, ol); 11665 } else { 11666 return false; 11667 } 11668 } 11669 11670 //TODO: document this 11671 FONSglyph* getGlyph (FONSfont* font, uint codepoint, short isize, short iblur, FONSBitmapFlag bitmapOption) nothrow @trusted @nogc { 11672 static uint fons__hashint() (uint a) pure nothrow @safe @nogc { 11673 pragma(inline, true); 11674 a += ~(a<<15); 11675 a ^= (a>>10); 11676 a += (a<<3); 11677 a ^= (a>>6); 11678 a += ~(a<<11); 11679 a ^= (a>>16); 11680 return a; 11681 } 11682 11683 // based on Exponential blur, Jani Huhtanen, 2006 11684 enum APREC = 16; 11685 enum ZPREC = 7; 11686 11687 static void fons__blurCols (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc { 11688 foreach (immutable int y; 0..h) { 11689 int z = 0; // force zero border 11690 foreach (int x; 1..w) { 11691 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC; 11692 dst[x] = cast(ubyte)(z>>ZPREC); 11693 } 11694 dst[w-1] = 0; // force zero border 11695 z = 0; 11696 for (int x = w-2; x >= 0; --x) { 11697 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC; 11698 dst[x] = cast(ubyte)(z>>ZPREC); 11699 } 11700 dst[0] = 0; // force zero border 11701 dst += dstStride; 11702 } 11703 } 11704 11705 static void fons__blurRows (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc { 11706 foreach (immutable int x; 0..w) { 11707 int z = 0; // force zero border 11708 for (int y = dstStride; y < h*dstStride; y += dstStride) { 11709 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC; 11710 dst[y] = cast(ubyte)(z>>ZPREC); 11711 } 11712 dst[(h-1)*dstStride] = 0; // force zero border 11713 z = 0; 11714 for (int y = (h-2)*dstStride; y >= 0; y -= dstStride) { 11715 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC; 11716 dst[y] = cast(ubyte)(z>>ZPREC); 11717 } 11718 dst[0] = 0; // force zero border 11719 ++dst; 11720 } 11721 } 11722 11723 static void fons__blur (ubyte* dst, int w, int h, int dstStride, int blur) nothrow @trusted @nogc { 11724 import std.math : expf = exp; 11725 if (blur < 1) return; 11726 // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity) 11727 immutable float sigma = cast(float)blur*0.57735f; // 1/sqrt(3) 11728 int alpha = cast(int)((1<<APREC)*(1.0f-expf(-2.3f/(sigma+1.0f)))); 11729 fons__blurRows(dst, w, h, dstStride, alpha); 11730 fons__blurCols(dst, w, h, dstStride, alpha); 11731 fons__blurRows(dst, w, h, dstStride, alpha); 11732 fons__blurCols(dst, w, h, dstStride, alpha); 11733 //fons__blurrows(dst, w, h, dstStride, alpha); 11734 //fons__blurcols(dst, w, h, dstStride, alpha); 11735 } 11736 11737 int advance, lsb, x0, y0, x1, y1, gx, gy; 11738 FONSglyph* glyph = null; 11739 float size = isize/10.0f; 11740 FONSfont* renderFont = font; 11741 11742 if (isize < 2) return null; 11743 if (iblur > 20) iblur = 20; 11744 int pad = iblur+2; 11745 11746 // Reset allocator. 11747 nscratch = 0; 11748 11749 // Find code point and size. 11750 uint h = fons__hashint(codepoint)&(FONS_HASH_LUT_SIZE-1); 11751 int i = font.lut.ptr[h]; 11752 while (i != -1) { 11753 //if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) return &font.glyphs[i]; 11754 if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) { 11755 glyph = &font.glyphs[i]; 11756 // Negative coordinate indicates there is no bitmap data created. 11757 if (bitmapOption == FONSBitmapFlag.Optional || (glyph.x0 >= 0 && glyph.y0 >= 0)) return glyph; 11758 // At this point, glyph exists but the bitmap data is not yet created. 11759 break; 11760 } 11761 i = font.glyphs[i].next; 11762 } 11763 11764 // Create a new glyph or rasterize bitmap data for a cached glyph. 11765 //scale = fons__tt_getPixelHeightScale(&font.font, size); 11766 int g = findGlyphForCP(font, cast(dchar)codepoint, &renderFont); 11767 // It is possible that we did not find a fallback glyph. 11768 // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph. 11769 11770 float scale = fons__tt_getPixelHeightScale(&renderFont.font, size); 11771 fons__tt_buildGlyphBitmap(&renderFont.font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1); 11772 int gw = x1-x0+pad*2; 11773 int gh = y1-y0+pad*2; 11774 11775 // Determines the spot to draw glyph in the atlas. 11776 if (bitmapOption == FONSBitmapFlag.Required) { 11777 // Find free spot for the rect in the atlas. 11778 bool added = atlas.addRect(gw, gh, &gx, &gy); 11779 if (!added && handleError !is null) { 11780 // Atlas is full, let the user to resize the atlas (or not), and try again. 11781 handleError(FONSError.AtlasFull, 0); 11782 added = atlas.addRect(gw, gh, &gx, &gy); 11783 } 11784 if (!added) return null; 11785 } else { 11786 // Negative coordinate indicates there is no bitmap data created. 11787 gx = -1; 11788 gy = -1; 11789 } 11790 11791 // Init glyph. 11792 if (glyph is null) { 11793 glyph = font.allocGlyph(); 11794 glyph.codepoint = codepoint; 11795 glyph.size = isize; 11796 glyph.blur = iblur; 11797 glyph.next = 0; 11798 11799 // Insert char to hash lookup. 11800 glyph.next = font.lut.ptr[h]; 11801 font.lut.ptr[h] = font.nglyphs-1; 11802 } 11803 glyph.index = g; 11804 glyph.x0 = cast(short)gx; 11805 glyph.y0 = cast(short)gy; 11806 glyph.x1 = cast(short)(glyph.x0+gw); 11807 glyph.y1 = cast(short)(glyph.y0+gh); 11808 glyph.xadv = cast(short)(scale*advance*10.0f); 11809 glyph.xoff = cast(short)(x0-pad); 11810 glyph.yoff = cast(short)(y0-pad); 11811 11812 if (bitmapOption == FONSBitmapFlag.Optional) return glyph; 11813 11814 // Rasterize 11815 ubyte* dst = &texData[(glyph.x0+pad)+(glyph.y0+pad)*params.width]; 11816 fons__tt_renderGlyphBitmap(&font.font, dst, gw-pad*2, gh-pad*2, params.width, scale, scale, g); 11817 11818 // Make sure there is one pixel empty border. 11819 dst = &texData[glyph.x0+glyph.y0*params.width]; 11820 foreach (immutable int y; 0..gh) { 11821 dst[y*params.width] = 0; 11822 dst[gw-1+y*params.width] = 0; 11823 } 11824 foreach (immutable int x; 0..gw) { 11825 dst[x] = 0; 11826 dst[x+(gh-1)*params.width] = 0; 11827 } 11828 11829 // Debug code to color the glyph background 11830 version(none) { 11831 foreach (immutable yy; 0..gh) { 11832 foreach (immutable xx; 0..gw) { 11833 int a = cast(int)dst[xx+yy*params.width]+42; 11834 if (a > 255) a = 255; 11835 dst[xx+yy*params.width] = cast(ubyte)a; 11836 } 11837 } 11838 } 11839 11840 // Blur 11841 if (iblur > 0) { 11842 nscratch = 0; 11843 ubyte* bdst = &texData[glyph.x0+glyph.y0*params.width]; 11844 fons__blur(bdst, gw, gh, params.width, iblur); 11845 } 11846 11847 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], glyph.x0); 11848 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], glyph.y0); 11849 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], glyph.x1); 11850 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], glyph.y1); 11851 11852 return glyph; 11853 } 11854 11855 //TODO: document this 11856 void getQuad (FONSfont* font, int prevGlyphIndex, FONSglyph* glyph, float size, float scale, float spacing, float* x, float* y, FONSQuad* q) nothrow @trusted @nogc { 11857 if (prevGlyphIndex >= 0) { 11858 immutable float adv = fons__tt_getGlyphKernAdvance(&font.font, size, prevGlyphIndex, glyph.index)/**scale*/; //k8: do we really need scale here? 11859 //if (adv != 0) { import core.stdc.stdio; printf("adv=%g (scale=%g; spacing=%g)\n", cast(double)adv, cast(double)scale, cast(double)spacing); } 11860 *x += cast(int)(adv+spacing /*+0.5f*/); //k8: for me, it looks better this way (with non-aa fonts) 11861 } 11862 11863 // Each glyph has 2px border to allow good interpolation, 11864 // one pixel to prevent leaking, and one to allow good interpolation for rendering. 11865 // Inset the texture region by one pixel for correct interpolation. 11866 immutable float xoff = cast(short)(glyph.xoff+1); 11867 immutable float yoff = cast(short)(glyph.yoff+1); 11868 immutable float x0 = cast(float)(glyph.x0+1); 11869 immutable float y0 = cast(float)(glyph.y0+1); 11870 immutable float x1 = cast(float)(glyph.x1-1); 11871 immutable float y1 = cast(float)(glyph.y1-1); 11872 11873 if (params.isZeroTopLeft) { 11874 immutable float rx = cast(float)cast(int)(*x+xoff); 11875 immutable float ry = cast(float)cast(int)(*y+yoff); 11876 11877 q.x0 = rx; 11878 q.y0 = ry; 11879 q.x1 = rx+x1-x0; 11880 q.y1 = ry+y1-y0; 11881 11882 q.s0 = x0*itw; 11883 q.t0 = y0*ith; 11884 q.s1 = x1*itw; 11885 q.t1 = y1*ith; 11886 } else { 11887 immutable float rx = cast(float)cast(int)(*x+xoff); 11888 immutable float ry = cast(float)cast(int)(*y-yoff); 11889 11890 q.x0 = rx; 11891 q.y0 = ry; 11892 q.x1 = rx+x1-x0; 11893 q.y1 = ry-y1+y0; 11894 11895 q.s0 = x0*itw; 11896 q.t0 = y0*ith; 11897 q.s1 = x1*itw; 11898 q.t1 = y1*ith; 11899 } 11900 11901 *x += cast(int)(glyph.xadv/10.0f+0.5f); 11902 } 11903 11904 void flush () nothrow @trusted @nogc { 11905 // flush texture 11906 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) { 11907 if (params.renderUpdate !is null) params.renderUpdate(params.userPtr, dirtyRect.ptr, texData); 11908 // reset dirty rect 11909 dirtyRect.ptr[0] = params.width; 11910 dirtyRect.ptr[1] = params.height; 11911 dirtyRect.ptr[2] = 0; 11912 dirtyRect.ptr[3] = 0; 11913 } 11914 } 11915 } 11916 11917 /// Free all resources used by the `stash`, and `stash` itself. 11918 /// Group: font_stash 11919 public void kill (ref FONSContext stash) nothrow @trusted @nogc { 11920 import core.stdc.stdlib : free; 11921 if (stash is null) return; 11922 stash.clear(); 11923 free(stash); 11924 stash = null; 11925 } 11926 11927 11928 // ////////////////////////////////////////////////////////////////////////// // 11929 void* fons__tmpalloc (usize size, void* up) nothrow @trusted @nogc { 11930 ubyte* ptr; 11931 FONSContext stash = cast(FONSContext)up; 11932 // 16-byte align the returned pointer 11933 size = (size+0xf)&~0xf; 11934 if (stash.nscratch+cast(int)size > FONS_SCRATCH_BUF_SIZE) { 11935 if (stash.handleError !is null) stash.handleError(FONSError.ScratchFull, stash.nscratch+cast(int)size); 11936 return null; 11937 } 11938 ptr = stash.scratch+stash.nscratch; 11939 stash.nscratch += cast(int)size; 11940 return ptr; 11941 } 11942 11943 void fons__tmpfree (void* ptr, void* up) nothrow @trusted @nogc { 11944 // empty 11945 } 11946 11947 11948 // ////////////////////////////////////////////////////////////////////////// // 11949 // Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de> 11950 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. 11951 11952 enum FONS_UTF8_ACCEPT = 0; 11953 enum FONS_UTF8_REJECT = 12; 11954 11955 static immutable ubyte[364] utf8d = [ 11956 // The first part of the table maps bytes to character classes that 11957 // to reduce the size of the transition table and create bitmasks. 11958 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, 11959 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, 11960 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, 11961 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, 11962 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, 11963 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, 11964 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, 11965 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, 11966 11967 // The second part is a transition table that maps a combination 11968 // of a state of the automaton and a character class to a state. 11969 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 11970 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, 11971 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 11972 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 11973 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 11974 ]; 11975 11976 private enum DecUtfMixin(string state, string codep, string byte_) = 11977 `{ 11978 uint type_ = utf8d.ptr[`~byte_~`]; 11979 `~codep~` = (`~state~` != FONS_UTF8_ACCEPT ? (`~byte_~`&0x3fu)|(`~codep~`<<6) : (0xff>>type_)&`~byte_~`); 11980 if ((`~state~` = utf8d.ptr[256+`~state~`+type_]) == FONS_UTF8_REJECT) { 11981 `~state~` = FONS_UTF8_ACCEPT; 11982 `~codep~` = 0xFFFD; 11983 } 11984 }`; 11985 11986 /* 11987 uint fons__decutf8 (uint* state, uint* codep, uint byte_) { 11988 pragma(inline, true); 11989 uint type = utf8d.ptr[byte_]; 11990 *codep = (*state != FONS_UTF8_ACCEPT ? (byte_&0x3fu)|(*codep<<6) : (0xff>>type)&byte_); 11991 *state = utf8d.ptr[256 + *state+type]; 11992 return *state; 11993 } 11994 */ 11995 11996 11997 // ////////////////////////////////////////////////////////////////////////// // 11998 /// This iterator can be used to do text measurement. 11999 /// $(WARNING Don't add new fonts to stash while you are iterating, or you WILL get segfault!) 12000 /// Group: font_stash 12001 public struct FONSTextBoundsIterator { 12002 private: 12003 FONSContext stash; 12004 FONSstate state; 12005 uint codepoint = 0xFFFD; 12006 uint utf8state = 0; 12007 int prevGlyphIndex = -1; 12008 short isize, iblur; 12009 float scale = 0; 12010 FONSfont* font; 12011 float startx = 0, x = 0, y = 0; 12012 float minx = 0, miny = 0, maxx = 0, maxy = 0; 12013 12014 private: 12015 void clear () nothrow @trusted @nogc { 12016 import core.stdc.string : memset; 12017 memset(&this, 0, this.sizeof); 12018 this.prevGlyphIndex = -1; 12019 this.codepoint = 0xFFFD; 12020 } 12021 12022 public: 12023 /// Initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator. 12024 this (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { reset(astash, ax, ay); } 12025 12026 /// (Re)initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator. 12027 void reset (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { 12028 clear(); 12029 12030 if (astash is null || astash.nstates == 0) return; 12031 12032 stash = astash; 12033 state = *stash.getState; 12034 12035 if (state.font < 0 || state.font >= stash.nfonts) { clear(); return; } 12036 font = stash.fonts[state.font]; 12037 if (font is null || font.fdata is null) { clear(); return; } 12038 12039 x = ax; 12040 y = ay; 12041 isize = cast(short)(state.size*10.0f); 12042 iblur = cast(short)state.blur; 12043 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 12044 12045 // align vertically 12046 y += astash.getVertAlign(font, state.talign, isize); 12047 12048 minx = maxx = x; 12049 miny = maxy = y; 12050 startx = x; 12051 } 12052 12053 /// Can this iterator be used? 12054 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null); } 12055 12056 /// Put some text into iterator, calculate new values. 12057 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { 12058 enum DoCodePointMixin = q{ 12059 glyph = stash.getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional); 12060 if (glyph !is null) { 12061 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q); 12062 if (q.x0 < minx) minx = q.x0; 12063 if (q.x1 > maxx) maxx = q.x1; 12064 if (stash.params.isZeroTopLeft) { 12065 if (q.y0 < miny) miny = q.y0; 12066 if (q.y1 > maxy) maxy = q.y1; 12067 } else { 12068 if (q.y1 < miny) miny = q.y1; 12069 if (q.y0 > maxy) maxy = q.y0; 12070 } 12071 prevGlyphIndex = glyph.index; 12072 } else { 12073 prevGlyphIndex = -1; 12074 } 12075 }; 12076 12077 if (stash is null || str.length == 0) return; // alas 12078 12079 FONSQuad q; 12080 FONSglyph* glyph; 12081 12082 static if (is(T == char)) { 12083 foreach (char ch; str) { 12084 mixin(DecUtfMixin!("utf8state", "codepoint", "cast(ubyte)ch")); 12085 if (utf8state) continue; // full char is not collected yet 12086 mixin(DoCodePointMixin); 12087 } 12088 } else { 12089 if (utf8state) { 12090 utf8state = 0; 12091 codepoint = 0xFFFD; 12092 mixin(DoCodePointMixin); 12093 } 12094 foreach (T dch; str) { 12095 static if (is(T == dchar)) { 12096 if (dch > dchar.max) dch = 0xFFFD; 12097 } 12098 codepoint = cast(uint)dch; 12099 mixin(DoCodePointMixin); 12100 } 12101 } 12102 } 12103 12104 /// Returns current advance. 12105 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null ? x-startx : 0); } 12106 12107 /// Returns current text bounds. 12108 void getBounds (ref float[4] bounds) const pure nothrow @safe @nogc { 12109 if (stash is null) { bounds[] = 0; return; } 12110 float lminx = minx, lmaxx = maxx; 12111 // align horizontally 12112 if (state.talign.left) { 12113 // empty 12114 } else if (state.talign.right) { 12115 float ca = advance; 12116 lminx -= ca; 12117 lmaxx -= ca; 12118 } else if (state.talign.center) { 12119 float ca = advance*0.5f; 12120 lminx -= ca; 12121 lmaxx -= ca; 12122 } 12123 bounds[0] = lminx; 12124 bounds[1] = miny; 12125 bounds[2] = lmaxx; 12126 bounds[3] = maxy; 12127 } 12128 12129 /// Returns current horizontal text bounds. 12130 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc { 12131 if (stash !is null) { 12132 float lminx = minx, lmaxx = maxx; 12133 // align horizontally 12134 if (state.talign.left) { 12135 // empty 12136 } else if (state.talign.right) { 12137 float ca = advance; 12138 lminx -= ca; 12139 lmaxx -= ca; 12140 } else if (state.talign.center) { 12141 float ca = advance*0.5f; 12142 lminx -= ca; 12143 lmaxx -= ca; 12144 } 12145 xmin = lminx; 12146 xmax = lmaxx; 12147 } else { 12148 xmin = xmax = 0; 12149 } 12150 } 12151 12152 /// Returns current vertical text bounds. 12153 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc { 12154 pragma(inline, true); 12155 if (stash !is null) { 12156 ymin = miny; 12157 ymax = maxy; 12158 } else { 12159 ymin = ymax = 0; 12160 } 12161 } 12162 12163 /// Returns font line height. 12164 float lineHeight () nothrow @trusted @nogc { 12165 pragma(inline, true); 12166 return (stash !is null ? stash.fonts[state.font].lineh*cast(short)(state.size*10.0f)/10.0f : 0); 12167 } 12168 12169 /// Returns font ascender (positive). 12170 float ascender () nothrow @trusted @nogc { 12171 pragma(inline, true); 12172 return (stash !is null ? stash.fonts[state.font].ascender*cast(short)(state.size*10.0f)/10.0f : 0); 12173 } 12174 12175 /// Returns font descender (negative). 12176 float descender () nothrow @trusted @nogc { 12177 pragma(inline, true); 12178 return (stash !is null ? stash.fonts[state.font].descender*cast(short)(state.size*10.0f)/10.0f : 0); 12179 } 12180 } 12181 12182 12183 // ////////////////////////////////////////////////////////////////////////// // 12184 // backgl 12185 // ////////////////////////////////////////////////////////////////////////// // 12186 import core.stdc.stdlib : malloc, realloc, free; 12187 import core.stdc.string : memcpy, memset; 12188 12189 static if (__VERSION__ < 2076) { 12190 private auto DGNoThrowNoGC(T) (scope T t) /*if (isFunctionPointer!T || isDelegate!T)*/ { 12191 import std.traits; 12192 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_; 12193 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 12194 } 12195 } 12196 12197 12198 //import arsd.simpledisplay; 12199 version(nanovg_bindbc_opengl_bindings) { 12200 import bindbc.opengl; 12201 } else version(nanovg_builtin_opengl_bindings) { 12202 import arsd.simpledisplay; 12203 12204 /++ 12205 A SimpleWindow subclass that encapsulates some nanovega defaults. You just set a `redrawNVGScene` delegate and, optionally, your nromal event handlers for simpledisplay, and the rest is set up for you. 12206 12207 History: 12208 Added January 22, 2021 (version 9.2 release) 12209 +/ 12210 public class NVGWindow : SimpleWindow { 12211 NVGContext nvg; 12212 12213 /++ 12214 12215 +/ 12216 this(int width, int height, string title) { 12217 setOpenGLContextVersion(3, 0); 12218 super(width, height, title, OpenGlOptions.yes, Resizability.allowResizing); 12219 12220 this.onClosing = delegate() { 12221 nvg.kill(); 12222 }; 12223 12224 this.visibleForTheFirstTime = delegate() { 12225 nvg = nvgCreateContext(); 12226 if(nvg is null) throw new Exception("cannot initialize NanoVega"); 12227 }; 12228 12229 this.redrawOpenGlScene = delegate() { 12230 if(redrawNVGScene is null) 12231 return; 12232 glViewport(0, 0, this.width, this.height); 12233 if(clearOnEachFrame) { 12234 glClearColor(0, 0, 0, 0); 12235 glClear(glNVGClearFlags); 12236 } 12237 12238 nvg.beginFrame(this.width, this.height); 12239 scope(exit) nvg.endFrame(); 12240 12241 redrawNVGScene(nvg); 12242 }; 12243 12244 this.setEventHandlers( 12245 &redrawOpenGlSceneNow, 12246 (KeyEvent ke) { 12247 if(ke.key == Key.Escape || ke.key == Key.Q) 12248 this.close(); 12249 } 12250 ); 12251 } 12252 12253 /++ 12254 12255 +/ 12256 bool clearOnEachFrame = true; 12257 12258 /++ 12259 12260 +/ 12261 void delegate(NVGContext nvg) redrawNVGScene; 12262 12263 /++ 12264 12265 +/ 12266 void redrawNVGSceneNow() { 12267 redrawOpenGlSceneNow(); 12268 } 12269 } 12270 12271 } else { 12272 import iv.glbinds; 12273 } 12274 12275 private: 12276 // sdpy is missing that yet 12277 static if (!is(typeof(GL_STENCIL_BUFFER_BIT))) enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 12278 12279 12280 12281 version(bindbc){ 12282 private extern(System) nothrow @nogc: 12283 // this definition doesn't exist in regular OpenGL (?) 12284 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 12285 private void nanovgInitOpenGL () { 12286 // i'm not aware of calling the load multiple times having negative side effects, so i don't do an initialization check 12287 GLSupport support = loadOpenGL(); 12288 if (support == GLSupport.noLibrary) 12289 assert(0, "OpenGL initialization failed: shared library failed to load"); 12290 else if (support == GLSupport.badLibrary) 12291 assert(0, "OpenGL initialization failed: a context-independent symbol failed to load"); 12292 else if (support == GLSupport.noContext) 12293 assert(0, "OpenGL initialization failed: a context needs to be created prior to initialization"); 12294 } 12295 } else { // OpenGL API missing from simpledisplay 12296 private void nanovgInitOpenGL () @nogc nothrow { 12297 __gshared bool initialized = false; 12298 if (initialized) return; 12299 12300 try 12301 gl3.loadDynamicLibrary(); 12302 catch(Exception) 12303 assert(0, "GL 3 failed to load"); 12304 12305 initialized = true; 12306 } 12307 } 12308 12309 12310 12311 /// Context creation flags. 12312 /// Group: context_management 12313 public enum NVGContextFlag : int { 12314 /// Nothing special, i.e. empty flag. 12315 None = 0, 12316 /// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA). 12317 Antialias = 1U<<0, 12318 /** Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little 12319 * slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. */ 12320 StencilStrokes = 1U<<1, 12321 /// Flag indicating that additional debug checks are done. 12322 Debug = 1U<<2, 12323 /// Filter (antialias) fonts 12324 FontAA = 1U<<7, 12325 /// Don't filter (antialias) fonts 12326 FontNoAA = 1U<<8, 12327 /// You can use this as a substitute for default flags, for cases like this: `nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);`. 12328 Default = 1U<<31, 12329 } 12330 12331 public enum NANOVG_GL_USE_STATE_FILTER = true; 12332 12333 /// Returns flags for glClear(). 12334 /// Group: context_management 12335 public uint glNVGClearFlags () pure nothrow @safe @nogc { 12336 pragma(inline, true); 12337 return (GL_COLOR_BUFFER_BIT|/*GL_DEPTH_BUFFER_BIT|*/GL_STENCIL_BUFFER_BIT); 12338 } 12339 12340 12341 // ////////////////////////////////////////////////////////////////////////// // 12342 private: 12343 12344 version = nanovega_shared_stencil; 12345 //version = nanovega_debug_clipping; 12346 12347 enum GLNVGuniformLoc { 12348 ViewSize, 12349 Tex, 12350 Frag, 12351 TMat, 12352 TTr, 12353 ClipTex, 12354 } 12355 12356 alias GLNVGshaderType = int; 12357 enum /*GLNVGshaderType*/ { 12358 NSVG_SHADER_FILLCOLOR, 12359 NSVG_SHADER_FILLGRAD, 12360 NSVG_SHADER_FILLIMG, 12361 NSVG_SHADER_SIMPLE, // also used for clipfill 12362 NSVG_SHADER_IMG, 12363 } 12364 12365 struct GLNVGshader { 12366 GLuint prog; 12367 GLuint frag; 12368 GLuint vert; 12369 GLint[GLNVGuniformLoc.max+1] loc; 12370 } 12371 12372 struct GLNVGtexture { 12373 int id; 12374 GLuint tex; 12375 int width, height; 12376 NVGtexture type; 12377 int flags; 12378 shared int rc; // this can be 0 with tex != 0 -- postponed deletion 12379 int nextfree; 12380 } 12381 12382 struct GLNVGblend { 12383 bool simple; 12384 GLenum srcRGB; 12385 GLenum dstRGB; 12386 GLenum srcAlpha; 12387 GLenum dstAlpha; 12388 } 12389 12390 alias GLNVGcallType = int; 12391 enum /*GLNVGcallType*/ { 12392 GLNVG_NONE = 0, 12393 GLNVG_FILL, 12394 GLNVG_CONVEXFILL, 12395 GLNVG_STROKE, 12396 GLNVG_TRIANGLES, 12397 GLNVG_AFFINE, // change affine transformation matrix 12398 GLNVG_PUSHCLIP, 12399 GLNVG_POPCLIP, 12400 GLNVG_RESETCLIP, 12401 GLNVG_CLIP_DDUMP_ON, 12402 GLNVG_CLIP_DDUMP_OFF, 12403 } 12404 12405 struct GLNVGcall { 12406 int type; 12407 int evenOdd; // for fill 12408 int image; 12409 int pathOffset; 12410 int pathCount; 12411 int triangleOffset; 12412 int triangleCount; 12413 int uniformOffset; 12414 NVGMatrix affine; 12415 GLNVGblend blendFunc; 12416 NVGClipMode clipmode; 12417 } 12418 12419 struct GLNVGpath { 12420 int fillOffset; 12421 int fillCount; 12422 int strokeOffset; 12423 int strokeCount; 12424 } 12425 12426 align(1) struct GLNVGfragUniforms { 12427 align(1): 12428 enum UNIFORM_ARRAY_SIZE = 13; 12429 // note: after modifying layout or size of uniform array, 12430 // don't forget to also update the fragment shader source! 12431 align(1) union { 12432 align(1): 12433 align(1) struct { 12434 align(1): 12435 float[12] scissorMat; // matrices are actually 3 vec4s 12436 float[12] paintMat; 12437 NVGColor innerCol; 12438 NVGColor middleCol; 12439 NVGColor outerCol; 12440 float[2] scissorExt; 12441 float[2] scissorScale; 12442 float[2] extent; 12443 float radius; 12444 float feather; 12445 float strokeMult; 12446 float strokeThr; 12447 float texType; 12448 float type; 12449 float doclip; 12450 float midp; // for gradients 12451 float unused2, unused3; 12452 } 12453 float[4][UNIFORM_ARRAY_SIZE] uniformArray; 12454 } 12455 } 12456 12457 enum GLMaskState { 12458 DontMask = -1, 12459 Uninitialized = 0, 12460 Initialized = 1, 12461 JustCleared = 2, 12462 } 12463 12464 import core.sync.mutex; 12465 __gshared Mutex GLNVGTextureLocker; 12466 shared static this() { 12467 GLNVGTextureLocker = new Mutex(); 12468 } 12469 12470 struct GLNVGcontext { 12471 private import core.thread : ThreadID; 12472 12473 GLNVGshader shader; 12474 GLNVGtexture* textures; 12475 float[2] view; 12476 int freetexid; // -1: none 12477 int ntextures; 12478 int ctextures; 12479 GLuint vertBuf; 12480 int fragSize; 12481 int flags; 12482 // FBOs for masks 12483 GLuint[NVG_MAX_STATES] fbo; 12484 GLuint[2][NVG_MAX_STATES] fboTex; // FBO textures: [0] is color, [1] is stencil 12485 int fboWidth, fboHeight; 12486 GLMaskState[NVG_MAX_STATES] maskStack; 12487 int msp; // mask stack pointer; starts from `0`; points to next free item; see below for logic description 12488 int lastClipFBO; // -666: cache invalidated; -1: don't mask 12489 int lastClipUniOfs; 12490 bool doClipUnion; // specal mode 12491 GLNVGshader shaderFillFBO; 12492 GLNVGshader shaderCopyFBO; 12493 12494 bool inFrame; // will be `true` if we can perform OpenGL operations (used in texture deletion) 12495 shared bool mustCleanTextures; // will be `true` if we should delete some textures 12496 ThreadID mainTID; 12497 uint mainFBO; 12498 12499 // Per frame buffers 12500 GLNVGcall* calls; 12501 int ccalls; 12502 int ncalls; 12503 GLNVGpath* paths; 12504 int cpaths; 12505 int npaths; 12506 NVGVertex* verts; 12507 int cverts; 12508 int nverts; 12509 ubyte* uniforms; 12510 int cuniforms; 12511 int nuniforms; 12512 NVGMatrix lastAffine; 12513 12514 // cached state 12515 static if (NANOVG_GL_USE_STATE_FILTER) { 12516 GLuint boundTexture; 12517 GLuint stencilMask; 12518 GLenum stencilFunc; 12519 GLint stencilFuncRef; 12520 GLuint stencilFuncMask; 12521 GLNVGblend blendFunc; 12522 } 12523 } 12524 12525 int glnvg__maxi() (int a, int b) { pragma(inline, true); return (a > b ? a : b); } 12526 12527 void glnvg__bindTexture (GLNVGcontext* gl, GLuint tex) nothrow @trusted @nogc { 12528 static if (NANOVG_GL_USE_STATE_FILTER) { 12529 if (gl.boundTexture != tex) { 12530 gl.boundTexture = tex; 12531 glBindTexture(GL_TEXTURE_2D, tex); 12532 } 12533 } else { 12534 glBindTexture(GL_TEXTURE_2D, tex); 12535 } 12536 } 12537 12538 void glnvg__stencilMask (GLNVGcontext* gl, GLuint mask) nothrow @trusted @nogc { 12539 static if (NANOVG_GL_USE_STATE_FILTER) { 12540 if (gl.stencilMask != mask) { 12541 gl.stencilMask = mask; 12542 glStencilMask(mask); 12543 } 12544 } else { 12545 glStencilMask(mask); 12546 } 12547 } 12548 12549 void glnvg__stencilFunc (GLNVGcontext* gl, GLenum func, GLint ref_, GLuint mask) nothrow @trusted @nogc { 12550 static if (NANOVG_GL_USE_STATE_FILTER) { 12551 if (gl.stencilFunc != func || gl.stencilFuncRef != ref_ || gl.stencilFuncMask != mask) { 12552 gl.stencilFunc = func; 12553 gl.stencilFuncRef = ref_; 12554 gl.stencilFuncMask = mask; 12555 glStencilFunc(func, ref_, mask); 12556 } 12557 } else { 12558 glStencilFunc(func, ref_, mask); 12559 } 12560 } 12561 12562 // texture id is never zero 12563 // sets refcount to one 12564 GLNVGtexture* glnvg__allocTexture (GLNVGcontext* gl) nothrow @trusted @nogc { 12565 GLNVGtexture* tex = null; 12566 12567 int tid = gl.freetexid; 12568 if (tid == -1) { 12569 if (gl.ntextures >= gl.ctextures) { 12570 assert(gl.ntextures == gl.ctextures); 12571 //pragma(msg, GLNVGtexture.sizeof*32); 12572 int ctextures = (gl.ctextures == 0 ? 32 : gl.ctextures+gl.ctextures/2); // 1.5x overallocate 12573 GLNVGtexture* textures = cast(GLNVGtexture*)realloc(gl.textures, GLNVGtexture.sizeof*ctextures); 12574 if (textures is null) assert(0, "NanoVega: out of memory for textures"); 12575 memset(&textures[gl.ctextures], 0, (ctextures-gl.ctextures)*GLNVGtexture.sizeof); 12576 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated more textures (n=%d; c=%d; nc=%d)\n", gl.ntextures, gl.ctextures, ctextures); }} 12577 gl.textures = textures; 12578 gl.ctextures = ctextures; 12579 } 12580 assert(gl.ntextures+1 <= gl.ctextures); 12581 tid = gl.ntextures++; 12582 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf(" got next free texture id %d, ntextures=%d\n", tid+1, gl.ntextures); }} 12583 } else { 12584 gl.freetexid = gl.textures[tid].nextfree; 12585 } 12586 assert(tid <= gl.ntextures); 12587 12588 assert(gl.textures[tid].id == 0); 12589 tex = &gl.textures[tid]; 12590 memset(tex, 0, (*tex).sizeof); 12591 tex.id = tid+1; 12592 tex.rc = 1; 12593 tex.nextfree = -1; 12594 12595 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated texture with id %d (%d)\n", tex.id, tid+1); }} 12596 12597 return tex; 12598 } 12599 12600 GLNVGtexture* glnvg__findTexture (GLNVGcontext* gl, int id) nothrow @trusted @nogc { 12601 if (id <= 0 || id > gl.ntextures) return null; 12602 if (gl.textures[id-1].id == 0) return null; // free one 12603 assert(gl.textures[id-1].id == id); 12604 return &gl.textures[id-1]; 12605 } 12606 12607 bool glnvg__deleteTexture (GLNVGcontext* gl, ref int id) nothrow @trusted @nogc { 12608 if (id <= 0 || id > gl.ntextures) return false; 12609 auto tx = &gl.textures[id-1]; 12610 if (tx.id == 0) { id = 0; return false; } // free one 12611 assert(tx.id == id); 12612 assert(tx.tex != 0); 12613 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("decrefing texture with id %d (%d)\n", tx.id, id); }} 12614 import core.atomic : atomicOp; 12615 if (atomicOp!"-="(tx.rc, 1) == 0) { 12616 import core.thread : ThreadID; 12617 ThreadID mytid; 12618 static if (__VERSION__ < 2076) { 12619 DGNoThrowNoGC(() { 12620 import core.thread; mytid = Thread.getThis.id; 12621 })(); 12622 } else { 12623 try { import core.thread; mytid = Thread.getThis.id; } catch (Exception e) {} 12624 } 12625 if (gl.mainTID == mytid && gl.inFrame) { 12626 // can delete it right now 12627 if ((tx.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tx.tex); 12628 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** deleted texture with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }} 12629 memset(tx, 0, (*tx).sizeof); 12630 //{ import core.stdc.stdio; printf("deleting texture with id %d\n", id); } 12631 tx.nextfree = gl.freetexid; 12632 gl.freetexid = id-1; 12633 } else { 12634 // alas, we aren't doing frame business, so we should postpone deletion 12635 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** POSTPONED texture deletion with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }} 12636 version(aliced) { 12637 { 12638 GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow; 12639 tx.id = 0; // mark it as dead 12640 gl.mustCleanTextures = true; // set "need cleanup" flag 12641 } 12642 } else { 12643 try { 12644 GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow; 12645 tx.id = 0; // mark it as dead 12646 gl.mustCleanTextures = true; // set "need cleanup" flag 12647 } catch (Exception e) {} 12648 } 12649 } 12650 } 12651 id = 0; 12652 return true; 12653 } 12654 12655 void glnvg__dumpShaderError (GLuint shader, const(char)* name, const(char)* type) nothrow @trusted @nogc { 12656 import core.stdc.stdio : fprintf, stderr; 12657 GLchar[512+1] str = 0; 12658 GLsizei len = 0; 12659 glGetShaderInfoLog(shader, 512, &len, str.ptr); 12660 if (len > 512) len = 512; 12661 str[len] = '\0'; 12662 fprintf(stderr, "Shader %s/%s error:\n%s\n", name, type, str.ptr); 12663 } 12664 12665 void glnvg__dumpProgramError (GLuint prog, const(char)* name) nothrow @trusted @nogc { 12666 import core.stdc.stdio : fprintf, stderr; 12667 GLchar[512+1] str = 0; 12668 GLsizei len = 0; 12669 glGetProgramInfoLog(prog, 512, &len, str.ptr); 12670 if (len > 512) len = 512; 12671 str[len] = '\0'; 12672 fprintf(stderr, "Program %s error:\n%s\n", name, str.ptr); 12673 } 12674 12675 void glnvg__resetError(bool force=false) (GLNVGcontext* gl) nothrow @trusted @nogc { 12676 static if (!force) { 12677 if ((gl.flags&NVGContextFlag.Debug) == 0) return; 12678 } 12679 glGetError(); 12680 } 12681 12682 void glnvg__checkError(bool force=false) (GLNVGcontext* gl, const(char)* str) nothrow @trusted @nogc { 12683 GLenum err; 12684 static if (!force) { 12685 if ((gl.flags&NVGContextFlag.Debug) == 0) return; 12686 } 12687 err = glGetError(); 12688 if (err != GL_NO_ERROR) { 12689 import core.stdc.stdio : fprintf, stderr; 12690 fprintf(stderr, "Error %08x after %s\n", err, str); 12691 return; 12692 } 12693 } 12694 12695 bool glnvg__createShader (GLNVGshader* shader, const(char)* name, const(char)* header, const(char)* opts, const(char)* vshader, const(char)* fshader) nothrow @trusted @nogc { 12696 GLint status; 12697 GLuint prog, vert, frag; 12698 const(char)*[3] str; 12699 12700 memset(shader, 0, (*shader).sizeof); 12701 12702 prog = glCreateProgram(); 12703 vert = glCreateShader(GL_VERTEX_SHADER); 12704 frag = glCreateShader(GL_FRAGMENT_SHADER); 12705 str[0] = header; 12706 str[1] = (opts !is null ? opts : ""); 12707 str[2] = vshader; 12708 glShaderSource(vert, 3, cast(const(char)**)str.ptr, null); 12709 12710 glCompileShader(vert); 12711 glGetShaderiv(vert, GL_COMPILE_STATUS, &status); 12712 if (status != GL_TRUE) { 12713 glnvg__dumpShaderError(vert, name, "vert"); 12714 return false; 12715 } 12716 12717 str[0] = header; 12718 str[1] = (opts !is null ? opts : ""); 12719 str[2] = fshader; 12720 glShaderSource(frag, 3, cast(const(char)**)str.ptr, null); 12721 12722 glCompileShader(frag); 12723 glGetShaderiv(frag, GL_COMPILE_STATUS, &status); 12724 if (status != GL_TRUE) { 12725 glnvg__dumpShaderError(frag, name, "frag"); 12726 return false; 12727 } 12728 12729 glAttachShader(prog, vert); 12730 glAttachShader(prog, frag); 12731 12732 glBindAttribLocation(prog, 0, "vertex"); 12733 glBindAttribLocation(prog, 1, "tcoord"); 12734 12735 glLinkProgram(prog); 12736 glGetProgramiv(prog, GL_LINK_STATUS, &status); 12737 if (status != GL_TRUE) { 12738 glnvg__dumpProgramError(prog, name); 12739 return false; 12740 } 12741 12742 shader.prog = prog; 12743 shader.vert = vert; 12744 shader.frag = frag; 12745 12746 return true; 12747 } 12748 12749 void glnvg__deleteShader (GLNVGshader* shader) nothrow @trusted @nogc { 12750 if (shader.prog != 0) glDeleteProgram(shader.prog); 12751 if (shader.vert != 0) glDeleteShader(shader.vert); 12752 if (shader.frag != 0) glDeleteShader(shader.frag); 12753 } 12754 12755 void glnvg__getUniforms (GLNVGshader* shader) nothrow @trusted @nogc { 12756 shader.loc[GLNVGuniformLoc.ViewSize] = glGetUniformLocation(shader.prog, "viewSize"); 12757 shader.loc[GLNVGuniformLoc.Tex] = glGetUniformLocation(shader.prog, "tex"); 12758 shader.loc[GLNVGuniformLoc.Frag] = glGetUniformLocation(shader.prog, "frag"); 12759 shader.loc[GLNVGuniformLoc.TMat] = glGetUniformLocation(shader.prog, "tmat"); 12760 shader.loc[GLNVGuniformLoc.TTr] = glGetUniformLocation(shader.prog, "ttr"); 12761 shader.loc[GLNVGuniformLoc.ClipTex] = glGetUniformLocation(shader.prog, "clipTex"); 12762 } 12763 12764 void glnvg__killFBOs (GLNVGcontext* gl) nothrow @trusted @nogc { 12765 foreach (immutable fidx, ref GLuint fbo; gl.fbo[]) { 12766 if (fbo != 0) { 12767 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); 12768 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); 12769 foreach (ref GLuint tid; gl.fboTex.ptr[fidx][]) if (tid != 0) { glDeleteTextures(1, &tid); tid = 0; } 12770 glDeleteFramebuffers(1, &fbo); 12771 fbo = 0; 12772 } 12773 } 12774 gl.fboWidth = gl.fboHeight = 0; 12775 } 12776 12777 // returns `true` is new FBO was created 12778 // will not unbind buffer, if it was created 12779 bool glnvg__allocFBO (GLNVGcontext* gl, int fidx, bool doclear=true) nothrow @trusted @nogc { 12780 assert(fidx >= 0 && fidx < gl.fbo.length); 12781 assert(gl.fboWidth > 0); 12782 assert(gl.fboHeight > 0); 12783 12784 if (gl.fbo.ptr[fidx] != 0) return false; // nothing to do, this FBO is already initialized 12785 12786 glnvg__resetError(gl); 12787 12788 // allocate FBO object 12789 GLuint fbo = 0; 12790 glGenFramebuffers(1, &fbo); 12791 if (fbo == 0) assert(0, "NanoVega: cannot create FBO"); 12792 glnvg__checkError(gl, "glnvg__allocFBO: glGenFramebuffers"); 12793 glBindFramebuffer(GL_FRAMEBUFFER, fbo); 12794 //scope(exit) glBindFramebuffer(GL_FRAMEBUFFER, 0); 12795 12796 // attach 2D texture to this FBO 12797 GLuint tidColor = 0; 12798 glGenTextures(1, &tidColor); 12799 if (tidColor == 0) assert(0, "NanoVega: cannot create RGBA texture for FBO"); 12800 glBindTexture(GL_TEXTURE_2D, tidColor); 12801 //scope(exit) glBindTexture(GL_TEXTURE_2D, 0); 12802 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 12803 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_S"); 12804 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 12805 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_T"); 12806 //FIXME: linear or nearest? 12807 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 12808 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 12809 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MIN_FILTER"); 12810 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 12811 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 12812 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MAG_FILTER"); 12813 // empty texture 12814 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 12815 // create texture with only one color channel 12816 glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, gl.fboWidth, gl.fboHeight, 0, GL_RED, GL_UNSIGNED_BYTE, null); 12817 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 12818 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (color)"); 12819 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tidColor, 0); 12820 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (color)"); 12821 12822 // attach stencil texture to this FBO 12823 GLuint tidStencil = 0; 12824 version(nanovega_shared_stencil) { 12825 if (gl.fboTex.ptr[0].ptr[0] == 0) { 12826 glGenTextures(1, &tidStencil); 12827 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO"); 12828 gl.fboTex.ptr[0].ptr[0] = tidStencil; 12829 } else { 12830 tidStencil = gl.fboTex.ptr[0].ptr[0]; 12831 } 12832 if (fidx != 0) gl.fboTex.ptr[fidx].ptr[1] = 0; // stencil texture is shared among FBOs 12833 } else { 12834 glGenTextures(1, &tidStencil); 12835 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO"); 12836 gl.fboTex.ptr[0].ptr[0] = tidStencil; 12837 } 12838 glBindTexture(GL_TEXTURE_2D, tidStencil); 12839 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, gl.fboWidth, gl.fboHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, null); 12840 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (stencil)"); 12841 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tidStencil, 0); 12842 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (stencil)"); 12843 12844 { 12845 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); 12846 if (status != GL_FRAMEBUFFER_COMPLETE) { 12847 version(all) { 12848 import core.stdc.stdio; 12849 if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) printf("fucked attachement\n"); 12850 if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) printf("fucked dimensions\n"); 12851 if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) printf("missing attachement\n"); 12852 if (status == GL_FRAMEBUFFER_UNSUPPORTED) printf("unsupported\n"); 12853 } 12854 assert(0, "NanoVega: framebuffer creation failed"); 12855 } 12856 } 12857 12858 // clear 'em all 12859 if (doclear) { 12860 glClearColor(0, 0, 0, 0); 12861 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 12862 } 12863 12864 // save texture ids 12865 gl.fbo.ptr[fidx] = fbo; 12866 gl.fboTex.ptr[fidx].ptr[0] = tidColor; 12867 version(nanovega_shared_stencil) {} else { 12868 gl.fboTex.ptr[fidx].ptr[1] = tidStencil; 12869 } 12870 12871 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture); 12872 12873 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): created with index %d\n", gl.msp-1, fidx); } 12874 12875 return true; 12876 } 12877 12878 // will not unbind buffer 12879 void glnvg__clearFBO (GLNVGcontext* gl, int fidx) nothrow @trusted @nogc { 12880 assert(fidx >= 0 && fidx < gl.fbo.length); 12881 assert(gl.fboWidth > 0); 12882 assert(gl.fboHeight > 0); 12883 assert(gl.fbo.ptr[fidx] != 0); 12884 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[fidx]); 12885 glClearColor(0, 0, 0, 0); 12886 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 12887 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cleared with index %d\n", gl.msp-1, fidx); } 12888 } 12889 12890 // will not unbind buffer 12891 void glnvg__copyFBOToFrom (GLNVGcontext* gl, int didx, int sidx) nothrow @trusted @nogc { 12892 import core.stdc.string : memset; 12893 assert(didx >= 0 && didx < gl.fbo.length); 12894 assert(sidx >= 0 && sidx < gl.fbo.length); 12895 assert(gl.fboWidth > 0); 12896 assert(gl.fboHeight > 0); 12897 assert(gl.fbo.ptr[didx] != 0); 12898 assert(gl.fbo.ptr[sidx] != 0); 12899 if (didx == sidx) return; 12900 12901 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copy FBO: %d -> %d\n", gl.msp-1, sidx, didx); } 12902 12903 glUseProgram(gl.shaderCopyFBO.prog); 12904 12905 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[didx]); 12906 glDisable(GL_CULL_FACE); 12907 glDisable(GL_BLEND); 12908 glDisable(GL_SCISSOR_TEST); 12909 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[sidx].ptr[0]); 12910 // copy texture by drawing full quad 12911 enum x = 0; 12912 enum y = 0; 12913 immutable int w = gl.fboWidth; 12914 immutable int h = gl.fboHeight; 12915 immutable(NVGVertex[4]) vertices = 12916 [NVGVertex(x, y), // top-left 12917 NVGVertex(w, y), // top-right 12918 NVGVertex(w, h), // bottom-right 12919 NVGVertex(x, h)]; // bottom-left 12920 12921 glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices); 12922 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 12923 12924 // restore state (but don't unbind FBO) 12925 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture); 12926 glEnable(GL_CULL_FACE); 12927 glEnable(GL_BLEND); 12928 glUseProgram(gl.shader.prog); 12929 } 12930 12931 void glnvg__resetFBOClipTextureCache (GLNVGcontext* gl) nothrow @trusted @nogc { 12932 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): texture cache invalidated (%d)\n", gl.msp-1, gl.lastClipFBO); } 12933 /* 12934 if (gl.lastClipFBO >= 0) { 12935 glActiveTexture(GL_TEXTURE1); 12936 glBindTexture(GL_TEXTURE_2D, 0); 12937 glActiveTexture(GL_TEXTURE0); 12938 } 12939 */ 12940 gl.lastClipFBO = -666; 12941 } 12942 12943 void glnvg__setFBOClipTexture (GLNVGcontext* gl, GLNVGfragUniforms* frag) nothrow @trusted @nogc { 12944 //assert(gl.msp > 0 && gl.msp <= gl.maskStack.length); 12945 if (gl.lastClipFBO != -666) { 12946 // cached 12947 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cached (%d)\n", gl.msp-1, gl.lastClipFBO); } 12948 frag.doclip = (gl.lastClipFBO >= 0 ? 1 : 0); 12949 return; 12950 } 12951 12952 // no cache 12953 int fboidx = -1; 12954 mainloop: foreach_reverse (immutable sp, GLMaskState mst; gl.maskStack.ptr[0..gl.msp]/*; reverse*/) { 12955 final switch (mst) { 12956 case GLMaskState.DontMask: fboidx = -1; break mainloop; 12957 case GLMaskState.Uninitialized: break; 12958 case GLMaskState.Initialized: fboidx = cast(int)sp; break mainloop; 12959 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__setFBOClipTexture()` internal error"); 12960 } 12961 } 12962 12963 if (fboidx < 0) { 12964 // don't mask 12965 gl.lastClipFBO = -1; 12966 frag.doclip = 0; 12967 } else { 12968 // do masking 12969 assert(gl.fbo.ptr[fboidx] != 0); 12970 gl.lastClipFBO = fboidx; 12971 frag.doclip = 1; 12972 } 12973 12974 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): new cache (new:%d)\n", gl.msp-1, gl.lastClipFBO); } 12975 12976 if (gl.lastClipFBO >= 0) { 12977 assert(gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 12978 glActiveTexture(GL_TEXTURE1); 12979 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 12980 glActiveTexture(GL_TEXTURE0); 12981 } 12982 } 12983 12984 // returns index in `gl.fbo`, or -1 for "don't mask" 12985 int glnvg__generateFBOClipTexture (GLNVGcontext* gl) nothrow @trusted @nogc { 12986 assert(gl.msp > 0 && gl.msp <= gl.maskStack.length); 12987 // we need initialized FBO, even for "don't mask" case 12988 // for this, look back in stack, and either copy initialized FBO, 12989 // or stop at first uninitialized one, and clear it 12990 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.Initialized) { 12991 // shortcut 12992 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); } 12993 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[gl.msp-1]); 12994 return gl.msp-1; 12995 } 12996 foreach_reverse (immutable sp; 0..gl.msp/*; reverse*/) { 12997 final switch (gl.maskStack.ptr[sp]) { 12998 case GLMaskState.DontMask: 12999 // clear it 13000 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture\n", gl.msp-1); } 13001 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1); 13002 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared; 13003 return gl.msp-1; 13004 case GLMaskState.Uninitialized: break; // do nothing 13005 case GLMaskState.Initialized: 13006 // i found her! copy to TOS 13007 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copying texture from %d\n", gl.msp-1, cast(int)sp); } 13008 glnvg__allocFBO(gl, gl.msp-1, false); 13009 glnvg__copyFBOToFrom(gl, gl.msp-1, sp); 13010 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized; 13011 return gl.msp-1; 13012 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__generateFBOClipTexture()` internal error"); 13013 } 13014 } 13015 // nothing was initialized, lol 13016 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture (first one)\n", gl.msp-1); } 13017 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1); 13018 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared; 13019 return gl.msp-1; 13020 } 13021 13022 void glnvg__renderPushClip (void* uptr) nothrow @trusted @nogc { 13023 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13024 GLNVGcall* call = glnvg__allocCall(gl); 13025 if (call is null) return; 13026 call.type = GLNVG_PUSHCLIP; 13027 } 13028 13029 void glnvg__renderPopClip (void* uptr) nothrow @trusted @nogc { 13030 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13031 GLNVGcall* call = glnvg__allocCall(gl); 13032 if (call is null) return; 13033 call.type = GLNVG_POPCLIP; 13034 } 13035 13036 void glnvg__renderResetClip (void* uptr) nothrow @trusted @nogc { 13037 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13038 GLNVGcall* call = glnvg__allocCall(gl); 13039 if (call is null) return; 13040 call.type = GLNVG_RESETCLIP; 13041 } 13042 13043 void glnvg__clipDebugDump (void* uptr, bool doit) nothrow @trusted @nogc { 13044 version(nanovega_debug_clipping) { 13045 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13046 GLNVGcall* call = glnvg__allocCall(gl); 13047 call.type = (doit ? GLNVG_CLIP_DDUMP_ON : GLNVG_CLIP_DDUMP_OFF); 13048 } 13049 } 13050 13051 bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc { 13052 import core.stdc.stdio : snprintf; 13053 13054 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13055 enum align_ = 4; 13056 13057 char[64] shaderHeader = void; 13058 //enum shaderHeader = "#define UNIFORM_ARRAY_SIZE 12\n"; 13059 snprintf(shaderHeader.ptr, shaderHeader.length, "#define UNIFORM_ARRAY_SIZE %u\n", cast(uint)GLNVGfragUniforms.UNIFORM_ARRAY_SIZE); 13060 13061 enum fillVertShader = q{ 13062 uniform vec2 viewSize; 13063 attribute vec2 vertex; 13064 attribute vec2 tcoord; 13065 varying vec2 ftcoord; 13066 varying vec2 fpos; 13067 uniform vec4 tmat; /* abcd of affine matrix: xyzw */ 13068 uniform vec2 ttr; /* tx and ty of affine matrix */ 13069 void main (void) { 13070 /* affine transformation */ 13071 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x; 13072 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y; 13073 ftcoord = tcoord; 13074 fpos = vec2(nx, ny); 13075 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1); 13076 } 13077 }; 13078 13079 enum fillFragShader = ` 13080 uniform vec4 frag[UNIFORM_ARRAY_SIZE]; 13081 uniform sampler2D tex; 13082 uniform sampler2D clipTex; 13083 uniform vec2 viewSize; 13084 varying vec2 ftcoord; 13085 varying vec2 fpos; 13086 #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz) 13087 #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz) 13088 #define innerCol frag[6] 13089 #define middleCol frag[7] 13090 #define outerCol frag[7+1] 13091 #define scissorExt frag[8+1].xy 13092 #define scissorScale frag[8+1].zw 13093 #define extent frag[9+1].xy 13094 #define radius frag[9+1].z 13095 #define feather frag[9+1].w 13096 #define strokeMult frag[10+1].x 13097 #define strokeThr frag[10+1].y 13098 #define texType int(frag[10+1].z) 13099 #define type int(frag[10+1].w) 13100 #define doclip int(frag[11+1].x) 13101 #define midp frag[11+1].y 13102 13103 float sdroundrect (in vec2 pt, in vec2 ext, in float rad) { 13104 vec2 ext2 = ext-vec2(rad, rad); 13105 vec2 d = abs(pt)-ext2; 13106 return min(max(d.x, d.y), 0.0)+length(max(d, 0.0))-rad; 13107 } 13108 13109 // Scissoring 13110 float scissorMask (in vec2 p) { 13111 vec2 sc = (abs((scissorMat*vec3(p, 1.0)).xy)-scissorExt); 13112 sc = vec2(0.5, 0.5)-sc*scissorScale; 13113 return clamp(sc.x, 0.0, 1.0)*clamp(sc.y, 0.0, 1.0); 13114 } 13115 13116 #ifdef EDGE_AA 13117 // Stroke - from [0..1] to clipped pyramid, where the slope is 1px. 13118 float strokeMask () { 13119 return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult)*min(1.0, ftcoord.y); 13120 } 13121 #endif 13122 13123 void main (void) { 13124 // clipping 13125 if (doclip != 0) { 13126 /*vec4 clr = texelFetch(clipTex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);*/ 13127 vec4 clr = texture2D(clipTex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y)); 13128 if (clr.r == 0.0) discard; 13129 } 13130 float scissor = scissorMask(fpos); 13131 if (scissor <= 0.0) discard; //k8: is it really faster? 13132 #ifdef EDGE_AA 13133 float strokeAlpha = strokeMask(); 13134 if (strokeAlpha < strokeThr) discard; 13135 #else 13136 float strokeAlpha = 1.0; 13137 #endif 13138 // rendering 13139 vec4 color; 13140 if (type == 0) { /* NSVG_SHADER_FILLCOLOR */ 13141 color = innerCol; 13142 // Combine alpha 13143 color *= strokeAlpha*scissor; 13144 } else if (type == 1) { /* NSVG_SHADER_FILLGRAD */ 13145 // Gradient 13146 // Calculate gradient color using box gradient 13147 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy; 13148 float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0); 13149 if (midp <= 0.0) { 13150 color = mix(innerCol, outerCol, d); 13151 } else { 13152 float gdst = min(midp, 1.0); 13153 if (d < gdst) { 13154 color = mix(innerCol, middleCol, d/gdst); 13155 } else { 13156 color = mix(middleCol, outerCol, (d-gdst)/gdst); 13157 } 13158 } 13159 // Combine alpha 13160 color *= strokeAlpha*scissor; 13161 } else if (type == 2) { /* NSVG_SHADER_FILLIMG */ 13162 // Image 13163 // Calculate color from texture 13164 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy/extent; 13165 color = texture2D(tex, pt); 13166 if (texType == 1) color = vec4(color.xyz*color.w, color.w); 13167 if (texType == 2) color = vec4(color.x); 13168 // Apply color tint and alpha 13169 color *= innerCol; 13170 // Combine alpha 13171 color *= strokeAlpha*scissor; 13172 } else if (type == 3) { /* NSVG_SHADER_SIMPLE */ 13173 // Stencil fill 13174 color = vec4(1, 1, 1, 1); 13175 } else if (type == 4) { /* NSVG_SHADER_IMG */ 13176 // Textured tris 13177 color = texture2D(tex, ftcoord); 13178 if (texType == 1) color = vec4(color.xyz*color.w, color.w); 13179 if (texType == 2) color = vec4(color.x); 13180 color *= scissor; 13181 color *= innerCol; // Apply color tint 13182 } 13183 gl_FragColor = color; 13184 } 13185 `; 13186 13187 enum clipVertShaderFill = q{ 13188 uniform vec2 viewSize; 13189 attribute vec2 vertex; 13190 uniform vec4 tmat; /* abcd of affine matrix: xyzw */ 13191 uniform vec2 ttr; /* tx and ty of affine matrix */ 13192 void main (void) { 13193 /* affine transformation */ 13194 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x; 13195 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y; 13196 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1); 13197 } 13198 }; 13199 13200 enum clipFragShaderFill = q{ 13201 uniform vec2 viewSize; 13202 void main (void) { 13203 gl_FragColor = vec4(1, 1, 1, 1); 13204 } 13205 }; 13206 13207 enum clipVertShaderCopy = q{ 13208 uniform vec2 viewSize; 13209 attribute vec2 vertex; 13210 void main (void) { 13211 gl_Position = vec4(2.0*vertex.x/viewSize.x-1.0, 1.0-2.0*vertex.y/viewSize.y, 0, 1); 13212 } 13213 }; 13214 13215 enum clipFragShaderCopy = q{ 13216 uniform sampler2D tex; 13217 uniform vec2 viewSize; 13218 void main (void) { 13219 //gl_FragColor = texelFetch(tex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0); 13220 gl_FragColor = texture2D(tex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y)); 13221 } 13222 }; 13223 13224 glnvg__checkError(gl, "init"); 13225 13226 string defines = (gl.flags&NVGContextFlag.Antialias ? "#define EDGE_AA 1\n" : null); 13227 if (!glnvg__createShader(&gl.shader, "shader", shaderHeader.ptr, defines.ptr, fillVertShader, fillFragShader)) return false; 13228 if (!glnvg__createShader(&gl.shaderFillFBO, "shaderFillFBO", shaderHeader.ptr, defines.ptr, clipVertShaderFill, clipFragShaderFill)) return false; 13229 if (!glnvg__createShader(&gl.shaderCopyFBO, "shaderCopyFBO", shaderHeader.ptr, defines.ptr, clipVertShaderCopy, clipFragShaderCopy)) return false; 13230 13231 glnvg__checkError(gl, "uniform locations"); 13232 glnvg__getUniforms(&gl.shader); 13233 glnvg__getUniforms(&gl.shaderFillFBO); 13234 glnvg__getUniforms(&gl.shaderCopyFBO); 13235 13236 // Create dynamic vertex array 13237 glGenBuffers(1, &gl.vertBuf); 13238 13239 gl.fragSize = GLNVGfragUniforms.sizeof+align_-GLNVGfragUniforms.sizeof%align_; 13240 13241 glnvg__checkError(gl, "create done"); 13242 13243 glFinish(); 13244 13245 return true; 13246 } 13247 13248 int glnvg__renderCreateTexture (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc { 13249 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13250 GLNVGtexture* tex = glnvg__allocTexture(gl); 13251 13252 if (tex is null) return 0; 13253 13254 glGenTextures(1, &tex.tex); 13255 tex.width = w; 13256 tex.height = h; 13257 tex.type = type; 13258 tex.flags = imageFlags; 13259 glnvg__bindTexture(gl, tex.tex); 13260 13261 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("created texture with id %d; glid=%u\n", tex.id, tex.tex); }} 13262 13263 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 13264 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width); 13265 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13266 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13267 13268 13269 13270 immutable ttype = (type == NVGtexture.RGBA ? GL_RGBA : GL_RED); 13271 glTexImage2D(GL_TEXTURE_2D, 0, ttype, w, h, 0, ttype, GL_UNSIGNED_BYTE, data); 13272 // GL 3.0 and later have support for a dedicated function for generating mipmaps 13273 // it needs to be called after the glTexImage2D call 13274 if (imageFlags & NVGImageFlag.GenerateMipmaps) 13275 glGenerateMipmap(GL_TEXTURE_2D); 13276 13277 immutable tfmin = 13278 (imageFlags & NVGImageFlag.GenerateMipmaps ? GL_LINEAR_MIPMAP_LINEAR : 13279 imageFlags & NVGImageFlag.NoFiltering ? GL_NEAREST : 13280 GL_LINEAR); 13281 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.0); 13282 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tfmin); 13283 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST : GL_LINEAR)); 13284 13285 int flag; 13286 if (imageFlags&NVGImageFlag.RepeatX) 13287 flag = GL_REPEAT; 13288 else if (imageFlags&NVGImageFlag.ClampToBorderX) 13289 flag = GL_CLAMP_TO_BORDER; 13290 else 13291 flag = GL_CLAMP_TO_EDGE; 13292 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, flag); 13293 13294 13295 if (imageFlags&NVGImageFlag.RepeatY) 13296 flag = GL_REPEAT; 13297 else if (imageFlags&NVGImageFlag.ClampToBorderY) 13298 flag = GL_CLAMP_TO_BORDER; 13299 else 13300 flag = GL_CLAMP_TO_EDGE; 13301 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, flag); 13302 13303 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 13304 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 13305 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13306 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13307 13308 glnvg__checkError(gl, "create tex"); 13309 glnvg__bindTexture(gl, 0); 13310 13311 return tex.id; 13312 } 13313 13314 bool glnvg__renderDeleteTexture (void* uptr, int image) nothrow @trusted @nogc { 13315 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13316 return glnvg__deleteTexture(gl, image); 13317 } 13318 13319 bool glnvg__renderTextureIncRef (void* uptr, int image) nothrow @trusted @nogc { 13320 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13321 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13322 if (tex is null) { 13323 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT incref texture with id %d\n", image); }} 13324 return false; 13325 } 13326 import core.atomic : atomicOp; 13327 atomicOp!"+="(tex.rc, 1); 13328 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("texture #%d: incref; newref=%d\n", image, tex.rc); }} 13329 return true; 13330 } 13331 13332 bool glnvg__renderUpdateTexture (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc { 13333 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13334 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13335 13336 if (tex is null) { 13337 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT update texture with id %d\n", image); }} 13338 return false; 13339 } 13340 13341 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("updated texture with id %d; glid=%u\n", tex.id, image, tex.tex); }} 13342 13343 glnvg__bindTexture(gl, tex.tex); 13344 13345 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 13346 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width); 13347 glPixelStorei(GL_UNPACK_SKIP_PIXELS, x); 13348 glPixelStorei(GL_UNPACK_SKIP_ROWS, y); 13349 13350 immutable ttype = (tex.type == NVGtexture.RGBA ? GL_RGBA : GL_RED); 13351 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, ttype, GL_UNSIGNED_BYTE, data); 13352 13353 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 13354 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 13355 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13356 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13357 13358 glnvg__bindTexture(gl, 0); 13359 13360 return true; 13361 } 13362 13363 bool glnvg__renderGetTextureSize (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc { 13364 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13365 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13366 if (tex is null) { 13367 if (w !is null) *w = 0; 13368 if (h !is null) *h = 0; 13369 return false; 13370 } else { 13371 if (w !is null) *w = tex.width; 13372 if (h !is null) *h = tex.height; 13373 return true; 13374 } 13375 } 13376 13377 void glnvg__xformToMat3x4 (float[] m3, const(float)[] t) nothrow @trusted @nogc { 13378 assert(t.length >= 6); 13379 assert(m3.length >= 12); 13380 m3.ptr[0] = t.ptr[0]; 13381 m3.ptr[1] = t.ptr[1]; 13382 m3.ptr[2] = 0.0f; 13383 m3.ptr[3] = 0.0f; 13384 m3.ptr[4] = t.ptr[2]; 13385 m3.ptr[5] = t.ptr[3]; 13386 m3.ptr[6] = 0.0f; 13387 m3.ptr[7] = 0.0f; 13388 m3.ptr[8] = t.ptr[4]; 13389 m3.ptr[9] = t.ptr[5]; 13390 m3.ptr[10] = 1.0f; 13391 m3.ptr[11] = 0.0f; 13392 } 13393 13394 NVGColor glnvg__premulColor() (in auto ref NVGColor c) nothrow @trusted @nogc { 13395 //pragma(inline, true); 13396 NVGColor res = void; 13397 res.r = c.r*c.a; 13398 res.g = c.g*c.a; 13399 res.b = c.b*c.a; 13400 res.a = c.a; 13401 return res; 13402 } 13403 13404 bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* paint, NVGscissor* scissor, float width, float fringe, float strokeThr) nothrow @trusted @nogc { 13405 import core.stdc.math : sqrtf; 13406 GLNVGtexture* tex = null; 13407 NVGMatrix invxform = void; 13408 13409 memset(frag, 0, (*frag).sizeof); 13410 13411 frag.innerCol = glnvg__premulColor(paint.innerColor); 13412 frag.middleCol = glnvg__premulColor(paint.middleColor); 13413 frag.outerCol = glnvg__premulColor(paint.outerColor); 13414 frag.midp = paint.midp; 13415 13416 if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) { 13417 memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof); 13418 frag.scissorExt.ptr[0] = 1.0f; 13419 frag.scissorExt.ptr[1] = 1.0f; 13420 frag.scissorScale.ptr[0] = 1.0f; 13421 frag.scissorScale.ptr[1] = 1.0f; 13422 } else { 13423 //nvgTransformInverse(invxform[], scissor.xform[]); 13424 invxform = scissor.xform.inverted; 13425 glnvg__xformToMat3x4(frag.scissorMat[], invxform.mat[]); 13426 frag.scissorExt.ptr[0] = scissor.extent.ptr[0]; 13427 frag.scissorExt.ptr[1] = scissor.extent.ptr[1]; 13428 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; 13429 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; 13430 } 13431 13432 memcpy(frag.extent.ptr, paint.extent.ptr, frag.extent.sizeof); 13433 frag.strokeMult = (width*0.5f+fringe*0.5f)/fringe; 13434 frag.strokeThr = strokeThr; 13435 13436 if (paint.image.valid) { 13437 tex = glnvg__findTexture(gl, paint.image.id); 13438 if (tex is null) return false; 13439 if ((tex.flags&NVGImageFlag.FlipY) != 0) { 13440 /* 13441 NVGMatrix flipped; 13442 nvgTransformScale(flipped[], 1.0f, -1.0f); 13443 nvgTransformMultiply(flipped[], paint.xform[]); 13444 nvgTransformInverse(invxform[], flipped[]); 13445 */ 13446 /* 13447 NVGMatrix m1 = void, m2 = void; 13448 nvgTransformTranslate(m1[], 0.0f, frag.extent.ptr[1]*0.5f); 13449 nvgTransformMultiply(m1[], paint.xform[]); 13450 nvgTransformScale(m2[], 1.0f, -1.0f); 13451 nvgTransformMultiply(m2[], m1[]); 13452 nvgTransformTranslate(m1[], 0.0f, -frag.extent.ptr[1]*0.5f); 13453 nvgTransformMultiply(m1[], m2[]); 13454 nvgTransformInverse(invxform[], m1[]); 13455 */ 13456 NVGMatrix m1 = NVGMatrix.Translated(0.0f, frag.extent.ptr[1]*0.5f); 13457 m1.mul(paint.xform); 13458 NVGMatrix m2 = NVGMatrix.Scaled(1.0f, -1.0f); 13459 m2.mul(m1); 13460 m1 = NVGMatrix.Translated(0.0f, -frag.extent.ptr[1]*0.5f); 13461 m1.mul(m2); 13462 invxform = m1.inverted; 13463 } else { 13464 //nvgTransformInverse(invxform[], paint.xform[]); 13465 invxform = paint.xform.inverted; 13466 } 13467 frag.type = NSVG_SHADER_FILLIMG; 13468 13469 if (tex.type == NVGtexture.RGBA) { 13470 frag.texType = (tex.flags&NVGImageFlag.Premultiplied ? 0 : 1); 13471 } else { 13472 frag.texType = 2; 13473 } 13474 //printf("frag.texType = %d\n", frag.texType); 13475 } else { 13476 frag.type = (paint.simpleColor ? NSVG_SHADER_FILLCOLOR : NSVG_SHADER_FILLGRAD); 13477 frag.radius = paint.radius; 13478 frag.feather = paint.feather; 13479 //nvgTransformInverse(invxform[], paint.xform[]); 13480 invxform = paint.xform.inverted; 13481 } 13482 13483 glnvg__xformToMat3x4(frag.paintMat[], invxform.mat[]); 13484 13485 return true; 13486 } 13487 13488 void glnvg__setUniforms (GLNVGcontext* gl, int uniformOffset, int image) nothrow @trusted @nogc { 13489 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); 13490 glnvg__setFBOClipTexture(gl, frag); 13491 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.Frag], frag.UNIFORM_ARRAY_SIZE, &(frag.uniformArray.ptr[0].ptr[0])); 13492 glnvg__checkError(gl, "glnvg__setUniforms"); 13493 if (image != 0) { 13494 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13495 glnvg__bindTexture(gl, (tex !is null ? tex.tex : 0)); 13496 glnvg__checkError(gl, "tex paint tex"); 13497 } else { 13498 glnvg__bindTexture(gl, 0); 13499 } 13500 } 13501 13502 void glnvg__finishClip (GLNVGcontext* gl, NVGClipMode clipmode) nothrow @trusted @nogc { 13503 assert(clipmode != NVGClipMode.None); 13504 13505 // fill FBO, clear stencil buffer 13506 //TODO: optimize with bounds? 13507 version(all) { 13508 //glnvg__resetAffine(gl); 13509 //glUseProgram(gl.shaderFillFBO.prog); 13510 glDisable(GL_CULL_FACE); 13511 glDisable(GL_BLEND); 13512 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13513 glEnable(GL_STENCIL_TEST); 13514 if (gl.doClipUnion) { 13515 // for "and" we should clear everything that is NOT stencil-masked 13516 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13517 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13518 } else { 13519 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x00, 0xff); 13520 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13521 } 13522 13523 immutable(NVGVertex[4]) vertices = 13524 [NVGVertex(0, 0, 0, 0), 13525 NVGVertex(0, gl.fboHeight, 0, 0), 13526 NVGVertex(gl.fboWidth, gl.fboHeight, 0, 0), 13527 NVGVertex(gl.fboWidth, 0, 0, 0)]; 13528 13529 glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices); 13530 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 13531 13532 //glnvg__restoreAffine(gl); 13533 } 13534 13535 glBindFramebuffer(GL_FRAMEBUFFER, gl.mainFBO); 13536 glDisable(GL_COLOR_LOGIC_OP); 13537 //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // done above 13538 glEnable(GL_BLEND); 13539 glDisable(GL_STENCIL_TEST); 13540 glEnable(GL_CULL_FACE); 13541 glUseProgram(gl.shader.prog); 13542 13543 // set current FBO as used one 13544 assert(gl.msp > 0 && gl.fbo.ptr[gl.msp-1] > 0 && gl.fboTex.ptr[gl.msp-1].ptr[0] > 0); 13545 if (gl.lastClipFBO != gl.msp-1) { 13546 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); } 13547 gl.lastClipFBO = gl.msp-1; 13548 glActiveTexture(GL_TEXTURE1); 13549 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 13550 glActiveTexture(GL_TEXTURE0); 13551 } 13552 } 13553 13554 void glnvg__setClipUniforms (GLNVGcontext* gl, int uniformOffset, NVGClipMode clipmode) nothrow @trusted @nogc { 13555 assert(clipmode != NVGClipMode.None); 13556 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); 13557 // save uniform offset for `glnvg__finishClip()` 13558 gl.lastClipUniOfs = uniformOffset; 13559 // get FBO index, bind this FBO 13560 immutable int clipTexId = glnvg__generateFBOClipTexture(gl); 13561 assert(clipTexId >= 0); 13562 glUseProgram(gl.shaderFillFBO.prog); 13563 glnvg__checkError(gl, "use"); 13564 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[clipTexId]); 13565 // set logic op for clip 13566 gl.doClipUnion = false; 13567 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.JustCleared) { 13568 // it is cleared to zero, we can just draw a path 13569 glDisable(GL_COLOR_LOGIC_OP); 13570 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized; 13571 } else { 13572 glEnable(GL_COLOR_LOGIC_OP); 13573 final switch (clipmode) { 13574 case NVGClipMode.None: assert(0, "wtf?!"); 13575 case NVGClipMode.Union: glLogicOp(GL_CLEAR); gl.doClipUnion = true; break; // use `GL_CLEAR` to avoid adding another shader mode 13576 case NVGClipMode.Or: glLogicOp(GL_COPY); break; // GL_OR 13577 case NVGClipMode.Xor: glLogicOp(GL_XOR); break; 13578 case NVGClipMode.Sub: glLogicOp(GL_CLEAR); break; 13579 case NVGClipMode.Replace: glLogicOp(GL_COPY); break; 13580 } 13581 } 13582 // set affine matrix 13583 glUniform4fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TMat], 1, gl.lastAffine.mat.ptr); 13584 glnvg__checkError(gl, "affine 0"); 13585 glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TTr], 1, gl.lastAffine.mat.ptr+4); 13586 glnvg__checkError(gl, "affine 1"); 13587 // setup common OpenGL parameters 13588 glDisable(GL_BLEND); 13589 glDisable(GL_CULL_FACE); 13590 glEnable(GL_STENCIL_TEST); 13591 glnvg__stencilMask(gl, 0xff); 13592 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13593 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 13594 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13595 } 13596 13597 void glnvg__renderViewport (void* uptr, int width, int height) nothrow @trusted @nogc { 13598 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13599 gl.inFrame = true; 13600 gl.view.ptr[0] = cast(float)width; 13601 gl.view.ptr[1] = cast(float)height; 13602 // kill FBOs if we need to create new ones (flushing will recreate 'em if necessary) 13603 if (width != gl.fboWidth || height != gl.fboHeight) { 13604 glnvg__killFBOs(gl); 13605 gl.fboWidth = width; 13606 gl.fboHeight = height; 13607 } 13608 gl.msp = 1; 13609 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13610 // texture cleanup 13611 import core.atomic : atomicLoad; 13612 if (atomicLoad(gl.mustCleanTextures)) { 13613 try { 13614 import core.thread : Thread; 13615 static if (__VERSION__ < 2076) { 13616 DGNoThrowNoGC(() { 13617 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13618 })(); 13619 } else { 13620 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13621 } 13622 { 13623 GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow; 13624 gl.mustCleanTextures = false; 13625 foreach (immutable tidx, ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) { 13626 // no need to use atomic ops here, as we're locked 13627 if (tex.rc == 0 && tex.tex != 0 && tex.id == 0) { 13628 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** cleaned up texture with glid=%u\n", tex.tex); }} 13629 import core.stdc.string : memset; 13630 if ((tex.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tex.tex); 13631 memset(&tex, 0, tex.sizeof); 13632 tex.nextfree = gl.freetexid; 13633 gl.freetexid = cast(int)tidx; 13634 } 13635 } 13636 } 13637 } catch (Exception e) {} 13638 } 13639 } 13640 13641 void glnvg__fill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13642 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13643 int npaths = call.pathCount; 13644 13645 if (call.clipmode == NVGClipMode.None) { 13646 // Draw shapes 13647 glEnable(GL_STENCIL_TEST); 13648 glnvg__stencilMask(gl, 0xffU); 13649 glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xffU); 13650 13651 glnvg__setUniforms(gl, call.uniformOffset, 0); 13652 glnvg__checkError(gl, "fill simple"); 13653 13654 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13655 if (call.evenOdd) { 13656 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT); 13657 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT); 13658 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); 13659 } else { 13660 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 13661 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 13662 } 13663 glDisable(GL_CULL_FACE); 13664 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13665 glEnable(GL_CULL_FACE); 13666 13667 // Draw anti-aliased pixels 13668 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13669 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image); 13670 glnvg__checkError(gl, "fill fill"); 13671 13672 if (gl.flags&NVGContextFlag.Antialias) { 13673 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xffU); 13674 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13675 // Draw fringes 13676 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13677 } 13678 13679 // Draw fill 13680 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xffU); 13681 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13682 if (call.evenOdd) { 13683 glDisable(GL_CULL_FACE); 13684 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount); 13685 //foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13686 glEnable(GL_CULL_FACE); 13687 } else { 13688 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount); 13689 } 13690 13691 glDisable(GL_STENCIL_TEST); 13692 } else { 13693 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); // this activates our FBO 13694 glnvg__checkError(gl, "fillclip simple"); 13695 glnvg__stencilFunc(gl, GL_ALWAYS, 0x00, 0xffU); 13696 if (call.evenOdd) { 13697 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT); 13698 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT); 13699 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); 13700 } else { 13701 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 13702 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 13703 } 13704 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13705 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13706 } 13707 } 13708 13709 void glnvg__convexFill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13710 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13711 int npaths = call.pathCount; 13712 13713 if (call.clipmode == NVGClipMode.None) { 13714 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13715 glnvg__checkError(gl, "convex fill"); 13716 if (call.evenOdd) glDisable(GL_CULL_FACE); 13717 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13718 if (gl.flags&NVGContextFlag.Antialias) { 13719 // Draw fringes 13720 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13721 } 13722 if (call.evenOdd) glEnable(GL_CULL_FACE); 13723 } else { 13724 glnvg__setClipUniforms(gl, call.uniformOffset, call.clipmode); // this activates our FBO 13725 glnvg__checkError(gl, "clip convex fill"); 13726 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13727 if (gl.flags&NVGContextFlag.Antialias) { 13728 // Draw fringes 13729 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13730 } 13731 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13732 } 13733 } 13734 13735 void glnvg__stroke (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13736 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13737 int npaths = call.pathCount; 13738 13739 if (call.clipmode == NVGClipMode.None) { 13740 if (gl.flags&NVGContextFlag.StencilStrokes) { 13741 glEnable(GL_STENCIL_TEST); 13742 glnvg__stencilMask(gl, 0xff); 13743 13744 // Fill the stroke base without overlap 13745 glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff); 13746 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 13747 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image); 13748 glnvg__checkError(gl, "stroke fill 0"); 13749 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13750 13751 // Draw anti-aliased pixels. 13752 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13753 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13754 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13755 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13756 13757 // Clear stencil buffer. 13758 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13759 glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff); 13760 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13761 glnvg__checkError(gl, "stroke fill 1"); 13762 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13763 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13764 13765 glDisable(GL_STENCIL_TEST); 13766 13767 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f); 13768 } else { 13769 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13770 glnvg__checkError(gl, "stroke fill"); 13771 // Draw Strokes 13772 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13773 } 13774 } else { 13775 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); 13776 glnvg__checkError(gl, "stroke fill 0"); 13777 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13778 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13779 } 13780 } 13781 13782 void glnvg__triangles (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13783 if (call.clipmode == NVGClipMode.None) { 13784 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13785 glnvg__checkError(gl, "triangles fill"); 13786 glDrawArrays(GL_TRIANGLES, call.triangleOffset, call.triangleCount); 13787 } else { 13788 //TODO(?): use texture as mask? 13789 } 13790 } 13791 13792 void glnvg__affine (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13793 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, call.affine.mat.ptr); 13794 glnvg__checkError(gl, "affine"); 13795 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, call.affine.mat.ptr+4); 13796 glnvg__checkError(gl, "affine"); 13797 //glnvg__setUniforms(gl, call.uniformOffset, call.image); 13798 } 13799 13800 void glnvg__renderCancelInternal (GLNVGcontext* gl, bool clearTextures) nothrow @trusted @nogc { 13801 scope(exit) gl.inFrame = false; 13802 if (clearTextures && gl.inFrame) { 13803 try { 13804 import core.thread : Thread; 13805 static if (__VERSION__ < 2076) { 13806 DGNoThrowNoGC(() { 13807 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13808 })(); 13809 } else { 13810 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13811 } 13812 } catch (Exception e) {} 13813 foreach (ref GLNVGcall c; gl.calls[0..gl.ncalls]) if (c.image > 0) glnvg__deleteTexture(gl, c.image); 13814 } 13815 gl.nverts = 0; 13816 gl.npaths = 0; 13817 gl.ncalls = 0; 13818 gl.nuniforms = 0; 13819 gl.msp = 1; 13820 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13821 } 13822 13823 void glnvg__renderCancel (void* uptr) nothrow @trusted @nogc { 13824 glnvg__renderCancelInternal(cast(GLNVGcontext*)uptr, true); 13825 } 13826 13827 GLenum glnvg_convertBlendFuncFactor (NVGBlendFactor factor) pure nothrow @trusted @nogc { 13828 if (factor == NVGBlendFactor.Zero) return GL_ZERO; 13829 if (factor == NVGBlendFactor.One) return GL_ONE; 13830 if (factor == NVGBlendFactor.SrcColor) return GL_SRC_COLOR; 13831 if (factor == NVGBlendFactor.OneMinusSrcColor) return GL_ONE_MINUS_SRC_COLOR; 13832 if (factor == NVGBlendFactor.DstColor) return GL_DST_COLOR; 13833 if (factor == NVGBlendFactor.OneMinusDstColor) return GL_ONE_MINUS_DST_COLOR; 13834 if (factor == NVGBlendFactor.SrcAlpha) return GL_SRC_ALPHA; 13835 if (factor == NVGBlendFactor.OneMinusSrcAlpha) return GL_ONE_MINUS_SRC_ALPHA; 13836 if (factor == NVGBlendFactor.DstAlpha) return GL_DST_ALPHA; 13837 if (factor == NVGBlendFactor.OneMinusDstAlpha) return GL_ONE_MINUS_DST_ALPHA; 13838 if (factor == NVGBlendFactor.SrcAlphaSaturate) return GL_SRC_ALPHA_SATURATE; 13839 return GL_INVALID_ENUM; 13840 } 13841 13842 GLNVGblend glnvg__buildBlendFunc (NVGCompositeOperationState op) pure nothrow @trusted @nogc { 13843 GLNVGblend res; 13844 res.simple = op.simple; 13845 res.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB); 13846 res.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB); 13847 res.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha); 13848 res.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha); 13849 if (res.simple) { 13850 if (res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) { 13851 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM; 13852 } 13853 } else { 13854 if (res.srcRGB == GL_INVALID_ENUM || res.dstRGB == GL_INVALID_ENUM || res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) { 13855 res.simple = true; 13856 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM; 13857 } 13858 } 13859 return res; 13860 } 13861 13862 void glnvg__blendCompositeOperation() (GLNVGcontext* gl, in auto ref GLNVGblend op) nothrow @trusted @nogc { 13863 //glBlendFuncSeparate(glnvg_convertBlendFuncFactor(op.srcRGB), glnvg_convertBlendFuncFactor(op.dstRGB), glnvg_convertBlendFuncFactor(op.srcAlpha), glnvg_convertBlendFuncFactor(op.dstAlpha)); 13864 static if (NANOVG_GL_USE_STATE_FILTER) { 13865 if (gl.blendFunc.simple == op.simple) { 13866 if (op.simple) { 13867 if (gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return; 13868 } else { 13869 if (gl.blendFunc.srcRGB == op.srcRGB && gl.blendFunc.dstRGB == op.dstRGB && gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return; 13870 } 13871 } 13872 gl.blendFunc = op; 13873 } 13874 if (op.simple) { 13875 if (op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) { 13876 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13877 } else { 13878 glBlendFunc(op.srcAlpha, op.dstAlpha); 13879 } 13880 } else { 13881 if (op.srcRGB == GL_INVALID_ENUM || op.dstRGB == GL_INVALID_ENUM || op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) { 13882 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13883 } else { 13884 glBlendFuncSeparate(op.srcRGB, op.dstRGB, op.srcAlpha, op.dstAlpha); 13885 } 13886 } 13887 } 13888 13889 void glnvg__renderSetAffine (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc { 13890 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13891 GLNVGcall* call; 13892 // if last operation was GLNVG_AFFINE, simply replace the matrix 13893 if (gl.ncalls > 0 && gl.calls[gl.ncalls-1].type == GLNVG_AFFINE) { 13894 call = &gl.calls[gl.ncalls-1]; 13895 } else { 13896 call = glnvg__allocCall(gl); 13897 if (call is null) return; 13898 call.type = GLNVG_AFFINE; 13899 } 13900 call.affine.mat.ptr[0..6] = mat.mat.ptr[0..6]; 13901 } 13902 13903 version(nanovega_debug_clipping) public __gshared bool nanovegaClipDebugDump = false; 13904 13905 void glnvg__renderFlush (void* uptr) nothrow @trusted @nogc { 13906 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13907 if (!gl.inFrame) assert(0, "NanoVega: internal driver error"); 13908 try { 13909 import core.thread : Thread; 13910 static if (__VERSION__ < 2076) { 13911 DGNoThrowNoGC(() { 13912 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13913 })(); 13914 } else { 13915 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13916 } 13917 } catch (Exception e) {} 13918 scope(exit) gl.inFrame = false; 13919 13920 glnvg__resetError!true(gl); 13921 { 13922 int vv = 0; 13923 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &vv); 13924 if (glGetError() || vv < 0) vv = 0; 13925 gl.mainFBO = cast(uint)vv; 13926 } 13927 13928 enum ShaderType { None, Fill, Clip } 13929 auto lastShader = ShaderType.None; 13930 if (gl.ncalls > 0) { 13931 gl.msp = 1; 13932 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13933 13934 // Setup require GL state. 13935 glUseProgram(gl.shader.prog); 13936 13937 glActiveTexture(GL_TEXTURE1); 13938 glBindTexture(GL_TEXTURE_2D, 0); 13939 glActiveTexture(GL_TEXTURE0); 13940 glnvg__resetFBOClipTextureCache(gl); 13941 13942 //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13943 static if (NANOVG_GL_USE_STATE_FILTER) { 13944 gl.blendFunc.simple = true; 13945 gl.blendFunc.srcRGB = gl.blendFunc.dstRGB = gl.blendFunc.srcAlpha = gl.blendFunc.dstAlpha = GL_INVALID_ENUM; 13946 } 13947 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // just in case 13948 glEnable(GL_CULL_FACE); 13949 glCullFace(GL_BACK); 13950 glFrontFace(GL_CCW); 13951 glEnable(GL_BLEND); 13952 glDisable(GL_DEPTH_TEST); 13953 glDisable(GL_SCISSOR_TEST); 13954 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13955 glStencilMask(0xffffffff); 13956 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13957 glStencilFunc(GL_ALWAYS, 0, 0xffffffff); 13958 glActiveTexture(GL_TEXTURE0); 13959 glBindTexture(GL_TEXTURE_2D, 0); 13960 static if (NANOVG_GL_USE_STATE_FILTER) { 13961 gl.boundTexture = 0; 13962 gl.stencilMask = 0xffffffff; 13963 gl.stencilFunc = GL_ALWAYS; 13964 gl.stencilFuncRef = 0; 13965 gl.stencilFuncMask = 0xffffffff; 13966 } 13967 glnvg__checkError(gl, "OpenGL setup"); 13968 13969 // Upload vertex data 13970 glBindBuffer(GL_ARRAY_BUFFER, gl.vertBuf); 13971 // ensure that there's space for at least 4 vertices in case we need to draw a quad (e. g. framebuffer copy) 13972 glBufferData(GL_ARRAY_BUFFER, (gl.nverts < 4 ? 4 : gl.nverts) * NVGVertex.sizeof, gl.verts, GL_STREAM_DRAW); 13973 glEnableVertexAttribArray(0); 13974 glEnableVertexAttribArray(1); 13975 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)cast(usize)0); 13976 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)(0+2*float.sizeof)); 13977 glnvg__checkError(gl, "vertex data uploading"); 13978 13979 // Set view and texture just once per frame. 13980 glUniform1i(gl.shader.loc[GLNVGuniformLoc.Tex], 0); 13981 if (gl.shader.loc[GLNVGuniformLoc.ClipTex] != -1) { 13982 //{ import core.stdc.stdio; printf("%d\n", gl.shader.loc[GLNVGuniformLoc.ClipTex]); } 13983 glUniform1i(gl.shader.loc[GLNVGuniformLoc.ClipTex], 1); 13984 } 13985 if (gl.shader.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shader.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 13986 glnvg__checkError(gl, "render shader setup"); 13987 13988 // Reset affine transformations. 13989 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, NVGMatrix.IdentityMat.ptr); 13990 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, NVGMatrix.IdentityMat.ptr+4); 13991 glnvg__checkError(gl, "affine setup"); 13992 13993 // set clip shaders params 13994 // fill 13995 glUseProgram(gl.shaderFillFBO.prog); 13996 glnvg__checkError(gl, "clip shaders setup (fill 0)"); 13997 if (gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 13998 glnvg__checkError(gl, "clip shaders setup (fill 1)"); 13999 // copy 14000 glUseProgram(gl.shaderCopyFBO.prog); 14001 glnvg__checkError(gl, "clip shaders setup (copy 0)"); 14002 if (gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 14003 glnvg__checkError(gl, "clip shaders setup (copy 1)"); 14004 //glUniform1i(gl.shaderFillFBO.loc[GLNVGuniformLoc.Tex], 0); 14005 glUniform1i(gl.shaderCopyFBO.loc[GLNVGuniformLoc.Tex], 0); 14006 glnvg__checkError(gl, "clip shaders setup (copy 2)"); 14007 // restore render shader 14008 glUseProgram(gl.shader.prog); 14009 14010 //{ 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]); } 14011 14012 gl.lastAffine.identity; 14013 14014 foreach (int i; 0..gl.ncalls) { 14015 GLNVGcall* call = &gl.calls[i]; 14016 switch (call.type) { 14017 case GLNVG_FILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__fill(gl, call); break; 14018 case GLNVG_CONVEXFILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__convexFill(gl, call); break; 14019 case GLNVG_STROKE: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__stroke(gl, call); break; 14020 case GLNVG_TRIANGLES: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__triangles(gl, call); break; 14021 case GLNVG_AFFINE: gl.lastAffine = call.affine; glnvg__affine(gl, call); break; 14022 // clip region management 14023 case GLNVG_PUSHCLIP: 14024 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]); } 14025 if (gl.msp >= gl.maskStack.length) assert(0, "NanoVega: mask stack overflow in OpenGL backend"); 14026 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.DontMask) { 14027 gl.maskStack.ptr[gl.msp++] = GLMaskState.DontMask; 14028 } else { 14029 gl.maskStack.ptr[gl.msp++] = GLMaskState.Uninitialized; 14030 } 14031 // no need to reset FBO cache here, as nothing was changed 14032 break; 14033 case GLNVG_POPCLIP: 14034 if (gl.msp <= 1) assert(0, "NanoVega: mask stack underflow in OpenGL backend"); 14035 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]); } 14036 --gl.msp; 14037 assert(gl.msp > 0); 14038 //{ import core.stdc.stdio; printf("popped; new msp is %d; state is %d\n", gl.msp, gl.maskStack.ptr[gl.msp]); } 14039 // check popped item 14040 final switch (gl.maskStack.ptr[gl.msp]) { 14041 case GLMaskState.DontMask: 14042 // if last FBO was "don't mask", reset cache if current is not "don't mask" 14043 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) { 14044 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14045 glnvg__resetFBOClipTextureCache(gl); 14046 } 14047 break; 14048 case GLMaskState.Uninitialized: 14049 // if last FBO texture was uninitialized, it means that nothing was changed, 14050 // so we can keep using cached FBO 14051 break; 14052 case GLMaskState.Initialized: 14053 // if last FBO was initialized, it means that something was definitely changed 14054 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14055 glnvg__resetFBOClipTextureCache(gl); 14056 break; 14057 case GLMaskState.JustCleared: assert(0, "NanoVega: internal FBO stack error"); 14058 } 14059 break; 14060 case GLNVG_RESETCLIP: 14061 // mark current mask as "don't mask" 14062 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]); } 14063 if (gl.msp > 0) { 14064 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) { 14065 gl.maskStack.ptr[gl.msp-1] = GLMaskState.DontMask; 14066 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14067 glnvg__resetFBOClipTextureCache(gl); 14068 } 14069 } 14070 break; 14071 case GLNVG_CLIP_DDUMP_ON: 14072 version(nanovega_debug_clipping) nanovegaClipDebugDump = true; 14073 break; 14074 case GLNVG_CLIP_DDUMP_OFF: 14075 version(nanovega_debug_clipping) nanovegaClipDebugDump = false; 14076 break; 14077 case GLNVG_NONE: break; 14078 default: 14079 { 14080 import core.stdc.stdio; stderr.fprintf("NanoVega FATAL: invalid command in OpenGL backend: %d\n", call.type); 14081 } 14082 assert(0, "NanoVega: invalid command in OpenGL backend (fatal internal error)"); 14083 } 14084 // and free texture, why not 14085 glnvg__deleteTexture(gl, call.image); 14086 } 14087 14088 glDisableVertexAttribArray(0); 14089 glDisableVertexAttribArray(1); 14090 glDisable(GL_CULL_FACE); 14091 glBindBuffer(GL_ARRAY_BUFFER, 0); 14092 glUseProgram(0); 14093 glnvg__bindTexture(gl, 0); 14094 } 14095 14096 // this will do all necessary cleanup 14097 glnvg__renderCancelInternal(gl, false); // no need to clear textures 14098 } 14099 14100 int glnvg__maxVertCount (const(NVGpath)* paths, int npaths) nothrow @trusted @nogc { 14101 int count = 0; 14102 foreach (int i; 0..npaths) { 14103 count += paths[i].nfill; 14104 count += paths[i].nstroke; 14105 } 14106 return count; 14107 } 14108 14109 GLNVGcall* glnvg__allocCall (GLNVGcontext* gl) nothrow @trusted @nogc { 14110 GLNVGcall* ret = null; 14111 if (gl.ncalls+1 > gl.ccalls) { 14112 GLNVGcall* calls; 14113 int ccalls = glnvg__maxi(gl.ncalls+1, 128)+gl.ccalls/2; // 1.5x Overallocate 14114 calls = cast(GLNVGcall*)realloc(gl.calls, GLNVGcall.sizeof*ccalls); 14115 if (calls is null) return null; 14116 gl.calls = calls; 14117 gl.ccalls = ccalls; 14118 } 14119 ret = &gl.calls[gl.ncalls++]; 14120 memset(ret, 0, GLNVGcall.sizeof); 14121 return ret; 14122 } 14123 14124 int glnvg__allocPaths (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14125 int ret = 0; 14126 if (gl.npaths+n > gl.cpaths) { 14127 GLNVGpath* paths; 14128 int cpaths = glnvg__maxi(gl.npaths+n, 128)+gl.cpaths/2; // 1.5x Overallocate 14129 paths = cast(GLNVGpath*)realloc(gl.paths, GLNVGpath.sizeof*cpaths); 14130 if (paths is null) return -1; 14131 gl.paths = paths; 14132 gl.cpaths = cpaths; 14133 } 14134 ret = gl.npaths; 14135 gl.npaths += n; 14136 return ret; 14137 } 14138 14139 int glnvg__allocVerts (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14140 int ret = 0; 14141 if (gl.nverts+n > gl.cverts) { 14142 NVGVertex* verts; 14143 int cverts = glnvg__maxi(gl.nverts+n, 4096)+gl.cverts/2; // 1.5x Overallocate 14144 verts = cast(NVGVertex*)realloc(gl.verts, NVGVertex.sizeof*cverts); 14145 if (verts is null) return -1; 14146 gl.verts = verts; 14147 gl.cverts = cverts; 14148 } 14149 ret = gl.nverts; 14150 gl.nverts += n; 14151 return ret; 14152 } 14153 14154 int glnvg__allocFragUniforms (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14155 int ret = 0, structSize = gl.fragSize; 14156 if (gl.nuniforms+n > gl.cuniforms) { 14157 ubyte* uniforms; 14158 int cuniforms = glnvg__maxi(gl.nuniforms+n, 128)+gl.cuniforms/2; // 1.5x Overallocate 14159 uniforms = cast(ubyte*)realloc(gl.uniforms, structSize*cuniforms); 14160 if (uniforms is null) return -1; 14161 gl.uniforms = uniforms; 14162 gl.cuniforms = cuniforms; 14163 } 14164 ret = gl.nuniforms*structSize; 14165 gl.nuniforms += n; 14166 return ret; 14167 } 14168 14169 GLNVGfragUniforms* nvg__fragUniformPtr (GLNVGcontext* gl, int i) nothrow @trusted @nogc { 14170 return cast(GLNVGfragUniforms*)&gl.uniforms[i]; 14171 } 14172 14173 void glnvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc { 14174 vtx.x = x; 14175 vtx.y = y; 14176 vtx.u = u; 14177 vtx.v = v; 14178 } 14179 14180 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 { 14181 if (npaths < 1) return; 14182 14183 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14184 GLNVGcall* call = glnvg__allocCall(gl); 14185 NVGVertex* quad; 14186 GLNVGfragUniforms* frag; 14187 int maxverts, offset; 14188 14189 if (call is null) return; 14190 14191 call.type = GLNVG_FILL; 14192 call.evenOdd = evenOdd; 14193 call.clipmode = clipmode; 14194 //if (clipmode != NVGClipMode.None) { import core.stdc.stdio; printf("CLIP!\n"); } 14195 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14196 call.triangleCount = 4; 14197 call.pathOffset = glnvg__allocPaths(gl, npaths); 14198 if (call.pathOffset == -1) goto error; 14199 call.pathCount = npaths; 14200 call.image = paint.image.id; 14201 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14202 14203 if (npaths == 1 && paths[0].convex) { 14204 call.type = GLNVG_CONVEXFILL; 14205 call.triangleCount = 0; // Bounding box fill quad not needed for convex fill 14206 } 14207 14208 // Allocate vertices for all the paths. 14209 maxverts = glnvg__maxVertCount(paths, npaths)+call.triangleCount; 14210 offset = glnvg__allocVerts(gl, maxverts); 14211 if (offset == -1) goto error; 14212 14213 foreach (int i; 0..npaths) { 14214 GLNVGpath* copy = &gl.paths[call.pathOffset+i]; 14215 const(NVGpath)* path = &paths[i]; 14216 memset(copy, 0, GLNVGpath.sizeof); 14217 if (path.nfill > 0) { 14218 copy.fillOffset = offset; 14219 copy.fillCount = path.nfill; 14220 memcpy(&gl.verts[offset], path.fill, NVGVertex.sizeof*path.nfill); 14221 offset += path.nfill; 14222 } 14223 if (path.nstroke > 0) { 14224 copy.strokeOffset = offset; 14225 copy.strokeCount = path.nstroke; 14226 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke); 14227 offset += path.nstroke; 14228 } 14229 } 14230 14231 // Setup uniforms for draw calls 14232 if (call.type == GLNVG_FILL) { 14233 import core.stdc.string : memcpy; 14234 // Quad 14235 call.triangleOffset = offset; 14236 quad = &gl.verts[call.triangleOffset]; 14237 glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f); 14238 glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f); 14239 glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f); 14240 glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f); 14241 // Get uniform 14242 call.uniformOffset = glnvg__allocFragUniforms(gl, 2); 14243 if (call.uniformOffset == -1) goto error; 14244 // Simple shader for stencil 14245 frag = nvg__fragUniformPtr(gl, call.uniformOffset); 14246 memset(frag, 0, (*frag).sizeof); 14247 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f); 14248 memcpy(nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), frag, (*frag).sizeof); 14249 frag.strokeThr = -1.0f; 14250 frag.type = NSVG_SHADER_SIMPLE; 14251 // Fill shader 14252 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, fringe, fringe, -1.0f); 14253 } else { 14254 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14255 if (call.uniformOffset == -1) goto error; 14256 // Fill shader 14257 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f); 14258 } 14259 14260 return; 14261 14262 error: 14263 // We get here if call alloc was ok, but something else is not. 14264 // Roll back the last call to prevent drawing it. 14265 if (gl.ncalls > 0) --gl.ncalls; 14266 } 14267 14268 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 { 14269 if (npaths < 1) return; 14270 14271 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14272 GLNVGcall* call = glnvg__allocCall(gl); 14273 int maxverts, offset; 14274 14275 if (call is null) return; 14276 14277 call.type = GLNVG_STROKE; 14278 call.clipmode = clipmode; 14279 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14280 call.pathOffset = glnvg__allocPaths(gl, npaths); 14281 if (call.pathOffset == -1) goto error; 14282 call.pathCount = npaths; 14283 call.image = paint.image.id; 14284 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14285 14286 // Allocate vertices for all the paths. 14287 maxverts = glnvg__maxVertCount(paths, npaths); 14288 offset = glnvg__allocVerts(gl, maxverts); 14289 if (offset == -1) goto error; 14290 14291 foreach (int i; 0..npaths) { 14292 GLNVGpath* copy = &gl.paths[call.pathOffset+i]; 14293 const(NVGpath)* path = &paths[i]; 14294 memset(copy, 0, GLNVGpath.sizeof); 14295 if (path.nstroke) { 14296 copy.strokeOffset = offset; 14297 copy.strokeCount = path.nstroke; 14298 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke); 14299 offset += path.nstroke; 14300 } 14301 } 14302 14303 if (gl.flags&NVGContextFlag.StencilStrokes) { 14304 // Fill shader 14305 call.uniformOffset = glnvg__allocFragUniforms(gl, 2); 14306 if (call.uniformOffset == -1) goto error; 14307 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); 14308 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f); 14309 } else { 14310 // Fill shader 14311 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14312 if (call.uniformOffset == -1) goto error; 14313 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); 14314 } 14315 14316 return; 14317 14318 error: 14319 // We get here if call alloc was ok, but something else is not. 14320 // Roll back the last call to prevent drawing it. 14321 if (gl.ncalls > 0) --gl.ncalls; 14322 } 14323 14324 void glnvg__renderTriangles (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc { 14325 if (nverts < 1) return; 14326 14327 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14328 GLNVGcall* call = glnvg__allocCall(gl); 14329 GLNVGfragUniforms* frag; 14330 14331 if (call is null) return; 14332 14333 call.type = GLNVG_TRIANGLES; 14334 call.clipmode = clipmode; 14335 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14336 call.image = paint.image.id; 14337 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14338 14339 // Allocate vertices for all the paths. 14340 call.triangleOffset = glnvg__allocVerts(gl, nverts); 14341 if (call.triangleOffset == -1) goto error; 14342 call.triangleCount = nverts; 14343 14344 memcpy(&gl.verts[call.triangleOffset], verts, NVGVertex.sizeof*nverts); 14345 14346 // Fill shader 14347 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14348 if (call.uniformOffset == -1) goto error; 14349 frag = nvg__fragUniformPtr(gl, call.uniformOffset); 14350 glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f); 14351 frag.type = NSVG_SHADER_IMG; 14352 14353 return; 14354 14355 error: 14356 // We get here if call alloc was ok, but something else is not. 14357 // Roll back the last call to prevent drawing it. 14358 if (gl.ncalls > 0) --gl.ncalls; 14359 } 14360 14361 void glnvg__renderDelete (void* uptr) nothrow @trusted @nogc { 14362 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14363 if (gl is null) return; 14364 14365 glnvg__killFBOs(gl); 14366 glnvg__deleteShader(&gl.shader); 14367 glnvg__deleteShader(&gl.shaderFillFBO); 14368 glnvg__deleteShader(&gl.shaderCopyFBO); 14369 14370 if (gl.vertBuf != 0) glDeleteBuffers(1, &gl.vertBuf); 14371 14372 foreach (ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) { 14373 if (tex.id != 0 && (tex.flags&NVGImageFlag.NoDelete) == 0) { 14374 assert(tex.tex != 0); 14375 glDeleteTextures(1, &tex.tex); 14376 } 14377 } 14378 free(gl.textures); 14379 14380 free(gl.paths); 14381 free(gl.verts); 14382 free(gl.uniforms); 14383 free(gl.calls); 14384 14385 free(gl); 14386 } 14387 14388 14389 /** Creates NanoVega contexts for OpenGL2+. 14390 * 14391 * Specify creation flags as additional arguments, like this: 14392 * `nvgCreateContext(NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes);` 14393 * 14394 * If you won't specify any flags, defaults will be used: 14395 * `[NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes]`. 14396 * 14397 * Group: context_management 14398 */ 14399 public NVGContext nvgCreateContext (const(NVGContextFlag)[] flagList...) nothrow @trusted @nogc { 14400 version(aliced) { 14401 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA; 14402 } else { 14403 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes; 14404 } 14405 uint flags = 0; 14406 if (flagList.length != 0) { 14407 foreach (immutable flg; flagList) flags |= (flg != NVGContextFlag.Default ? flg : DefaultFlags); 14408 } else { 14409 flags = DefaultFlags; 14410 } 14411 NVGparams params = void; 14412 NVGContext ctx = null; 14413 version(nanovg_builtin_opengl_bindings) nanovgInitOpenGL(); // why not? 14414 version(nanovg_bindbc_opengl_bindings) nanovgInitOpenGL(); 14415 GLNVGcontext* gl = cast(GLNVGcontext*)malloc(GLNVGcontext.sizeof); 14416 if (gl is null) goto error; 14417 memset(gl, 0, GLNVGcontext.sizeof); 14418 14419 memset(¶ms, 0, params.sizeof); 14420 params.renderCreate = &glnvg__renderCreate; 14421 params.renderCreateTexture = &glnvg__renderCreateTexture; 14422 params.renderTextureIncRef = &glnvg__renderTextureIncRef; 14423 params.renderDeleteTexture = &glnvg__renderDeleteTexture; 14424 params.renderUpdateTexture = &glnvg__renderUpdateTexture; 14425 params.renderGetTextureSize = &glnvg__renderGetTextureSize; 14426 params.renderViewport = &glnvg__renderViewport; 14427 params.renderCancel = &glnvg__renderCancel; 14428 params.renderFlush = &glnvg__renderFlush; 14429 params.renderPushClip = &glnvg__renderPushClip; 14430 params.renderPopClip = &glnvg__renderPopClip; 14431 params.renderResetClip = &glnvg__renderResetClip; 14432 params.renderFill = &glnvg__renderFill; 14433 params.renderStroke = &glnvg__renderStroke; 14434 params.renderTriangles = &glnvg__renderTriangles; 14435 params.renderSetAffine = &glnvg__renderSetAffine; 14436 params.renderDelete = &glnvg__renderDelete; 14437 params.userPtr = gl; 14438 params.edgeAntiAlias = (flags&NVGContextFlag.Antialias ? true : false); 14439 if (flags&(NVGContextFlag.FontAA|NVGContextFlag.FontNoAA)) { 14440 params.fontAA = (flags&NVGContextFlag.FontNoAA ? NVG_INVERT_FONT_AA : !NVG_INVERT_FONT_AA); 14441 } else { 14442 params.fontAA = NVG_INVERT_FONT_AA; 14443 } 14444 14445 gl.flags = flags; 14446 gl.freetexid = -1; 14447 14448 ctx = createInternal(¶ms); 14449 if (ctx is null) goto error; 14450 14451 static if (__VERSION__ < 2076) { 14452 DGNoThrowNoGC(() { import core.thread; gl.mainTID = Thread.getThis.id; })(); 14453 } else { 14454 try { import core.thread; gl.mainTID = Thread.getThis.id; } catch (Exception e) {} 14455 } 14456 14457 return ctx; 14458 14459 error: 14460 // 'gl' is freed by nvgDeleteInternal. 14461 if (ctx !is null) ctx.deleteInternal(); 14462 return null; 14463 } 14464 14465 /// Using OpenGL texture id creates GLNVGtexture and return its id. 14466 /// Group: images 14467 public int glCreateImageFromHandleGL2 (NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc { 14468 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr; 14469 GLNVGtexture* tex = glnvg__allocTexture(gl); 14470 14471 if (tex is null) return 0; 14472 14473 tex.type = NVGtexture.RGBA; 14474 tex.tex = textureId; 14475 tex.flags = imageFlags; 14476 tex.width = w; 14477 tex.height = h; 14478 14479 return tex.id; 14480 } 14481 14482 /// Create NVGImage from OpenGL texture id. 14483 /// Group: images 14484 public NVGImage glCreateImageFromOpenGLTexture(NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc { 14485 auto id = glCreateImageFromHandleGL2(ctx, textureId, w, h, imageFlags); 14486 14487 NVGImage res; 14488 if (id > 0) { 14489 res.id = id; 14490 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); } 14491 res.ctx = ctx; 14492 ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount 14493 } 14494 return res; 14495 } 14496 14497 /// Returns OpenGL texture id for NanoVega image. 14498 /// Group: images 14499 public GLuint glImageHandleGL2 (NVGContext ctx, int image) nothrow @trusted @nogc { 14500 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr; 14501 GLNVGtexture* tex = glnvg__findTexture(gl, image); 14502 return tex.tex; 14503 } 14504 14505 14506 // ////////////////////////////////////////////////////////////////////////// // 14507 private: 14508 14509 static if (NanoVegaHasFontConfig) { 14510 version(nanovg_builtin_fontconfig_bindings) { 14511 pragma(lib, "fontconfig"); 14512 14513 private extern(C) nothrow @trusted @nogc { 14514 enum FC_FILE = "file"; /* String */ 14515 alias FcBool = int; 14516 alias FcChar8 = char; 14517 struct FcConfig; 14518 struct FcPattern; 14519 alias FcMatchKind = int; 14520 enum : FcMatchKind { 14521 FcMatchPattern, 14522 FcMatchFont, 14523 FcMatchScan 14524 } 14525 alias FcResult = int; 14526 enum : FcResult { 14527 FcResultMatch, 14528 FcResultNoMatch, 14529 FcResultTypeMismatch, 14530 FcResultNoId, 14531 FcResultOutOfMemory 14532 } 14533 FcBool FcInit (); 14534 FcBool FcConfigSubstituteWithPat (FcConfig* config, FcPattern* p, FcPattern* p_pat, FcMatchKind kind); 14535 void FcDefaultSubstitute (FcPattern* pattern); 14536 FcBool FcConfigSubstitute (FcConfig* config, FcPattern* p, FcMatchKind kind); 14537 FcPattern* FcFontMatch (FcConfig* config, FcPattern* p, FcResult* result); 14538 FcPattern* FcNameParse (const(FcChar8)* name); 14539 void FcPatternDestroy (FcPattern* p); 14540 FcResult FcPatternGetString (const(FcPattern)* p, const(char)* object, int n, FcChar8** s); 14541 } 14542 } 14543 14544 __gshared bool fontconfigAvailable = false; 14545 // initialize fontconfig 14546 shared static this () { 14547 if (FcInit()) { 14548 fontconfigAvailable = true; 14549 } else { 14550 import core.stdc.stdio : stderr, fprintf; 14551 stderr.fprintf("***NanoVega WARNING: cannot init fontconfig!\n"); 14552 } 14553 } 14554 } 14555 14556 14557 // ////////////////////////////////////////////////////////////////////////// // 14558 public enum BaphometDims = 512.0f; // baphomet icon is 512x512 ([0..511]) 14559 14560 private static immutable ubyte[7641] baphometPath = [ 14561 0x01,0x04,0x06,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x08,0xa0,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43, 14562 0x00,0x80,0xff,0x43,0xa2,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x08,0x00,0x80,0xff, 14563 0x43,0x7a,0x89,0xe5,0x42,0xa0,0x1d,0xc6,0x43,0x00,0x00,0x00,0x00,0x30,0x89,0x7f,0x43,0x00,0x00,0x00, 14564 0x00,0x08,0x7a,0x89,0xe5,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7a,0x89,0xe5,0x42,0x00,0x00, 14565 0x00,0x00,0x30,0x89,0x7f,0x43,0x08,0x00,0x00,0x00,0x00,0xa2,0x1d,0xc6,0x43,0x7a,0x89,0xe5,0x42,0x00, 14566 0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x09,0x06,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd, 14567 0x43,0x08,0x16,0x68,0xb3,0x43,0x72,0x87,0xdd,0x43,0x71,0x87,0xdd,0x43,0x17,0x68,0xb3,0x43,0x71,0x87, 14568 0xdd,0x43,0x30,0x89,0x7f,0x43,0x08,0x71,0x87,0xdd,0x43,0xd2,0x2f,0x18,0x43,0x16,0x68,0xb3,0x43,0x35, 14569 0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x35,0xe2,0x87,0x42,0x08,0xd1,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42, 14570 0x35,0xe2,0x87,0x42,0xd2,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x08,0x35,0xe2,0x87, 14571 0x42,0x17,0x68,0xb3,0x43,0xd1,0x2f,0x18,0x43,0x72,0x87,0xdd,0x43,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd, 14572 0x43,0x09,0x06,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x07,0xa4,0x3f,0x7f,0x43,0x0b,0x86,0xdc,0x43, 14573 0x07,0x6c,0xb9,0xb2,0x43,0xe8,0xd1,0xca,0x42,0x07,0x6e,0x4d,0xa0,0x42,0xa9,0x10,0x9c,0x43,0x07,0xb7, 14574 0x40,0xd7,0x43,0xa9,0x10,0x9c,0x43,0x07,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x09,0x06,0x98,0x42, 14575 0x74,0x43,0xb1,0x8d,0x68,0x43,0x08,0xd7,0x24,0x79,0x43,0xba,0x83,0x6e,0x43,0xa9,0x16,0x7c,0x43,0x56, 14576 0xa1,0x76,0x43,0x74,0x2a,0x7d,0x43,0x44,0x73,0x80,0x43,0x08,0x55,0xd1,0x7e,0x43,0xe3,0xea,0x76,0x43, 14577 0xbc,0x18,0x81,0x43,0x7f,0xa8,0x6e,0x43,0x8f,0x0a,0x84,0x43,0x02,0xfc,0x68,0x43,0x09,0x06,0x92,0x29, 14578 0x8d,0x43,0x73,0xc3,0x67,0x43,0x08,0xa4,0xd9,0x8e,0x43,0xf2,0xa6,0x7a,0x43,0x8f,0x22,0x88,0x43,0x75, 14579 0x2a,0x7d,0x43,0x42,0x7f,0x82,0x43,0x08,0xc8,0x88,0x43,0x09,0x06,0xc1,0x79,0x74,0x43,0x50,0x64,0x89, 14580 0x43,0x08,0x68,0x2d,0x72,0x43,0xee,0x21,0x81,0x43,0xcd,0x97,0x55,0x43,0xe6,0xf1,0x7b,0x43,0x91,0xec, 14581 0x5d,0x43,0xa8,0xc7,0x6a,0x43,0x09,0x06,0xfa,0xa5,0x52,0x43,0x60,0x97,0x7c,0x43,0x08,0x19,0xff,0x50, 14582 0x43,0xe9,0x6e,0x8a,0x43,0xb0,0xbd,0x70,0x43,0x4c,0x51,0x82,0x43,0x04,0xeb,0x69,0x43,0x66,0x0f,0x8e, 14583 0x43,0x09,0x06,0x17,0xbf,0x71,0x43,0x2c,0x58,0x94,0x43,0x08,0x1c,0x96,0x6e,0x43,0x61,0x68,0x99,0x43, 14584 0x2d,0x3a,0x6e,0x43,0xc8,0x81,0x9e,0x43,0xb7,0x9b,0x72,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0x30,0xdb, 14585 0x82,0x43,0xdb,0xe9,0x93,0x43,0x08,0x11,0x82,0x84,0x43,0x61,0x68,0x99,0x43,0xe8,0x4a,0x84,0x43,0x8e, 14586 0xa6,0x9e,0x43,0x42,0x7f,0x82,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0xc4,0x02,0x85,0x43,0xd1,0x0b,0x92, 14587 0x43,0x08,0xd6,0xb2,0x86,0x43,0x34,0x1e,0x92,0x43,0x4f,0x58,0x87,0x43,0xa4,0xf1,0x92,0x43,0x03,0xd9, 14588 0x87,0x43,0x7b,0xc6,0x94,0x43,0x09,0x06,0x87,0x3e,0x64,0x43,0x31,0x3b,0x93,0x43,0x08,0x3b,0xbf,0x64, 14589 0x43,0x6f,0xf9,0x91,0x43,0x96,0x0b,0x67,0x43,0xc5,0x4a,0x91,0x43,0xcf,0xfe,0x6a,0x43,0x31,0x2f,0x91, 14590 0x43,0x09,0x06,0x16,0x74,0xb5,0x43,0x08,0xec,0x8e,0x43,0x08,0x1b,0x4b,0xb2,0x43,0xee,0x5d,0x8b,0x43, 14591 0x48,0x4d,0xad,0x43,0x12,0xa6,0x8a,0x43,0xf3,0xd7,0xa7,0x43,0x74,0xb8,0x8a,0x43,0x08,0x8c,0xb2,0xa0, 14592 0x43,0xcd,0xf8,0x8a,0x43,0x68,0x46,0x9b,0x43,0x79,0x8f,0x87,0x43,0x49,0xc9,0x96,0x43,0xe9,0x3e,0x82, 14593 0x43,0x08,0x60,0x5c,0x97,0x43,0xa1,0xde,0x8b,0x43,0x4e,0xa0,0x93,0x43,0x31,0x3b,0x93,0x43,0x9f,0xea, 14594 0x8d,0x43,0x27,0x8d,0x99,0x43,0x08,0x07,0xe0,0x8c,0x43,0x06,0x34,0x9b,0x43,0x38,0xe9,0x8c,0x43,0x46, 14595 0x0a,0x9e,0x43,0x3d,0xcc,0x8b,0x43,0xb2,0x06,0xa2,0x43,0x08,0xf1,0x40,0x8a,0x43,0xb0,0x12,0xa4,0x43, 14596 0x39,0xd1,0x88,0x43,0x76,0x43,0xa6,0x43,0xfa,0x06,0x88,0x43,0xa4,0x75,0xa9,0x43,0x08,0x19,0x6c,0x88, 14597 0x43,0x9f,0x9e,0xac,0x43,0x66,0xeb,0x87,0x43,0x44,0x76,0xb0,0x43,0x6b,0xce,0x86,0x43,0x3b,0xbc,0xb4, 14598 0x43,0x08,0xa9,0x8c,0x85,0x43,0x06,0xd0,0xb5,0x43,0xfa,0xee,0x83,0x43,0x74,0xa3,0xb6,0x43,0x3d,0x90, 14599 0x81,0x43,0x31,0xf6,0xb6,0x43,0x08,0x9d,0x61,0x7d,0x43,0xee,0x48,0xb7,0x43,0x3b,0x1f,0x75,0x43,0xcf, 14600 0xe3,0xb6,0x43,0xee,0x6f,0x6d,0x43,0x68,0xe2,0xb5,0x43,0x08,0xd4,0xed,0x6b,0x43,0x87,0x2f,0xb2,0x43, 14601 0x0e,0xc9,0x6b,0x43,0xa7,0x7c,0xae,0x43,0x98,0xfa,0x67,0x43,0xab,0x53,0xab,0x43,0x08,0x25,0x2c,0x64, 14602 0x43,0x33,0xa2,0xa8,0x43,0x40,0x96,0x61,0x43,0xc3,0xc2,0xa5,0x43,0x64,0xde,0x60,0x43,0xfa,0xa2,0xa2, 14603 0x43,0x08,0xb0,0x5d,0x60,0x43,0x06,0x4c,0x9f,0x43,0x9a,0xca,0x5f,0x43,0x38,0x3d,0x9b,0x43,0x3b,0x8f, 14604 0x5c,0x43,0x85,0xb0,0x98,0x43,0x08,0x42,0x36,0x51,0x43,0x3d,0xf0,0x91,0x43,0xcd,0x4f,0x49,0x43,0xdb, 14605 0xb9,0x8b,0x43,0xe0,0xdb,0x44,0x43,0x42,0x8b,0x84,0x43,0x08,0x7e,0xc9,0x44,0x43,0x8a,0x57,0x8d,0x43, 14606 0xbc,0x6c,0x0f,0x43,0x23,0x62,0x8e,0x43,0xf5,0x17,0x07,0x43,0xc5,0x3e,0x8f,0x43,0x09,0x06,0xe0,0xea, 14607 0x76,0x43,0xab,0xef,0xc5,0x43,0x08,0x12,0x00,0x79,0x43,0xab,0xcb,0xbf,0x43,0x79,0xb9,0x6d,0x43,0x7e, 14608 0x8d,0xba,0x43,0xee,0x6f,0x6d,0x43,0x98,0xeb,0xb5,0x43,0x08,0xe0,0x02,0x7b,0x43,0x5f,0x1c,0xb8,0x43, 14609 0x85,0x2c,0x82,0x43,0xe9,0x65,0xb8,0x43,0xd6,0xb2,0x86,0x43,0xc6,0x05,0xb5,0x43,0x08,0x03,0xcd,0x85, 14610 0x43,0x5a,0x39,0xb9,0x43,0xe4,0x4f,0x81,0x43,0xdb,0xd4,0xbf,0x43,0xdf,0x6c,0x82,0x43,0xbc,0x93,0xc5, 14611 0x43,0x09,0x06,0xf0,0xd0,0x22,0x43,0x5d,0x19,0x08,0x43,0x08,0xbc,0xab,0x49,0x43,0x4a,0x35,0x29,0x43, 14612 0xcb,0xf7,0x65,0x43,0xce,0x37,0x45,0x43,0x0e,0x99,0x63,0x43,0x67,0xc6,0x5c,0x43,0x09,0x06,0x05,0x94, 14613 0xab,0x43,0xc2,0x13,0x04,0x43,0x08,0x9f,0x26,0x98,0x43,0x11,0x42,0x25,0x43,0x97,0x00,0x8a,0x43,0x32, 14614 0x32,0x41,0x43,0xf5,0x2f,0x8b,0x43,0xc7,0xc0,0x58,0x43,0x09,0x06,0x8f,0x85,0x48,0x43,0xe0,0xa8,0x8c, 14615 0x43,0x08,0x55,0xaa,0x48,0x43,0xe0,0xa8,0x8c,0x43,0x6b,0x3d,0x49,0x43,0xc1,0x43,0x8c,0x43,0x31,0x62, 14616 0x49,0x43,0xc1,0x43,0x8c,0x43,0x08,0x2f,0xe3,0x2f,0x43,0xad,0xe7,0x98,0x43,0xff,0x0d,0x0d,0x43,0xad, 14617 0xf3,0x9a,0x43,0xf0,0xaf,0xcc,0x42,0x74,0x00,0x97,0x43,0x08,0xbb,0xa2,0xf7,0x42,0x93,0x4d,0x93,0x43, 14618 0x5e,0x19,0x08,0x43,0x5a,0x2a,0x87,0x43,0x23,0x6e,0x10,0x43,0x42,0x97,0x86,0x43,0x08,0xca,0xe8,0x33, 14619 0x43,0x1b,0x3c,0x80,0x43,0x80,0xe8,0x4d,0x43,0xda,0xf4,0x70,0x43,0xae,0x0e,0x4f,0x43,0x2b,0x1b,0x65, 14620 0x43,0x08,0x66,0x96,0x54,0x43,0xa3,0xe1,0x3b,0x43,0x4e,0xc4,0x19,0x43,0xa0,0x1a,0x16,0x43,0x10,0xe2, 14621 0x14,0x43,0x26,0x14,0xe0,0x42,0x08,0x5c,0x91,0x1c,0x43,0xcb,0x27,0xee,0x42,0xa9,0x40,0x24,0x43,0x71, 14622 0x3b,0xfc,0x42,0xf3,0xef,0x2b,0x43,0x8b,0x27,0x05,0x43,0x08,0xe2,0x4b,0x2c,0x43,0x48,0x86,0x07,0x43, 14623 0x79,0x62,0x2f,0x43,0x05,0xe5,0x09,0x43,0x55,0x32,0x34,0x43,0xa0,0xd2,0x09,0x43,0x08,0x74,0xa3,0x36, 14624 0x43,0x3a,0xd1,0x08,0x43,0x7e,0x81,0x38,0x43,0x09,0xd4,0x0a,0x43,0x0d,0xba,0x39,0x43,0xa0,0xea,0x0d, 14625 0x43,0x08,0x6f,0xe4,0x3d,0x43,0x43,0xc7,0x0e,0x43,0xd6,0xe5,0x3e,0x43,0xc4,0x4a,0x11,0x43,0x55,0x7a, 14626 0x40,0x43,0x59,0x72,0x13,0x43,0x08,0x55,0x92,0x44,0x43,0xbf,0x73,0x14,0x43,0x23,0x95,0x46,0x43,0xa5, 14627 0x09,0x17,0x43,0xe0,0xf3,0x48,0x43,0xfe,0x55,0x19,0x43,0x08,0xcd,0x4f,0x49,0x43,0xaa,0x10,0x1c,0x43, 14628 0x61,0x77,0x4b,0x43,0xfe,0x6d,0x1d,0x43,0x80,0xe8,0x4d,0x43,0x2b,0x94,0x1e,0x43,0x08,0x58,0xc9,0x51, 14629 0x43,0x41,0x27,0x1f,0x43,0x9b,0x82,0x53,0x43,0x35,0x72,0x20,0x43,0x53,0xf2,0x54,0x43,0x88,0xcf,0x21, 14630 0x43,0x08,0x7b,0x29,0x55,0x43,0xe8,0x0a,0x25,0x43,0xb2,0x2d,0x58,0x43,0xef,0xe8,0x26,0x43,0x9b,0xb2, 14631 0x5b,0x43,0xd0,0x8f,0x28,0x43,0x08,0x5f,0xef,0x5f,0x43,0xeb,0x11,0x2a,0x43,0xfd,0xdc,0x5f,0x43,0x6e, 14632 0x95,0x2c,0x43,0x3b,0xa7,0x60,0x43,0x2b,0xf4,0x2e,0x43,0x08,0x06,0xbb,0x61,0x43,0xfd,0xe5,0x31,0x43, 14633 0xe7,0x61,0x63,0x43,0xef,0x30,0x33,0x43,0x53,0x52,0x65,0x43,0xa3,0xb1,0x33,0x43,0x08,0x12,0xa0,0x68, 14634 0x43,0x7f,0x69,0x34,0x43,0x40,0xc6,0x69,0x43,0x64,0xff,0x36,0x43,0x7e,0x90,0x6a,0x43,0x71,0xcc,0x39, 14635 0x43,0x08,0xbc,0x5a,0x6b,0x43,0x51,0x73,0x3b,0x43,0xc1,0x49,0x6c,0x43,0xa5,0xd0,0x3c,0x43,0xe0,0xba, 14636 0x6e,0x43,0xb8,0x74,0x3c,0x43,0x08,0x6b,0x1c,0x73,0x43,0x13,0xc1,0x3e,0x43,0x40,0xf6,0x71,0x43,0xce, 14637 0x1f,0x41,0x43,0x55,0x89,0x72,0x43,0x8d,0x7e,0x43,0x43,0x08,0x68,0x2d,0x72,0x43,0x89,0xae,0x4b,0x43, 14638 0xc1,0x79,0x74,0x43,0xcb,0x78,0x4c,0x43,0x55,0xa1,0x76,0x43,0x5b,0xb1,0x4d,0x43,0x08,0xa2,0x38,0x7a, 14639 0x43,0xd1,0x56,0x4e,0x43,0x85,0xb6,0x78,0x43,0xb1,0x15,0x54,0x43,0x83,0xc7,0x77,0x43,0x89,0x0e,0x5c, 14640 0x43,0x08,0xcf,0x46,0x77,0x43,0x0f,0x81,0x5f,0x43,0x1a,0xde,0x7a,0x43,0xce,0xc7,0x5d,0x43,0x42,0x73, 14641 0x80,0x43,0x99,0xc3,0x5a,0x43,0x08,0x85,0x2c,0x82,0x43,0xf6,0xe6,0x59,0x43,0x81,0x3d,0x81,0x43,0x16, 14642 0x10,0x50,0x43,0xd6,0x8e,0x80,0x43,0x5b,0x99,0x49,0x43,0x08,0xc4,0xea,0x80,0x43,0x22,0x95,0x46,0x43, 14643 0xfa,0xe2,0x81,0x43,0xda,0xec,0x43,0x43,0x78,0x77,0x83,0x43,0xe4,0xb2,0x41,0x43,0x08,0x8a,0x27,0x85, 14644 0x43,0x86,0x77,0x3e,0x43,0x0c,0x9f,0x85,0x43,0x07,0xf4,0x3b,0x43,0x8f,0x16,0x86,0x43,0xe6,0x82,0x39, 14645 0x43,0x08,0x85,0x44,0x86,0x43,0x37,0xd9,0x35,0x43,0x1e,0x4f,0x87,0x43,0xe1,0x7b,0x34,0x43,0xdf,0x90, 14646 0x88,0x43,0xb6,0x55,0x33,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0xe5,0x31,0x43,0xfa,0x12,0x8a,0x43,0xbf, 14647 0x03,0x2d,0x43,0x19,0x78,0x8a,0x43,0x45,0x5e,0x2c,0x43,0x08,0x03,0xf1,0x8b,0x43,0xac,0x47,0x29,0x43, 14648 0x2f,0x17,0x8d,0x43,0x45,0x46,0x28,0x43,0xc8,0x21,0x8e,0x43,0x30,0xb3,0x27,0x43,0x08,0xa9,0xc8,0x8f, 14649 0x43,0xef,0xe8,0x26,0x43,0xbf,0x5b,0x90,0x43,0x5b,0xc1,0x24,0x43,0x10,0xca,0x90,0x43,0xa0,0x62,0x22, 14650 0x43,0x08,0x26,0x5d,0x91,0x43,0xbb,0xcc,0x1f,0x43,0xf0,0x70,0x92,0x43,0x78,0x13,0x1e,0x43,0x77,0xd7, 14651 0x93,0x43,0x73,0x24,0x1d,0x43,0x08,0x65,0x3f,0x96,0x43,0xce,0x58,0x1b,0x43,0xbe,0x7f,0x96,0x43,0xbf, 14652 0x8b,0x18,0x43,0x60,0x5c,0x97,0x43,0xb6,0xad,0x16,0x43,0x08,0xba,0xa8,0x99,0x43,0x78,0xcb,0x11,0x43, 14653 0x49,0xe1,0x9a,0x43,0x78,0xcb,0x11,0x43,0x01,0x51,0x9c,0x43,0x73,0xdc,0x10,0x43,0x08,0x72,0x24,0x9d, 14654 0x43,0xd2,0xff,0x0f,0x43,0x1c,0xd3,0x9d,0x43,0x07,0xec,0x0e,0x43,0xeb,0xc9,0x9d,0x43,0xe8,0x7a,0x0c, 14655 0x43,0x08,0x60,0x80,0x9d,0x43,0xd7,0xbe,0x08,0x43,0x4d,0xe8,0x9f,0x43,0x86,0x50,0x08,0x43,0x25,0xbd, 14656 0xa1,0x43,0x5b,0x2a,0x07,0x43,0x08,0x99,0x7f,0xa3,0x43,0xc9,0xf1,0x05,0x43,0x48,0x1d,0xa5,0x43,0x86, 14657 0x38,0x04,0x43,0x6c,0x71,0xa6,0x43,0x18,0x59,0x01,0x43,0x08,0x32,0x96,0xa6,0x43,0x6e,0x64,0xff,0x42, 14658 0x48,0x29,0xa7,0x43,0xed,0xcf,0xfd,0x42,0x5f,0xbc,0xa7,0x43,0x71,0x3b,0xfc,0x42,0x08,0xf3,0xe3,0xa9, 14659 0x43,0xf7,0x7d,0xf7,0x42,0xd8,0x6d,0xaa,0x43,0x45,0xe5,0xf2,0x42,0x48,0x41,0xab,0x43,0xcb,0x27,0xee, 14660 0x42,0x08,0x24,0xf9,0xab,0x43,0x52,0x6a,0xe9,0x42,0xee,0x0c,0xad,0x43,0x4c,0x8c,0xe7,0x42,0x1b,0x33, 14661 0xae,0x43,0xcc,0xf7,0xe5,0x42,0x08,0xaa,0x6b,0xaf,0x43,0xe8,0x61,0xe3,0x42,0x90,0xf5,0xaf,0x43,0xc9, 14662 0xf0,0xe0,0x42,0xe0,0x63,0xb0,0x43,0xe5,0x5a,0xde,0x42,0x08,0xaa,0x83,0xb3,0x43,0x29,0x2d,0x09,0x43, 14663 0x6a,0xfe,0x8e,0x43,0xb8,0x74,0x3c,0x43,0xd5,0x06,0x95,0x43,0xe6,0x79,0x67,0x43,0x08,0x2f,0x53,0x97, 14664 0x43,0xe9,0xb0,0x74,0x43,0xa8,0x28,0xa0,0x43,0x43,0xfd,0x76,0x43,0x83,0x28,0xad,0x43,0x17,0x59,0x81, 14665 0x43,0x08,0x3d,0xe7,0xbf,0x43,0x4b,0x8d,0x8c,0x43,0xae,0x96,0xba,0x43,0x66,0x27,0x92,0x43,0x15,0xe0, 14666 0xc7,0x43,0x6f,0x11,0x96,0x43,0x08,0x7e,0x5d,0xb2,0x43,0xdb,0x01,0x98,0x43,0x9e,0x56,0xa0,0x43,0x80, 14667 0xc1,0x97,0x43,0x69,0x2e,0x97,0x43,0x31,0x17,0x8d,0x43,0x09,0x06,0xab,0xa7,0x39,0x43,0x67,0x0f,0x0e, 14668 0x43,0x08,0xdb,0xbc,0x3b,0x43,0xe8,0x92,0x10,0x43,0xb5,0x85,0x3b,0x43,0x97,0x3c,0x14,0x43,0xab,0xa7, 14669 0x39,0x43,0x0c,0x0b,0x18,0x43,0x09,0x06,0xca,0x30,0x40,0x43,0x30,0x3b,0x13,0x43,0x08,0x17,0xc8,0x43, 14670 0x43,0xa5,0x09,0x17,0x43,0x7e,0xc9,0x44,0x43,0x1a,0xd8,0x1a,0x43,0x9d,0x22,0x43,0x43,0x8d,0xa6,0x1e, 14671 0x43,0x09,0x06,0xc8,0x78,0x4c,0x43,0xed,0xc9,0x1d,0x43,0x08,0x0b,0x32,0x4e,0x43,0x22,0xce,0x20,0x43, 14672 0x23,0xc5,0x4e,0x43,0x58,0xd2,0x23,0x43,0x0b,0x32,0x4e,0x43,0x2b,0xc4,0x26,0x43,0x09,0x06,0xec,0x08, 14673 0x58,0x43,0xc7,0xb1,0x26,0x43,0x08,0x02,0x9c,0x58,0x43,0xef,0x00,0x2b,0x43,0xd9,0x64,0x58,0x43,0x02, 14674 0xbd,0x2e,0x43,0x10,0x51,0x57,0x43,0x37,0xc1,0x31,0x43,0x09,0x06,0xcb,0xdf,0x61,0x43,0x4a,0x65,0x31, 14675 0x43,0x08,0xbe,0x2a,0x63,0x43,0xbd,0x33,0x35,0x43,0x32,0xe1,0x62,0x43,0x56,0x4a,0x38,0x43,0xde,0x83, 14676 0x61,0x43,0x3c,0xe0,0x3a,0x43,0x09,0x06,0x1c,0x7e,0x6a,0x43,0x5b,0x39,0x39,0x43,0x08,0x31,0x11,0x6b, 14677 0x43,0x0c,0xd2,0x3d,0x43,0x1c,0x7e,0x6a,0x43,0x13,0xd9,0x42,0x43,0xd9,0xc4,0x68,0x43,0xcb,0x60,0x48, 14678 0x43,0x09,0x06,0xe5,0xc1,0x73,0x43,0x16,0xf8,0x4b,0x43,0x08,0xa6,0xf7,0x72,0x43,0xb1,0xfd,0x4f,0x43, 14679 0x3b,0x07,0x71,0x43,0x4a,0x14,0x53,0x43,0xa2,0xf0,0x6d,0x43,0x7c,0x29,0x55,0x43,0x09,0x06,0x00,0x8d, 14680 0xa6,0x43,0xef,0x21,0x01,0x43,0x08,0x52,0xfb,0xa6,0x43,0xce,0xc8,0x02,0x43,0xe6,0x16,0xa7,0x43,0x51, 14681 0x4c,0x05,0x43,0x3b,0x68,0xa6,0x43,0x4c,0x75,0x08,0x43,0x09,0x06,0xde,0x20,0xa1,0x43,0x86,0x50,0x08, 14682 0x43,0x08,0xd4,0x4e,0xa1,0x43,0xd3,0xe7,0x0b,0x43,0xb5,0xe9,0xa0,0x43,0x59,0x5a,0x0f,0x43,0xba,0xcc, 14683 0x9f,0x43,0x54,0x83,0x12,0x43,0x09,0x06,0x77,0xfb,0x99,0x43,0x6c,0x16,0x13,0x43,0x08,0xde,0xfc,0x9a, 14684 0x43,0x4a,0xbd,0x14,0x43,0x06,0x34,0x9b,0x43,0xfe,0x55,0x19,0x43,0x13,0xe9,0x99,0x43,0x41,0x27,0x1f, 14685 0x43,0x09,0x06,0x46,0xce,0x93,0x43,0x26,0xa5,0x1d,0x43,0x08,0xe7,0xaa,0x94,0x43,0xbb,0xcc,0x1f,0x43, 14686 0x18,0xb4,0x94,0x43,0xa8,0x40,0x24,0x43,0xe2,0xbb,0x93,0x43,0x21,0xfe,0x28,0x43,0x09,0x06,0xb1,0x8e, 14687 0x8d,0x43,0xa8,0x58,0x28,0x43,0x08,0x19,0x90,0x8e,0x43,0x54,0x13,0x2b,0x43,0xa4,0xd9,0x8e,0x43,0x84, 14688 0x40,0x31,0x43,0x46,0xaa,0x8d,0x43,0x29,0x24,0x37,0x43,0x09,0x06,0xd6,0xbe,0x88,0x43,0xef,0x30,0x33, 14689 0x43,0x08,0x0c,0xb7,0x89,0x43,0x0e,0xa2,0x35,0x43,0xc0,0x37,0x8a,0x43,0x7a,0xaa,0x3b,0x43,0xbb,0x48, 14690 0x89,0x43,0xbb,0x7b,0x41,0x43,0x09,0x06,0x3a,0xad,0x82,0x43,0xc4,0x59,0x43,0x43,0x08,0xd2,0xb7,0x83, 14691 0x43,0x2b,0x5b,0x44,0x43,0x35,0xd6,0x85,0x43,0x48,0xf5,0x49,0x43,0x42,0x97,0x86,0x43,0xc4,0xa1,0x4f, 14692 0x43,0x09,0x06,0x9c,0xb3,0x80,0x43,0x48,0x55,0x5a,0x43,0x08,0xff,0xc5,0x80,0x43,0x09,0x73,0x55,0x43, 14693 0x93,0xe1,0x80,0x43,0x0f,0x39,0x53,0x43,0xf1,0xbe,0x7e,0x43,0x18,0xe7,0x4c,0x43,0x09,0x06,0xe0,0x02, 14694 0x7b,0x43,0x92,0xec,0x5d,0x43,0x08,0x09,0x3a,0x7b,0x43,0xf0,0xf7,0x58,0x43,0x09,0x3a,0x7b,0x43,0xe6, 14695 0x31,0x5b,0x43,0xe0,0x02,0x7b,0x43,0xa8,0x4f,0x56,0x43,0x09,0x06,0x39,0x4f,0x7d,0x43,0x3e,0x8f,0x5c, 14696 0x43,0x08,0xe9,0xe0,0x7c,0x43,0x03,0x9c,0x58,0x43,0x1e,0x2b,0x81,0x43,0x7f,0x30,0x5a,0x43,0xff,0x73, 14697 0x7d,0x43,0xf6,0xb6,0x51,0x43,0x09,0x06,0x5c,0xb8,0x52,0x43,0x28,0x21,0x87,0x43,0x08,0xae,0x3e,0x57, 14698 0x43,0x12,0x9a,0x88,0x43,0x23,0xf5,0x56,0x43,0x04,0xf1,0x8b,0x43,0x25,0xfc,0x5b,0x43,0x85,0x74,0x8e, 14699 0x43,0x08,0x2f,0xf2,0x61,0x43,0x8e,0x52,0x90,0x43,0xd9,0xdc,0x6c,0x43,0x85,0x74,0x8e,0x43,0xc6,0x20, 14700 0x69,0x43,0x3d,0xd8,0x8d,0x43,0x08,0x6d,0x8c,0x5a,0x43,0xf5,0x3b,0x8d,0x43,0x3d,0x77,0x58,0x43,0xa1, 14701 0xc6,0x87,0x43,0xf8,0xed,0x5e,0x43,0x5e,0x0d,0x86,0x43,0x09,0x06,0xde,0xcc,0x92,0x43,0xf7,0x17,0x87, 14702 0x43,0x08,0xb6,0x89,0x90,0x43,0xae,0x87,0x88,0x43,0x4a,0xa5,0x90,0x43,0xa1,0xde,0x8b,0x43,0xf9,0x2a, 14703 0x8e,0x43,0x23,0x62,0x8e,0x43,0x08,0xf5,0x2f,0x8b,0x43,0x5c,0x49,0x90,0x43,0x35,0xd6,0x85,0x43,0x8e, 14704 0x46,0x8e,0x43,0x3d,0xb4,0x87,0x43,0x47,0xaa,0x8d,0x43,0x08,0x6a,0xfe,0x8e,0x43,0xff,0x0d,0x8d,0x43, 14705 0xbb,0x6c,0x8f,0x43,0xf7,0x17,0x87,0x43,0x5c,0x31,0x8c,0x43,0xb2,0x5e,0x85,0x43,0x09,0x06,0x60,0x38, 14706 0x91,0x43,0x69,0x5d,0x7a,0x43,0x08,0x34,0x1e,0x92,0x43,0x1e,0x5b,0x89,0x43,0x04,0x63,0x7e,0x43,0x5e, 14707 0x01,0x84,0x43,0x59,0x2a,0x87,0x43,0x0d,0xcf,0x8d,0x43,0x09,0x03,0x04,0x06,0x5a,0x18,0x63,0x43,0x82, 14708 0x79,0x8b,0x43,0x08,0x25,0x2c,0x64,0x43,0x82,0x79,0x8b,0x43,0x2a,0x1b,0x65,0x43,0x9d,0xef,0x8a,0x43, 14709 0x2a,0x1b,0x65,0x43,0xc1,0x37,0x8a,0x43,0x08,0x2a,0x1b,0x65,0x43,0x17,0x89,0x89,0x43,0x25,0x2c,0x64, 14710 0x43,0x31,0xff,0x88,0x43,0x5a,0x18,0x63,0x43,0x31,0xff,0x88,0x43,0x08,0xf3,0x16,0x62,0x43,0x31,0xff, 14711 0x88,0x43,0xee,0x27,0x61,0x43,0x17,0x89,0x89,0x43,0xee,0x27,0x61,0x43,0xc1,0x37,0x8a,0x43,0x08,0xee, 14712 0x27,0x61,0x43,0x9d,0xef,0x8a,0x43,0xf3,0x16,0x62,0x43,0x82,0x79,0x8b,0x43,0x5a,0x18,0x63,0x43,0x82, 14713 0x79,0x8b,0x43,0x09,0x06,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x08,0x34,0xee,0x89,0x43,0x82,0x79, 14714 0x8b,0x43,0x85,0x5c,0x8a,0x43,0x9d,0xef,0x8a,0x43,0x85,0x5c,0x8a,0x43,0xc1,0x37,0x8a,0x43,0x08,0x85, 14715 0x5c,0x8a,0x43,0x17,0x89,0x89,0x43,0x34,0xee,0x89,0x43,0x31,0xff,0x88,0x43,0x4f,0x64,0x89,0x43,0x31, 14716 0xff,0x88,0x43,0x08,0x9c,0xe3,0x88,0x43,0x31,0xff,0x88,0x43,0x19,0x6c,0x88,0x43,0x17,0x89,0x89,0x43, 14717 0x19,0x6c,0x88,0x43,0xc1,0x37,0x8a,0x43,0x08,0x19,0x6c,0x88,0x43,0x9d,0xef,0x8a,0x43,0x9c,0xe3,0x88, 14718 0x43,0x82,0x79,0x8b,0x43,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x09,0x02,0x04,0x06,0x19,0x60,0x86, 14719 0x43,0xec,0xed,0xa3,0x43,0x08,0x35,0xd6,0x85,0x43,0x76,0x43,0xa6,0x43,0x93,0xe1,0x80,0x43,0x57,0x02, 14720 0xac,0x43,0x61,0xd8,0x80,0x43,0x87,0x17,0xae,0x43,0x08,0xa5,0x85,0x80,0x43,0xc3,0xfe,0xaf,0x43,0xce, 14721 0xbc,0x80,0x43,0x83,0x40,0xb1,0x43,0xa5,0x91,0x82,0x43,0x79,0x6e,0xb1,0x43,0x08,0x23,0x26,0x84,0x43, 14722 0x40,0x93,0xb1,0x43,0x30,0xe7,0x84,0x43,0xbe,0x1b,0xb1,0x43,0x11,0x82,0x84,0x43,0xab,0x6b,0xaf,0x43, 14723 0x08,0xb7,0x41,0x84,0x43,0x3b,0x98,0xae,0x43,0xb7,0x41,0x84,0x43,0xc3,0xf2,0xad,0x43,0xa1,0xae,0x83, 14724 0x43,0x83,0x28,0xad,0x43,0x08,0xb2,0x52,0x83,0x43,0x80,0x39,0xac,0x43,0x81,0x49,0x83,0x43,0xf0,0x00, 14725 0xab,0x43,0xe4,0x67,0x85,0x43,0x76,0x4f,0xa8,0x43,0x08,0x9c,0xd7,0x86,0x43,0xd1,0x83,0xa6,0x43,0xec, 14726 0x45,0x87,0x43,0x01,0x75,0xa2,0x43,0x19,0x60,0x86,0x43,0xec,0xed,0xa3,0x43,0x09,0x06,0xd9,0xdc,0x6c, 14727 0x43,0x14,0x25,0xa4,0x43,0x08,0xa2,0xf0,0x6d,0x43,0x9f,0x7a,0xa6,0x43,0x47,0xec,0x77,0x43,0x80,0x39, 14728 0xac,0x43,0xa9,0xfe,0x77,0x43,0xb0,0x4e,0xae,0x43,0x08,0x23,0xa4,0x78,0x43,0xea,0x35,0xb0,0x43,0xd2, 14729 0x35,0x78,0x43,0xab,0x77,0xb1,0x43,0xc1,0x79,0x74,0x43,0xa2,0xa5,0xb1,0x43,0x08,0xc6,0x50,0x71,0x43, 14730 0x68,0xca,0xb1,0x43,0xab,0xce,0x6f,0x43,0xe7,0x52,0xb1,0x43,0xea,0x98,0x70,0x43,0xd4,0xa2,0xaf,0x43, 14731 0x08,0x9d,0x19,0x71,0x43,0x96,0xd8,0xae,0x43,0x9d,0x19,0x71,0x43,0xec,0x29,0xae,0x43,0xca,0x3f,0x72, 14732 0x43,0xab,0x5f,0xad,0x43,0x08,0xa6,0xf7,0x72,0x43,0xa7,0x70,0xac,0x43,0x09,0x0a,0x73,0x43,0x17,0x38, 14733 0xab,0x43,0x44,0xcd,0x6e,0x43,0x9f,0x86,0xa8,0x43,0x08,0xd4,0xed,0x6b,0x43,0xf8,0xba,0xa6,0x43,0x31, 14734 0x11,0x6b,0x43,0x2a,0xac,0xa2,0x43,0xd9,0xdc,0x6c,0x43,0x14,0x25,0xa4,0x43,0x09,0x01,0x05,0x06,0x66, 14735 0x5d,0x7a,0x43,0x74,0xeb,0xc2,0x43,0x08,0x09,0x22,0x77,0x43,0x50,0xbb,0xc7,0x43,0xe9,0xe0,0x7c,0x43, 14736 0xf5,0x86,0xc9,0x43,0x8f,0x94,0x7a,0x43,0xc5,0x95,0xcd,0x43,0x09,0x06,0x08,0x98,0x80,0x43,0x6b,0x19, 14737 0xc3,0x43,0x08,0xb7,0x35,0x82,0x43,0x79,0xf2,0xc7,0x43,0xf1,0xbe,0x7e,0x43,0x1e,0xbe,0xc9,0x43,0x73, 14738 0x7c,0x80,0x43,0xec,0xcc,0xcd,0x43,0x09,0x06,0x28,0xab,0x7d,0x43,0xae,0xde,0xc6,0x43,0x08,0x1e,0xcd, 14739 0x7b,0x43,0x8a,0xa2,0xc9,0x43,0x30,0x89,0x7f,0x43,0x5c,0x94,0xcc,0x43,0x28,0xab,0x7d,0x43,0x42,0x2a, 14740 0xcf,0x43,0x09,0x01,0x05,0x06,0x24,0x14,0xe0,0x42,0xf5,0x77,0x97,0x43,0x08,0xf7,0x1d,0xe7,0x42,0x74, 14741 0x00,0x97,0x43,0x4d,0x93,0xec,0x42,0xdb,0xf5,0x95,0x43,0x29,0x4b,0xed,0x42,0xcd,0x34,0x95,0x43,0x09, 14742 0x06,0x29,0x7b,0xf5,0x42,0x6f,0x1d,0x98,0x43,0x08,0xe4,0xf1,0xfb,0x42,0x61,0x5c,0x97,0x43,0xdb,0x7d, 14743 0x01,0x43,0xb2,0xbe,0x95,0x43,0x55,0x23,0x02,0x43,0xe7,0xaa,0x94,0x43,0x09,0x06,0x98,0xdc,0x03,0x43, 14744 0xbe,0x8b,0x98,0x43,0x08,0x66,0xdf,0x05,0x43,0x47,0xe6,0x97,0x43,0xae,0x87,0x08,0x43,0x98,0x48,0x96, 14745 0x43,0x61,0x08,0x09,0x43,0xd6,0x06,0x95,0x43,0x09,0x06,0x31,0x0b,0x0b,0x43,0x8e,0x82,0x98,0x43,0x08, 14746 0xdb,0xc5,0x0d,0x43,0x80,0xc1,0x97,0x43,0xd6,0xee,0x10,0x43,0xa9,0xec,0x95,0x43,0x79,0xcb,0x11,0x43, 14747 0x55,0x8f,0x94,0x43,0x09,0x06,0xd1,0x2f,0x18,0x43,0xdb,0x01,0x98,0x43,0x08,0xad,0xe7,0x18,0x43,0x38, 14748 0x25,0x97,0x43,0x8a,0x9f,0x19,0x43,0x80,0xb5,0x95,0x43,0xd6,0x1e,0x19,0x43,0xe0,0xd8,0x94,0x43,0x09, 14749 0x06,0x9a,0x5b,0x1d,0x43,0x58,0x8a,0x97,0x43,0x08,0x01,0x5d,0x1e,0x43,0xf1,0x88,0x96,0x43,0x2f,0x83, 14750 0x1f,0x43,0x19,0xb4,0x94,0x43,0x19,0xf0,0x1e,0x43,0x6f,0x05,0x94,0x43,0x09,0x06,0x0b,0x53,0x24,0x43, 14751 0xae,0xdb,0x96,0x43,0x08,0x25,0xd5,0x25,0x43,0x50,0xac,0x95,0x43,0x53,0xfb,0x26,0x43,0x8a,0x7b,0x93, 14752 0x43,0x76,0x43,0x26,0x43,0xb7,0x95,0x92,0x43,0x09,0x06,0x76,0x5b,0x2a,0x43,0x47,0xda,0x95,0x43,0x08, 14753 0xf3,0xef,0x2b,0x43,0x10,0xe2,0x94,0x43,0x6d,0x95,0x2c,0x43,0xae,0xc3,0x92,0x43,0x68,0xa6,0x2b,0x43, 14754 0x47,0xc2,0x91,0x43,0x09,0x06,0x36,0xc1,0x31,0x43,0x2c,0x58,0x94,0x43,0x08,0x8c,0x1e,0x33,0x43,0x31, 14755 0x3b,0x93,0x43,0x79,0x7a,0x33,0x43,0xff,0x25,0x91,0x43,0xd9,0x9d,0x32,0x43,0xc1,0x5b,0x90,0x43,0x09, 14756 0x06,0x25,0x35,0x36,0x43,0x31,0x3b,0x93,0x43,0x08,0x3f,0xb7,0x37,0x43,0xc1,0x67,0x92,0x43,0xe0,0x93, 14757 0x38,0x43,0xae,0xb7,0x90,0x43,0x7e,0x81,0x38,0x43,0x0d,0xdb,0x8f,0x43,0x09,0x06,0xb5,0x85,0x3b,0x43, 14758 0xe4,0xaf,0x91,0x43,0x08,0xcf,0x07,0x3d,0x43,0x9d,0x13,0x91,0x43,0xbc,0x63,0x3d,0x43,0x47,0xb6,0x8f, 14759 0x43,0xe5,0x9a,0x3d,0x43,0x74,0xd0,0x8e,0x43,0x09,0x06,0xae,0xc6,0x42,0x43,0xa4,0xd9,0x8e,0x43,0x08, 14760 0xca,0x48,0x44,0x43,0xfa,0x2a,0x8e,0x43,0xa2,0x11,0x44,0x43,0x9d,0xfb,0x8c,0x43,0x55,0x92,0x44,0x43, 14761 0x0d,0xc3,0x8b,0x43,0x09,0x06,0x39,0x10,0xc3,0x43,0x34,0x36,0x96,0x43,0x08,0x92,0x44,0xc1,0x43,0xe4, 14762 0xc7,0x95,0x43,0x6f,0xf0,0xbf,0x43,0x4b,0xbd,0x94,0x43,0x47,0xb9,0xbf,0x43,0x0b,0xf3,0x93,0x43,0x09, 14763 0x06,0x8f,0x49,0xbe,0x43,0xb7,0xad,0x96,0x43,0x08,0x11,0xb5,0xbc,0x43,0x77,0xe3,0x95,0x43,0x9c,0xf2, 14764 0xba,0x43,0xfa,0x4e,0x94,0x43,0xae,0x96,0xba,0x43,0x31,0x3b,0x93,0x43,0x09,0x06,0xdb,0xb0,0xb9,0x43, 14765 0x10,0xee,0x96,0x43,0x08,0x42,0xa6,0xb8,0x43,0xc8,0x51,0x96,0x43,0x50,0x5b,0xb7,0x43,0x19,0xb4,0x94, 14766 0x43,0xf7,0x1a,0xb7,0x43,0x58,0x72,0x93,0x43,0x09,0x06,0xf2,0x2b,0xb6,0x43,0x10,0xee,0x96,0x43,0x08, 14767 0x9d,0xce,0xb4,0x43,0x04,0x2d,0x96,0x43,0xed,0x30,0xb3,0x43,0x2c,0x58,0x94,0x43,0xce,0xcb,0xb2,0x43, 14768 0xd6,0xfa,0x92,0x43,0x09,0x06,0x5a,0x09,0xb1,0x43,0x19,0xc0,0x96,0x43,0x08,0x6c,0xad,0xb0,0x43,0x77, 14769 0xe3,0x95,0x43,0x7e,0x51,0xb0,0x43,0xc0,0x73,0x94,0x43,0xd8,0x91,0xb0,0x43,0x1e,0x97,0x93,0x43,0x09, 14770 0x06,0x48,0x4d,0xad,0x43,0xbe,0x7f,0x96,0x43,0x08,0x95,0xcc,0xac,0x43,0x58,0x7e,0x95,0x43,0x4d,0x30, 14771 0xac,0x43,0x80,0xa9,0x93,0x43,0xd8,0x79,0xac,0x43,0xd6,0xfa,0x92,0x43,0x09,0x06,0x90,0xd1,0xa9,0x43, 14772 0x14,0xd1,0x95,0x43,0x08,0x83,0x10,0xa9,0x43,0xb7,0xa1,0x94,0x43,0x3b,0x74,0xa8,0x43,0xf1,0x70,0x92, 14773 0x43,0x29,0xd0,0xa8,0x43,0x1e,0x8b,0x91,0x43,0x09,0x06,0x5a,0xcd,0xa6,0x43,0x8a,0x87,0x95,0x43,0x08, 14774 0x1c,0x03,0xa6,0x43,0x23,0x86,0x94,0x43,0x5f,0xb0,0xa5,0x43,0xc1,0x67,0x92,0x43,0xe1,0x27,0xa6,0x43, 14775 0x8a,0x6f,0x91,0x43,0x09,0x06,0xd4,0x5a,0xa3,0x43,0x2c,0x58,0x94,0x43,0x08,0x29,0xac,0xa2,0x43,0x31, 14776 0x3b,0x93,0x43,0x32,0x7e,0xa2,0x43,0xff,0x25,0x91,0x43,0x83,0xec,0xa2,0x43,0x8e,0x52,0x90,0x43,0x09, 14777 0x06,0xf8,0x96,0xa0,0x43,0x1e,0x97,0x93,0x43,0x08,0xeb,0xd5,0x9f,0x43,0x7b,0xba,0x92,0x43,0x99,0x67, 14778 0x9f,0x43,0x9d,0x13,0x91,0x43,0x99,0x67,0x9f,0x43,0xfa,0x36,0x90,0x43,0x09,0x06,0xeb,0xc9,0x9d,0x43, 14779 0xc8,0x39,0x92,0x43,0x08,0xde,0x08,0x9d,0x43,0xb2,0xa6,0x91,0x43,0xe6,0xda,0x9c,0x43,0x2c,0x40,0x90, 14780 0x43,0x52,0xbf,0x9c,0x43,0x5a,0x5a,0x8f,0x43,0x09,0x06,0x37,0x3d,0x9b,0x43,0x85,0x80,0x90,0x43,0x08, 14781 0x2a,0x7c,0x9a,0x43,0xdb,0xd1,0x8f,0x43,0xf0,0xa0,0x9a,0x43,0x7d,0xa2,0x8e,0x43,0x65,0x57,0x9a,0x43, 14782 0xee,0x69,0x8d,0x43,0x09,0x02,0x04,0x06,0x2a,0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x08,0x0d,0x8a,0x31, 14783 0x42,0x9f,0x0e,0x94,0x43,0xf3,0x1f,0x34,0x42,0x3d,0xfc,0x93,0x43,0x63,0xff,0x36,0x42,0xa9,0xe0,0x93, 14784 0x43,0x08,0xb5,0x34,0x5d,0x42,0x0b,0xf3,0x93,0x43,0x6d,0xa4,0x5e,0x42,0x03,0x39,0x98,0x43,0xe7,0x31, 14785 0x5b,0x42,0x93,0x89,0x9d,0x43,0x08,0x02,0x9c,0x58,0x42,0xd4,0x5a,0xa3,0x43,0x38,0x70,0x53,0x42,0x14, 14786 0x49,0xaa,0x43,0xf8,0xed,0x5e,0x42,0x83,0x28,0xad,0x43,0x08,0xea,0x68,0x68,0x42,0x20,0x22,0xaf,0x43, 14787 0x12,0xb8,0x6c,0x42,0xb5,0x49,0xb1,0x43,0x2a,0x4b,0x6d,0x42,0x0d,0x96,0xb3,0x43,0x07,0x2a,0x4b,0x6d, 14788 0x42,0xc6,0x05,0xb5,0x43,0x08,0x87,0x6e,0x6c,0x42,0x68,0xee,0xb7,0x43,0x1c,0x66,0x66,0x42,0x31,0x0e, 14789 0xbb,0x43,0x57,0x11,0x5e,0x42,0x8f,0x49,0xbe,0x43,0x08,0x66,0x96,0x54,0x42,0xb9,0x5c,0xb8,0x43,0x2c, 14790 0x2b,0x3c,0x42,0x68,0xd6,0xb3,0x43,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x07,0x2a,0xf4,0x2e,0x42, 14791 0x61,0xa4,0xa3,0x43,0x08,0x55,0x1a,0x30,0x42,0xf0,0xd0,0xa2,0x43,0xf8,0xf6,0x30,0x42,0xb2,0x06,0xa2, 14792 0x43,0x98,0xd3,0x31,0x42,0xd6,0x4e,0xa1,0x43,0x08,0x1c,0x6f,0x38,0x42,0x2a,0x94,0x9e,0x43,0xc1,0x22, 14793 0x36,0x42,0xf5,0x9b,0x9d,0x43,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x57, 14794 0xa2,0x9b,0x43,0x08,0xab,0x8f,0x35,0x42,0x8a,0xab,0x9b,0x43,0xe9,0x71,0x3a,0x42,0xb2,0xe2,0x9b,0x43, 14795 0xb7,0x74,0x3c,0x42,0x34,0x5a,0x9c,0x43,0x08,0x23,0x7d,0x42,0x42,0x0b,0x2f,0x9e,0x43,0xe5,0x9a,0x3d, 14796 0x42,0x38,0x6d,0xa3,0x43,0x36,0xd9,0x35,0x42,0xf3,0xd7,0xa7,0x43,0x08,0x12,0x61,0x2e,0x42,0xb0,0x42, 14797 0xac,0x43,0x63,0xff,0x36,0x42,0xdd,0x74,0xaf,0x43,0x1e,0xa6,0x45,0x42,0x44,0x82,0xb2,0x43,0x08,0x74, 14798 0x1b,0x4b,0x42,0x79,0x7a,0xb3,0x43,0x10,0x21,0x4f,0x42,0x2a,0x18,0xb5,0x43,0xdb,0x4c,0x54,0x42,0x91, 14799 0x19,0xb6,0x43,0x08,0xee,0x3f,0x65,0x42,0x5f,0x28,0xba,0x43,0xa7,0xaf,0x66,0x42,0xb9,0x50,0xb6,0x43, 14800 0x14,0x58,0x5c,0x42,0xca,0xdc,0xb1,0x43,0x08,0x2c,0x8b,0x4c,0x42,0x4e,0x30,0xac,0x43,0x19,0xcf,0x48, 14801 0x42,0x2a,0xd0,0xa8,0x43,0xbc,0xab,0x49,0x42,0xa9,0x4c,0xa6,0x43,0x08,0x61,0x5f,0x47,0x42,0xfa,0xa2, 14802 0xa2,0x43,0xa7,0xaf,0x66,0x42,0x85,0x98,0x94,0x43,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x07,0x2a, 14803 0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x09,0x06,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x08,0xdc,0xe3, 14804 0xf1,0x41,0xe9,0x9e,0x92,0x43,0xd2,0xe7,0x0b,0x42,0xd6,0x06,0x95,0x43,0x2a,0xf4,0x2e,0x42,0x04,0x21, 14805 0x94,0x43,0x07,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x08,0x87,0x17,0x2e,0x42,0xc3,0x62,0x95,0x43, 14806 0xe7,0x3a,0x2d,0x42,0xf5,0x6b,0x95,0x43,0x44,0x5e,0x2c,0x42,0xf5,0x6b,0x95,0x43,0x08,0xd1,0x47,0x1c, 14807 0x42,0x19,0xc0,0x96,0x43,0x66,0xdf,0x05,0x42,0x38,0x19,0x95,0x43,0x12,0x6a,0x00,0x42,0xb2,0xbe,0x95, 14808 0x43,0x08,0xbb,0x6b,0xea,0x41,0xd6,0x12,0x97,0x43,0x2d,0x82,0xfa,0x41,0x61,0x74,0x9b,0x43,0x7e,0x72, 14809 0x06,0x42,0x8a,0xab,0x9b,0x43,0x08,0xc8,0x39,0x12,0x42,0x4e,0xd0,0x9b,0x43,0x53,0xe3,0x22,0x42,0xc3, 14810 0x86,0x9b,0x43,0x2a,0xf4,0x2e,0x42,0x57,0xa2,0x9b,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43, 14811 0x08,0x01,0xa5,0x2a,0x42,0xa4,0x2d,0x9d,0x43,0x96,0x9c,0x24,0x42,0x06,0x40,0x9d,0x43,0x8a,0xb7,0x1d, 14812 0x42,0x9a,0x5b,0x9d,0x43,0x08,0x6b,0x16,0x13,0x42,0xcd,0x64,0x9d,0x43,0x42,0xc7,0x0e,0x42,0x9a,0x5b, 14813 0x9d,0x43,0x23,0x26,0x04,0x42,0xcd,0x64,0x9d,0x43,0x08,0xe6,0x91,0xeb,0x41,0x38,0x49,0x9d,0x43,0x73, 14814 0x7b,0xdb,0x41,0xf5,0x83,0x99,0x43,0x7f,0x60,0xe2,0x41,0x0b,0x0b,0x98,0x43,0x08,0x7f,0x60,0xe2,0x41, 14815 0xec,0x99,0x95,0x43,0xe3,0x5a,0xde,0x41,0xbe,0x7f,0x96,0x43,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43, 14816 0x07,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x09,0x06,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x08, 14817 0xd4,0x7e,0x29,0x42,0xab,0x6b,0xaf,0x43,0x4e,0x0c,0x26,0x42,0x44,0x6a,0xae,0x43,0x38,0x79,0x25,0x42, 14818 0xd4,0x96,0xad,0x43,0x08,0x25,0xbd,0x21,0x42,0xe2,0x4b,0xac,0x43,0x49,0x35,0x29,0x42,0x9a,0x97,0xa7, 14819 0x43,0x2a,0xf4,0x2e,0x42,0x61,0xa4,0xa3,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x09,0x06, 14820 0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x08,0x86,0x20,0x80,0x43,0x57,0x41,0xe6,0x43,0x7d,0x4e,0x80, 14821 0x43,0x25,0x38,0xe6,0x43,0xa5,0x85,0x80,0x43,0xf3,0x2e,0xe6,0x43,0x08,0x35,0xca,0x83,0x43,0xd4,0xc9, 14822 0xe5,0x43,0x9c,0xd7,0x86,0x43,0x44,0x91,0xe4,0x43,0xd5,0xca,0x8a,0x43,0x91,0x1c,0xe6,0x43,0x08,0x53, 14823 0x5f,0x8c,0x43,0xf8,0x1d,0xe7,0x43,0x2f,0x17,0x8d,0x43,0x4e,0x7b,0xe8,0x43,0x92,0x29,0x8d,0x43,0x2f, 14824 0x22,0xea,0x43,0x07,0x92,0x29,0x8d,0x43,0x44,0xb5,0xea,0x43,0x08,0xfe,0x0d,0x8d,0x43,0x2a,0x4b,0xed, 14825 0x43,0xe3,0x8b,0x8b,0x43,0x55,0x7d,0xf0,0x43,0xec,0x51,0x89,0x43,0x72,0x0b,0xf4,0x43,0x08,0xcd,0xd4, 14826 0x84,0x43,0x9d,0x55,0xfb,0x43,0xc9,0xe5,0x83,0x43,0x74,0x1e,0xfb,0x43,0x73,0x94,0x84,0x43,0x5a,0x90, 14827 0xf7,0x43,0x08,0xe8,0x62,0x88,0x43,0xfd,0x30,0xee,0x43,0x39,0xc5,0x86,0x43,0xdd,0xbf,0xeb,0x43,0x35, 14828 0xbe,0x81,0x43,0x40,0xde,0xed,0x43,0x08,0x4f,0x34,0x81,0x43,0x36,0x0c,0xee,0x43,0x08,0x98,0x80,0x43, 14829 0xfd,0x30,0xee,0x43,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x40,0xec, 14830 0x43,0x08,0x35,0xbe,0x81,0x43,0x06,0xf7,0xeb,0x43,0x15,0x65,0x83,0x43,0x49,0xa4,0xeb,0x43,0x1e,0x43, 14831 0x85,0x43,0xbe,0x5a,0xeb,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0x18,0xea,0x43,0x42,0x97,0x86,0x43,0x5f, 14832 0x67,0xf4,0x43,0xa9,0x98,0x87,0x43,0xd4,0x1d,0xf4,0x43,0x08,0x5c,0x25,0x8a,0x43,0xcf,0x16,0xef,0x43, 14833 0x46,0xaa,0x8d,0x43,0x5a,0x3c,0xe9,0x43,0x19,0x6c,0x88,0x43,0x53,0x5e,0xe7,0x43,0x08,0xc4,0x02,0x85, 14834 0x43,0x96,0x0b,0xe7,0x43,0x85,0x2c,0x82,0x43,0x83,0x67,0xe7,0x43,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7, 14835 0x43,0x07,0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x09,0x06,0xfd,0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43, 14836 0x08,0xfa,0x6c,0x78,0x43,0xd1,0xc2,0xe0,0x43,0x25,0x5c,0x6c,0x43,0x25,0x44,0xe8,0x43,0x1d,0xe5,0x7f, 14837 0x43,0x87,0x4a,0xe6,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,0x43,0x08,0xa6,0x27,0x7b,0x43,0x91, 14838 0x28,0xe8,0x43,0xbc,0xa2,0x77,0x43,0xb0,0x8d,0xe8,0x43,0xc6,0x68,0x75,0x43,0x57,0x4d,0xe8,0x43,0x08, 14839 0xe0,0xd2,0x72,0x43,0xab,0x9e,0xe7,0x43,0x50,0x9a,0x71,0x43,0x2a,0x27,0xe7,0x43,0xea,0x98,0x70,0x43, 14840 0x57,0x35,0xe4,0x43,0x08,0x94,0x3b,0x6f,0x43,0x14,0x7c,0xe2,0x43,0xff,0x13,0x6d,0x43,0x06,0xbb,0xe1, 14841 0x43,0xcf,0xfe,0x6a,0x43,0x06,0xbb,0xe1,0x43,0x08,0x44,0x9d,0x66,0x43,0x77,0x8e,0xe2,0x43,0x3b,0xef, 14842 0x6c,0x43,0x91,0x10,0xe4,0x43,0xfd,0x24,0x6c,0x43,0xb0,0x81,0xe6,0x43,0x08,0x96,0x23,0x6b,0x43,0xee, 14843 0x57,0xe9,0x43,0xca,0x0f,0x6a,0x43,0x5f,0x37,0xec,0x43,0x55,0x71,0x6e,0x43,0x9f,0x01,0xed,0x43,0x08, 14844 0xdb,0xfb,0x75,0x43,0x3b,0xef,0xec,0x43,0x09,0x3a,0x7b,0x43,0xb0,0xa5,0xec,0x43,0x1d,0xe5,0x7f,0x43, 14845 0x91,0x40,0xec,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x08,0xa9,0x16,0x7c,0x43,0xb0,0xb1, 14846 0xee,0x43,0x47,0xec,0x77,0x43,0xd9,0xe8,0xee,0x43,0x1e,0x9d,0x73,0x43,0xcf,0x16,0xef,0x43,0x08,0x0e, 14847 0xc9,0x6b,0x43,0xee,0x7b,0xef,0x43,0x7e,0x90,0x6a,0x43,0xfd,0x30,0xee,0x43,0x01,0xfc,0x68,0x43,0x4e, 14848 0x93,0xec,0x43,0x08,0x31,0xf9,0x66,0x43,0x4e,0x87,0xea,0x43,0x31,0x11,0x6b,0x43,0xd4,0xd5,0xe7,0x43, 14849 0xd9,0xc4,0x68,0x43,0xd4,0xc9,0xe5,0x43,0x08,0xe5,0x79,0x67,0x43,0x77,0x9a,0xe4,0x43,0x44,0x9d,0x66, 14850 0x43,0xab,0x86,0xe3,0x43,0x7e,0x78,0x66,0x43,0x0b,0xaa,0xe2,0x43,0x07,0x7e,0x78,0x66,0x43,0x57,0x29, 14851 0xe2,0x43,0x08,0xa7,0xaf,0x66,0x43,0xbe,0x1e,0xe1,0x43,0x87,0x56,0x68,0x43,0x77,0x82,0xe0,0x43,0xfd, 14852 0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,0x09,0x06,0xc4,0x41,0xbf,0x43,0x85,0xc0,0x72,0x42,0x08,0x73,0xdf, 14853 0xc0,0x43,0xf4,0x76,0x72,0x42,0x97,0x33,0xc2,0x43,0x85,0xc0,0x72,0x42,0xb2,0xb5,0xc3,0x43,0x64,0x56, 14854 0x75,0x42,0x08,0x03,0x24,0xc4,0x43,0x5e,0x7f,0x78,0x42,0xfa,0x51,0xc4,0x43,0x01,0x85,0x7c,0x42,0x5c, 14855 0x64,0xc4,0x43,0xa0,0xb3,0x80,0x42,0x07,0x5c,0x64,0xc4,0x43,0x10,0x93,0x83,0x42,0x08,0xc8,0x48,0xc4, 14856 0x43,0x1c,0x78,0x8a,0x42,0x27,0x6c,0xc3,0x43,0xaf,0xcf,0x94,0x42,0x23,0x7d,0xc2,0x43,0x99,0x9c,0xa4, 14857 0x42,0x08,0x3d,0xe7,0xbf,0x43,0xfb,0xfd,0xb5,0x42,0xb3,0x9d,0xbf,0x43,0x88,0x17,0xae,0x42,0xc4,0x41, 14858 0xbf,0x43,0x69,0x76,0xa3,0x42,0x07,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x08,0x4f,0x8b,0xbf,0x43, 14859 0xed,0x81,0x91,0x42,0xe4,0xa6,0xbf,0x43,0x5d,0x61,0x94,0x42,0xfa,0x39,0xc0,0x43,0x3b,0x49,0x9d,0x42, 14860 0x08,0x2b,0x43,0xc0,0x43,0x28,0xed,0xa9,0x42,0x61,0x3b,0xc1,0x43,0x00,0x9e,0xa5,0x42,0xe4,0xb2,0xc1, 14861 0x43,0x5d,0x91,0x9c,0x42,0x08,0x78,0xce,0xc1,0x43,0xfd,0x36,0x90,0x42,0x22,0x89,0xc4,0x43,0x81,0x72, 14862 0x86,0x42,0xae,0xc6,0xc2,0x43,0xa0,0xb3,0x80,0x42,0x08,0x54,0x86,0xc2,0x43,0x58,0xd1,0x7e,0x42,0x30, 14863 0x32,0xc1,0x43,0xce,0x5e,0x7b,0x42,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x07,0xc4,0x41,0xbf,0x43, 14864 0x85,0xc0,0x72,0x42,0x09,0x06,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x08,0x35,0xfd,0xbb,0x43,0xa4, 14865 0xa1,0x5c,0x42,0x5e,0x34,0xbc,0x43,0x9d,0x2a,0x70,0x42,0x5e,0x40,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08, 14866 0x4c,0x9c,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,0xef,0xbe,0x43,0x0e,0x0a,0x73,0x42,0xc4,0x41,0xbf,0x43, 14867 0x85,0xc0,0x72,0x42,0x07,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x08,0xcd,0x13,0xbf,0x43,0xe8,0xf1, 14868 0x7b,0x42,0xd6,0xe5,0xbe,0x43,0x71,0x3b,0x7c,0x42,0xdf,0xb7,0xbe,0x43,0x71,0x3b,0x7c,0x42,0x08,0x08, 14869 0xe3,0xbc,0x43,0xa4,0x61,0x7d,0x42,0x28,0x3c,0xbb,0x43,0x91,0x45,0x69,0x42,0x28,0x3c,0xbb,0x43,0x58, 14870 0x71,0x6e,0x42,0x08,0xce,0xfb,0xba,0x43,0xd5,0x35,0x78,0x42,0x59,0x45,0xbb,0x43,0x58,0x23,0x82,0x42, 14871 0xa1,0xe1,0xbb,0x43,0xd7,0xbe,0x88,0x42,0x08,0xc9,0x18,0xbc,0x43,0xaf,0x9f,0x8c,0x42,0x1e,0x76,0xbd, 14872 0x43,0x51,0x7c,0x8d,0x42,0xd6,0xe5,0xbe,0x43,0xf4,0x58,0x8e,0x42,0x08,0x9c,0x0a,0xbf,0x43,0x45,0xc7, 14873 0x8e,0x42,0x30,0x26,0xbf,0x43,0x96,0x35,0x8f,0x42,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x07,0xc4, 14874 0x41,0xbf,0x43,0x69,0x76,0xa3,0x42,0x08,0x08,0xef,0xbe,0x43,0xb1,0xd6,0x99,0x42,0xe8,0x89,0xbe,0x43, 14875 0xde,0xc5,0x8d,0x42,0xc0,0x46,0xbc,0x43,0xc2,0x5b,0x90,0x42,0x08,0x9c,0xf2,0xba,0x43,0x86,0x80,0x90, 14876 0x42,0xf2,0x43,0xba,0x43,0xe8,0x73,0x87,0x42,0x8f,0x31,0xba,0x43,0xb6,0xf4,0x7d,0x42,0x07,0x8f,0x31, 14877 0xba,0x43,0x21,0xc6,0x76,0x42,0x08,0xc0,0x3a,0xba,0x43,0x5f,0x48,0x6b,0x42,0xae,0x96,0xba,0x43,0xe3, 14878 0x83,0x61,0x42,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x09,0x06,0xea,0x74,0xea,0x43,0x61,0x44,0x93, 14879 0x43,0x08,0x24,0x5c,0xec,0x43,0x31,0x3b,0x93,0x43,0xfb,0x30,0xee,0x43,0x93,0x4d,0x93,0x43,0x0d,0xe1, 14880 0xef,0x43,0x80,0xa9,0x93,0x43,0x08,0x8f,0x58,0xf0,0x43,0xd1,0x17,0x94,0x43,0xb7,0x8f,0xf0,0x43,0x10, 14881 0xe2,0x94,0x43,0xea,0x98,0xf0,0x43,0xa9,0xec,0x95,0x43,0x07,0xea,0x98,0xf0,0x43,0x38,0x25,0x97,0x43, 14882 0x08,0x23,0x74,0xf0,0x43,0x9f,0x32,0x9a,0x43,0x5a,0x60,0xef,0x43,0x53,0xcb,0x9e,0x43,0x2d,0x3a,0xee, 14883 0x43,0xfd,0x91,0xa3,0x43,0x08,0xa2,0xf0,0xed,0x43,0xdd,0x38,0xa5,0x43,0x17,0xa7,0xed,0x43,0xbe,0xdf, 14884 0xa6,0x43,0x5a,0x54,0xed,0x43,0x9f,0x86,0xa8,0x43,0x08,0xfc,0x24,0xec,0x43,0xca,0xc4,0xad,0x43,0x48, 14885 0xa4,0xeb,0x43,0x40,0x6f,0xab,0x43,0x28,0x3f,0xeb,0x43,0x1c,0x0f,0xa8,0x43,0x08,0x1f,0x6d,0xeb,0x43, 14886 0x72,0x48,0xa3,0x43,0x67,0x09,0xec,0x43,0xd1,0x53,0x9e,0x43,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,0x43, 14887 0x07,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x08,0x7e,0x90,0xea,0x43,0x8a,0x9f,0x99,0x43,0x12,0xac, 14888 0xea,0x43,0xbc,0xa8,0x99,0x43,0xa7,0xc7,0xea,0x43,0xbc,0xa8,0x99,0x43,0x08,0x51,0x76,0xeb,0x43,0x9f, 14889 0x32,0x9a,0x43,0x5e,0x37,0xec,0x43,0x49,0xed,0x9c,0x43,0xb0,0xa5,0xec,0x43,0x2a,0xa0,0xa0,0x43,0x08, 14890 0x09,0xe6,0xec,0x43,0xd1,0x77,0xa4,0x43,0x28,0x4b,0xed,0x43,0x61,0xa4,0xa3,0x43,0xab,0xc2,0xed,0x43, 14891 0x8e,0xb2,0xa0,0x43,0x08,0x70,0xe7,0xed,0x43,0xde,0x08,0x9d,0x43,0x87,0x86,0xf0,0x43,0x2f,0x53,0x97, 14892 0x43,0x87,0x7a,0xee,0x43,0xec,0x99,0x95,0x43,0x08,0xca,0x27,0xee,0x43,0xff,0x3d,0x95,0x43,0x74,0xca, 14893 0xec,0x43,0x55,0x8f,0x94,0x43,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x07,0xea,0x74,0xea,0x43,0x61, 14894 0x44,0x93,0x43,0x09,0x06,0x05,0xd3,0xe5,0x43,0x19,0x9c,0x90,0x43,0x08,0x09,0xc2,0xe6,0x43,0xd1,0xff, 14895 0x8f,0x43,0x4d,0x6f,0xe6,0x43,0x74,0xe8,0x92,0x43,0x3b,0xd7,0xe8,0x43,0xc3,0x56,0x93,0x43,0x08,0x1f, 14896 0x61,0xe9,0x43,0x93,0x4d,0x93,0x43,0x05,0xeb,0xe9,0x43,0x93,0x4d,0x93,0x43,0xea,0x74,0xea,0x43,0x61, 14897 0x44,0x93,0x43,0x07,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x08,0x24,0x50,0xea,0x43,0xe7,0xaa,0x94, 14898 0x43,0x2d,0x22,0xea,0x43,0xe7,0xaa,0x94,0x43,0x36,0xf4,0xe9,0x43,0xe7,0xaa,0x94,0x43,0x08,0xa2,0xcc, 14899 0xe7,0x43,0xe0,0xd8,0x94,0x43,0xd4,0xc9,0xe5,0x43,0x19,0xa8,0x92,0x43,0xd4,0xc9,0xe5,0x43,0x27,0x69, 14900 0x93,0x43,0x08,0x17,0x77,0xe5,0x43,0xe0,0xd8,0x94,0x43,0x67,0xe5,0xe5,0x43,0x47,0xda,0x95,0x43,0x43, 14901 0x9d,0xe6,0x43,0xe2,0xd3,0x97,0x43,0x08,0x9d,0xdd,0xe6,0x43,0xad,0xe7,0x98,0x43,0x09,0xce,0xe8,0x43, 14902 0xff,0x55,0x99,0x43,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x07,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b, 14903 0x43,0x08,0x71,0xcf,0xe9,0x43,0x53,0xb3,0x9a,0x43,0xa7,0xbb,0xe8,0x43,0xdb,0x0d,0x9a,0x43,0xc6,0x14, 14904 0xe7,0x43,0xdb,0x0d,0x9a,0x43,0x08,0x48,0x80,0xe5,0x43,0xdb,0x0d,0x9a,0x43,0x0a,0xb6,0xe4,0x43,0xc3, 14905 0x6e,0x97,0x43,0x76,0x9a,0xe4,0x43,0x74,0xf4,0x94,0x43,0x07,0x76,0x9a,0xe4,0x43,0x79,0xd7,0x93,0x43, 14906 0x08,0xd8,0xac,0xe4,0x43,0x66,0x27,0x92,0x43,0x29,0x1b,0xe5,0x43,0xe0,0xc0,0x90,0x43,0x05,0xd3,0xe5, 14907 0x43,0x19,0x9c,0x90,0x43,0x09,0x06,0x1b,0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x08,0x71,0x0b,0xf4,0x42, 14908 0x00,0x0e,0x8d,0x42,0x8c,0x0f,0x01,0x43,0x3e,0xc0,0x89,0x42,0xf3,0x28,0x06,0x43,0x48,0x9e,0x8b,0x42, 14909 0x08,0x15,0x89,0x09,0x43,0x00,0x0e,0x8d,0x42,0xe0,0x9c,0x0a,0x43,0xc1,0x8b,0x98,0x42,0xa6,0xc1,0x0a, 14910 0x43,0x02,0xa5,0xaa,0x42,0x07,0xa6,0xc1,0x0a,0x43,0xf9,0xf6,0xb0,0x42,0x08,0xa6,0xc1,0x0a,0x43,0x47, 14911 0x8e,0xb4,0x42,0x42,0xaf,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0xe0,0x9c,0x0a,0x43,0xba,0x74,0xbc,0x42,0x08, 14912 0xa1,0xd2,0x09,0x43,0x40,0x47,0xd0,0x42,0x0d,0xab,0x07,0x43,0x91,0xb5,0xd0,0x42,0x3b,0xb9,0x04,0x43, 14913 0xec,0x71,0xba,0x42,0x08,0xe5,0x5b,0x03,0x43,0xe3,0x33,0xa8,0x42,0x63,0xd8,0x00,0x43,0xce,0x70,0x9f, 14914 0x42,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x07,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,0x42,0x08,0xed, 14915 0x6f,0xed,0x42,0x73,0x24,0x9d,0x42,0xd8,0x0c,0xf5,0x42,0x99,0x6c,0x9c,0x42,0x27,0xab,0xfd,0x42,0xea, 14916 0xda,0x9c,0x42,0x08,0x36,0xca,0x03,0x43,0x2b,0x94,0x9e,0x42,0x68,0xc7,0x01,0x43,0x8f,0xbe,0xa2,0x42, 14917 0xfa,0x06,0x08,0x43,0x73,0xb4,0xb5,0x42,0x08,0x8e,0x2e,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0x9d,0xe3,0x08, 14918 0x43,0xd7,0x1e,0x99,0x42,0x28,0x15,0x05,0x43,0x32,0x3b,0x93,0x42,0x08,0x63,0xf0,0x04,0x43,0x70,0xed, 14919 0x8f,0x42,0x71,0x0b,0xf4,0x42,0x32,0x3b,0x93,0x42,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x07,0x1b, 14920 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x09,0x06,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,0x08,0x8e,0x55, 14921 0xc0,0x42,0xb8,0x4d,0x86,0x42,0x60,0xbf,0xd7,0x42,0x3e,0xf0,0x91,0x42,0x63,0xf6,0xe4,0x42,0x70,0xed, 14922 0x8f,0x42,0x08,0x7a,0x89,0xe5,0x42,0xac,0xc8,0x8f,0x42,0xcc,0xf7,0xe5,0x42,0xac,0xc8,0x8f,0x42,0x1b, 14923 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x07,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x08,0x63,0xf6,0xe4, 14924 0x42,0x3b,0x19,0x95,0x42,0xe6,0x61,0xe3,0x42,0x00,0x3e,0x95,0x42,0xf4,0x16,0xe2,0x42,0xc4,0x62,0x95, 14925 0x42,0x08,0x6e,0x74,0xd6,0x42,0x15,0xd1,0x95,0x42,0x97,0x63,0xca,0x42,0xaf,0xcf,0x94,0x42,0xfb,0x2d, 14926 0xbe,0x42,0x86,0x80,0x90,0x42,0x08,0x97,0x03,0xba,0x42,0xce,0x10,0x8f,0x42,0x5e,0x28,0xba,0x42,0x3e, 14927 0xf0,0x91,0x42,0xf2,0x4f,0xbc,0x42,0x45,0xf7,0x96,0x42,0x08,0x27,0x54,0xbf,0x42,0x73,0x24,0x9d,0x42, 14928 0xa5,0xe8,0xc0,0x42,0x86,0xe0,0xa0,0x42,0xe4,0xca,0xc5,0x42,0xed,0x11,0xaa,0x42,0x08,0x54,0xaa,0xc8, 14929 0x42,0x86,0x40,0xb1,0x42,0x59,0x81,0xc5,0x42,0xa1,0x11,0xc4,0x42,0x3e,0xe7,0xbf,0x42,0xfb,0x8d,0xce, 14930 0x42,0x08,0xb4,0x6d,0xb7,0x42,0x30,0xc2,0xd9,0x42,0x46,0xf5,0xc9,0x42,0xdf,0x53,0xd9,0x42,0x38,0x40, 14931 0xcb,0x42,0x62,0x8f,0xcf,0x42,0x08,0x7d,0xf9,0xcc,0x42,0xec,0xa1,0xc2,0x42,0x07,0x43,0xcd,0x42,0x6c, 14932 0xdd,0xb8,0x42,0x2b,0x8b,0xcc,0x42,0x92,0xf5,0xaf,0x42,0x08,0xf9,0x8d,0xce,0x42,0x41,0x57,0xa7,0x42, 14933 0x5b,0xb8,0xd2,0x42,0xae,0x2f,0xa5,0x42,0x18,0x2f,0xd9,0x42,0x13,0x2a,0xa1,0x42,0x08,0x41,0x7e,0xdd, 14934 0x42,0xe3,0x03,0xa0,0x42,0x2e,0xf2,0xe1,0x42,0x7c,0x02,0x9f,0x42,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e, 14935 0x42,0x07,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x08,0x4d,0x63,0xe4,0x42,0x00,0x9e,0xa5,0x42,0xf4, 14936 0x16,0xe2,0x42,0x15,0x31,0xa6,0x42,0x99,0xca,0xdf,0x42,0x2b,0xc4,0xa6,0x42,0x08,0xc0,0x82,0xc6,0x42, 14937 0xc4,0xc2,0xa5,0x42,0x57,0xe1,0xd5,0x42,0x91,0xb5,0xd0,0x42,0x54,0xda,0xd0,0x42,0x97,0x93,0xd2,0x42, 14938 0x08,0x9c,0x3a,0xc7,0x42,0x17,0x58,0xdc,0x42,0x9c,0x0a,0xbf,0x42,0x6e,0xa4,0xde,0x42,0x90,0x25,0xb8, 14939 0x42,0xdf,0x53,0xd9,0x42,0x08,0x59,0x21,0xb5,0x42,0xf2,0xdf,0xd4,0x42,0x51,0x43,0xb3,0x42,0x91,0xb5, 14940 0xd0,0x42,0xc5,0x29,0xbb,0x42,0x0e,0x1a,0xca,0x42,0x08,0x65,0x36,0xc4,0x42,0xd0,0x07,0xbd,0x42,0x3e, 14941 0xe7,0xbf,0x42,0x37,0x09,0xbe,0x42,0x0c,0xea,0xc1,0x42,0xcd,0xd0,0xaf,0x42,0x08,0x2b,0x5b,0xc4,0x42, 14942 0x18,0x08,0xa3,0x42,0x67,0xa6,0xab,0x42,0x99,0x3c,0x94,0x42,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42, 14943 0x09,]; 14944 14945 private struct ThePath { 14946 public: 14947 enum Command { 14948 Bounds, // always first, has 4 args (x0, y0, x1, y1) 14949 StrokeMode, 14950 FillMode, 14951 StrokeFillMode, 14952 NormalStroke, 14953 ThinStroke, 14954 MoveTo, 14955 LineTo, 14956 CubicTo, // cubic bezier 14957 EndPath, 14958 } 14959 14960 public: 14961 const(ubyte)[] path; 14962 uint ppos; 14963 14964 public: 14965 this (const(void)[] apath) pure nothrow @trusted @nogc { 14966 path = cast(const(ubyte)[])apath; 14967 } 14968 14969 @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (ppos >= path.length); } 14970 14971 Command getCommand () nothrow @trusted @nogc { 14972 pragma(inline, true); 14973 if (ppos >= cast(uint)path.length) assert(0, "invalid path"); 14974 return cast(Command)(path.ptr[ppos++]); 14975 } 14976 14977 // number of (x,y) pairs for this command 14978 static int argCount (in Command cmd) nothrow @safe @nogc { 14979 version(aliced) pragma(inline, true); 14980 if (cmd == Command.Bounds) return 2; 14981 else if (cmd == Command.MoveTo || cmd == Command.LineTo) return 1; 14982 else if (cmd == Command.CubicTo) return 3; 14983 else return 0; 14984 } 14985 14986 void skipArgs (int argc) nothrow @trusted @nogc { 14987 pragma(inline, true); 14988 ppos += cast(uint)(float.sizeof*2*argc); 14989 } 14990 14991 float getFloat () nothrow @trusted @nogc { 14992 pragma(inline, true); 14993 if (ppos >= cast(uint)path.length || cast(uint)path.length-ppos < float.sizeof) assert(0, "invalid path"); 14994 version(LittleEndian) { 14995 float res = *cast(const(float)*)(&path.ptr[ppos]); 14996 ppos += cast(uint)float.sizeof; 14997 return res; 14998 } else { 14999 static assert(float.sizeof == 4); 15000 uint xp = path.ptr[ppos]|(path.ptr[ppos+1]<<8)|(path.ptr[ppos+2]<<16)|(path.ptr[ppos+3]<<24); 15001 ppos += cast(uint)float.sizeof; 15002 return *cast(const(float)*)(&xp); 15003 } 15004 } 15005 } 15006 15007 // this will add baphomet's background path to the current NanoVega path, so you can fill it. 15008 public void addBaphometBack (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc { 15009 if (nvg is null) return; 15010 15011 auto path = ThePath(baphometPath); 15012 15013 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15014 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15015 15016 bool inPath = false; 15017 while (!path.empty) { 15018 auto cmd = path.getCommand(); 15019 switch (cmd) { 15020 case ThePath.Command.MoveTo: 15021 inPath = true; 15022 immutable float ex = getScaledX(); 15023 immutable float ey = getScaledY(); 15024 nvg.moveTo(ex, ey); 15025 break; 15026 case ThePath.Command.LineTo: 15027 inPath = true; 15028 immutable float ex = getScaledX(); 15029 immutable float ey = getScaledY(); 15030 nvg.lineTo(ex, ey); 15031 break; 15032 case ThePath.Command.CubicTo: // cubic bezier 15033 inPath = true; 15034 immutable float x1 = getScaledX(); 15035 immutable float y1 = getScaledY(); 15036 immutable float x2 = getScaledX(); 15037 immutable float y2 = getScaledY(); 15038 immutable float ex = getScaledX(); 15039 immutable float ey = getScaledY(); 15040 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15041 break; 15042 case ThePath.Command.EndPath: 15043 if (inPath) return; 15044 break; 15045 default: 15046 path.skipArgs(path.argCount(cmd)); 15047 break; 15048 } 15049 } 15050 } 15051 15052 // this will add baphomet's pupil paths to the current NanoVega path, so you can fill it. 15053 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 { 15054 // pupils starts with "fill-and-stroke" mode 15055 if (nvg is null) return; 15056 15057 auto path = ThePath(baphometPath); 15058 15059 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15060 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15061 15062 bool inPath = false; 15063 bool pupLeft = true; 15064 while (!path.empty) { 15065 auto cmd = path.getCommand(); 15066 switch (cmd) { 15067 case ThePath.Command.StrokeFillMode: inPath = true; break; 15068 case ThePath.Command.MoveTo: 15069 if (!inPath) goto default; 15070 static if (!left) { if (pupLeft) goto default; } 15071 static if (!right) { if (!pupLeft) goto default; } 15072 immutable float ex = getScaledX(); 15073 immutable float ey = getScaledY(); 15074 nvg.moveTo(ex, ey); 15075 break; 15076 case ThePath.Command.LineTo: 15077 if (!inPath) goto default; 15078 static if (!left) { if (pupLeft) goto default; } 15079 static if (!right) { if (!pupLeft) goto default; } 15080 immutable float ex = getScaledX(); 15081 immutable float ey = getScaledY(); 15082 nvg.lineTo(ex, ey); 15083 break; 15084 case ThePath.Command.CubicTo: // cubic bezier 15085 if (!inPath) goto default; 15086 static if (!left) { if (pupLeft) goto default; } 15087 static if (!right) { if (!pupLeft) goto default; } 15088 immutable float x1 = getScaledX(); 15089 immutable float y1 = getScaledY(); 15090 immutable float x2 = getScaledX(); 15091 immutable float y2 = getScaledY(); 15092 immutable float ex = getScaledX(); 15093 immutable float ey = getScaledY(); 15094 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15095 break; 15096 case ThePath.Command.EndPath: 15097 if (inPath) { 15098 if (pupLeft) pupLeft = false; else return; 15099 } 15100 break; 15101 default: 15102 path.skipArgs(path.argCount(cmd)); 15103 break; 15104 } 15105 } 15106 } 15107 15108 // mode: 'f' to allow fills; 's' to allow strokes; 'w' to allow stroke widths; 'c' to replace fills with strokes 15109 public void renderBaphomet(string mode="fs") (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc { 15110 template hasChar(char ch, string s) { 15111 static if (s.length == 0) enum hasChar = false; 15112 else static if (s[0] == ch) enum hasChar = true; 15113 else enum hasChar = hasChar!(ch, s[1..$]); 15114 } 15115 enum AllowStroke = hasChar!('s', mode); 15116 enum AllowFill = hasChar!('f', mode); 15117 enum AllowWidth = hasChar!('w', mode); 15118 enum Contour = hasChar!('c', mode); 15119 //static assert(AllowWidth || AllowFill); 15120 15121 if (nvg is null) return; 15122 15123 auto path = ThePath(baphometPath); 15124 15125 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15126 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15127 15128 int mode = 0; 15129 int sw = ThePath.Command.NormalStroke; 15130 nvg.beginPath(); 15131 while (!path.empty) { 15132 auto cmd = path.getCommand(); 15133 switch (cmd) { 15134 case ThePath.Command.StrokeMode: mode = ThePath.Command.StrokeMode; break; 15135 case ThePath.Command.FillMode: mode = ThePath.Command.FillMode; break; 15136 case ThePath.Command.StrokeFillMode: mode = ThePath.Command.StrokeFillMode; break; 15137 case ThePath.Command.NormalStroke: sw = ThePath.Command.NormalStroke; break; 15138 case ThePath.Command.ThinStroke: sw = ThePath.Command.ThinStroke; break; 15139 case ThePath.Command.MoveTo: 15140 immutable float ex = getScaledX(); 15141 immutable float ey = getScaledY(); 15142 nvg.moveTo(ex, ey); 15143 break; 15144 case ThePath.Command.LineTo: 15145 immutable float ex = getScaledX(); 15146 immutable float ey = getScaledY(); 15147 nvg.lineTo(ex, ey); 15148 break; 15149 case ThePath.Command.CubicTo: // cubic bezier 15150 immutable float x1 = getScaledX(); 15151 immutable float y1 = getScaledY(); 15152 immutable float x2 = getScaledX(); 15153 immutable float y2 = getScaledY(); 15154 immutable float ex = getScaledX(); 15155 immutable float ey = getScaledY(); 15156 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15157 break; 15158 case ThePath.Command.EndPath: 15159 if (mode == ThePath.Command.FillMode || mode == ThePath.Command.StrokeFillMode) { 15160 static if (AllowFill || Contour) { 15161 static if (Contour) { 15162 if (mode == ThePath.Command.FillMode) { nvg.strokeWidth = 1; nvg.stroke(); } 15163 } else { 15164 nvg.fill(); 15165 } 15166 } 15167 } 15168 if (mode == ThePath.Command.StrokeMode || mode == ThePath.Command.StrokeFillMode) { 15169 static if (AllowStroke || Contour) { 15170 static if (AllowWidth) { 15171 if (sw == ThePath.Command.NormalStroke) nvg.strokeWidth = 1; 15172 else if (sw == ThePath.Command.ThinStroke) nvg.strokeWidth = 0.5; 15173 else assert(0, "wtf?!"); 15174 } 15175 nvg.stroke(); 15176 } 15177 } 15178 nvg.newPath(); 15179 break; 15180 default: 15181 path.skipArgs(path.argCount(cmd)); 15182 break; 15183 } 15184 } 15185 nvg.newPath(); 15186 }