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 this.setAsCurrentOpenGlContext(); 12226 nvg = nvgCreateContext(); 12227 if(nvg is null) throw new Exception("cannot initialize NanoVega"); 12228 }; 12229 12230 this.redrawOpenGlScene = delegate() { 12231 if(redrawNVGScene is null) 12232 return; 12233 glViewport(0, 0, this.width, this.height); 12234 if(clearOnEachFrame) { 12235 glClearColor(0, 0, 0, 0); 12236 glClear(glNVGClearFlags); 12237 } 12238 12239 nvg.beginFrame(this.width, this.height); 12240 scope(exit) nvg.endFrame(); 12241 12242 redrawNVGScene(nvg); 12243 }; 12244 12245 this.setEventHandlers( 12246 &redrawOpenGlSceneNow, 12247 (KeyEvent ke) { 12248 if(ke.key == Key.Escape || ke.key == Key.Q) 12249 this.close(); 12250 } 12251 ); 12252 } 12253 12254 /++ 12255 12256 +/ 12257 bool clearOnEachFrame = true; 12258 12259 /++ 12260 12261 +/ 12262 void delegate(NVGContext nvg) redrawNVGScene; 12263 12264 /++ 12265 12266 +/ 12267 void redrawNVGSceneNow() { 12268 redrawOpenGlSceneNow(); 12269 } 12270 } 12271 12272 } else { 12273 import iv.glbinds; 12274 } 12275 12276 private: 12277 // sdpy is missing that yet 12278 static if (!is(typeof(GL_STENCIL_BUFFER_BIT))) enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 12279 12280 12281 12282 version(bindbc){ 12283 private extern(System) nothrow @nogc: 12284 // this definition doesn't exist in regular OpenGL (?) 12285 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 12286 private void nanovgInitOpenGL () { 12287 // i'm not aware of calling the load multiple times having negative side effects, so i don't do an initialization check 12288 GLSupport support = loadOpenGL(); 12289 if (support == GLSupport.noLibrary) 12290 assert(0, "OpenGL initialization failed: shared library failed to load"); 12291 else if (support == GLSupport.badLibrary) 12292 assert(0, "OpenGL initialization failed: a context-independent symbol failed to load"); 12293 else if (support == GLSupport.noContext) 12294 assert(0, "OpenGL initialization failed: a context needs to be created prior to initialization"); 12295 } 12296 } else { // OpenGL API missing from simpledisplay 12297 private void nanovgInitOpenGL () @nogc nothrow { 12298 __gshared bool initialized = false; 12299 if (initialized) return; 12300 12301 try 12302 gl3.loadDynamicLibrary(); 12303 catch(Exception) 12304 assert(0, "GL 3 failed to load"); 12305 12306 initialized = true; 12307 } 12308 } 12309 12310 12311 12312 /// Context creation flags. 12313 /// Group: context_management 12314 public enum NVGContextFlag : int { 12315 /// Nothing special, i.e. empty flag. 12316 None = 0, 12317 /// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA). 12318 Antialias = 1U<<0, 12319 /** Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little 12320 * slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. */ 12321 StencilStrokes = 1U<<1, 12322 /// Flag indicating that additional debug checks are done. 12323 Debug = 1U<<2, 12324 /// Filter (antialias) fonts 12325 FontAA = 1U<<7, 12326 /// Don't filter (antialias) fonts 12327 FontNoAA = 1U<<8, 12328 /// You can use this as a substitute for default flags, for cases like this: `nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);`. 12329 Default = 1U<<31, 12330 } 12331 12332 public enum NANOVG_GL_USE_STATE_FILTER = true; 12333 12334 /// Returns flags for glClear(). 12335 /// Group: context_management 12336 public uint glNVGClearFlags () pure nothrow @safe @nogc { 12337 pragma(inline, true); 12338 return (GL_COLOR_BUFFER_BIT|/*GL_DEPTH_BUFFER_BIT|*/GL_STENCIL_BUFFER_BIT); 12339 } 12340 12341 12342 // ////////////////////////////////////////////////////////////////////////// // 12343 private: 12344 12345 version = nanovega_shared_stencil; 12346 //version = nanovega_debug_clipping; 12347 12348 enum GLNVGuniformLoc { 12349 ViewSize, 12350 Tex, 12351 Frag, 12352 TMat, 12353 TTr, 12354 ClipTex, 12355 } 12356 12357 alias GLNVGshaderType = int; 12358 enum /*GLNVGshaderType*/ { 12359 NSVG_SHADER_FILLCOLOR, 12360 NSVG_SHADER_FILLGRAD, 12361 NSVG_SHADER_FILLIMG, 12362 NSVG_SHADER_SIMPLE, // also used for clipfill 12363 NSVG_SHADER_IMG, 12364 } 12365 12366 struct GLNVGshader { 12367 GLuint prog; 12368 GLuint frag; 12369 GLuint vert; 12370 GLint[GLNVGuniformLoc.max+1] loc; 12371 } 12372 12373 struct GLNVGtexture { 12374 int id; 12375 GLuint tex; 12376 int width, height; 12377 NVGtexture type; 12378 int flags; 12379 shared int rc; // this can be 0 with tex != 0 -- postponed deletion 12380 int nextfree; 12381 } 12382 12383 struct GLNVGblend { 12384 bool simple; 12385 GLenum srcRGB; 12386 GLenum dstRGB; 12387 GLenum srcAlpha; 12388 GLenum dstAlpha; 12389 } 12390 12391 alias GLNVGcallType = int; 12392 enum /*GLNVGcallType*/ { 12393 GLNVG_NONE = 0, 12394 GLNVG_FILL, 12395 GLNVG_CONVEXFILL, 12396 GLNVG_STROKE, 12397 GLNVG_TRIANGLES, 12398 GLNVG_AFFINE, // change affine transformation matrix 12399 GLNVG_PUSHCLIP, 12400 GLNVG_POPCLIP, 12401 GLNVG_RESETCLIP, 12402 GLNVG_CLIP_DDUMP_ON, 12403 GLNVG_CLIP_DDUMP_OFF, 12404 } 12405 12406 struct GLNVGcall { 12407 int type; 12408 int evenOdd; // for fill 12409 int image; 12410 int pathOffset; 12411 int pathCount; 12412 int triangleOffset; 12413 int triangleCount; 12414 int uniformOffset; 12415 NVGMatrix affine; 12416 GLNVGblend blendFunc; 12417 NVGClipMode clipmode; 12418 } 12419 12420 struct GLNVGpath { 12421 int fillOffset; 12422 int fillCount; 12423 int strokeOffset; 12424 int strokeCount; 12425 } 12426 12427 align(1) struct GLNVGfragUniforms { 12428 align(1): 12429 enum UNIFORM_ARRAY_SIZE = 13; 12430 // note: after modifying layout or size of uniform array, 12431 // don't forget to also update the fragment shader source! 12432 align(1) union { 12433 align(1): 12434 align(1) struct { 12435 align(1): 12436 float[12] scissorMat; // matrices are actually 3 vec4s 12437 float[12] paintMat; 12438 NVGColor innerCol; 12439 NVGColor middleCol; 12440 NVGColor outerCol; 12441 float[2] scissorExt; 12442 float[2] scissorScale; 12443 float[2] extent; 12444 float radius; 12445 float feather; 12446 float strokeMult; 12447 float strokeThr; 12448 float texType; 12449 float type; 12450 float doclip; 12451 float midp; // for gradients 12452 float unused2, unused3; 12453 } 12454 float[4][UNIFORM_ARRAY_SIZE] uniformArray; 12455 } 12456 } 12457 12458 enum GLMaskState { 12459 DontMask = -1, 12460 Uninitialized = 0, 12461 Initialized = 1, 12462 JustCleared = 2, 12463 } 12464 12465 import core.sync.mutex; 12466 __gshared Mutex GLNVGTextureLocker; 12467 shared static this() { 12468 GLNVGTextureLocker = new Mutex(); 12469 } 12470 12471 struct GLNVGcontext { 12472 private import core.thread : ThreadID; 12473 12474 GLNVGshader shader; 12475 GLNVGtexture* textures; 12476 float[2] view; 12477 int freetexid; // -1: none 12478 int ntextures; 12479 int ctextures; 12480 GLuint vertBuf; 12481 int fragSize; 12482 int flags; 12483 // FBOs for masks 12484 GLuint[NVG_MAX_STATES] fbo; 12485 GLuint[2][NVG_MAX_STATES] fboTex; // FBO textures: [0] is color, [1] is stencil 12486 int fboWidth, fboHeight; 12487 GLMaskState[NVG_MAX_STATES] maskStack; 12488 int msp; // mask stack pointer; starts from `0`; points to next free item; see below for logic description 12489 int lastClipFBO; // -666: cache invalidated; -1: don't mask 12490 int lastClipUniOfs; 12491 bool doClipUnion; // specal mode 12492 GLNVGshader shaderFillFBO; 12493 GLNVGshader shaderCopyFBO; 12494 12495 bool inFrame; // will be `true` if we can perform OpenGL operations (used in texture deletion) 12496 shared bool mustCleanTextures; // will be `true` if we should delete some textures 12497 ThreadID mainTID; 12498 uint mainFBO; 12499 12500 // Per frame buffers 12501 GLNVGcall* calls; 12502 int ccalls; 12503 int ncalls; 12504 GLNVGpath* paths; 12505 int cpaths; 12506 int npaths; 12507 NVGVertex* verts; 12508 int cverts; 12509 int nverts; 12510 ubyte* uniforms; 12511 int cuniforms; 12512 int nuniforms; 12513 NVGMatrix lastAffine; 12514 12515 // cached state 12516 static if (NANOVG_GL_USE_STATE_FILTER) { 12517 GLuint boundTexture; 12518 GLuint stencilMask; 12519 GLenum stencilFunc; 12520 GLint stencilFuncRef; 12521 GLuint stencilFuncMask; 12522 GLNVGblend blendFunc; 12523 } 12524 } 12525 12526 int glnvg__maxi() (int a, int b) { pragma(inline, true); return (a > b ? a : b); } 12527 12528 void glnvg__bindTexture (GLNVGcontext* gl, GLuint tex) nothrow @trusted @nogc { 12529 static if (NANOVG_GL_USE_STATE_FILTER) { 12530 if (gl.boundTexture != tex) { 12531 gl.boundTexture = tex; 12532 glBindTexture(GL_TEXTURE_2D, tex); 12533 } 12534 } else { 12535 glBindTexture(GL_TEXTURE_2D, tex); 12536 } 12537 } 12538 12539 void glnvg__stencilMask (GLNVGcontext* gl, GLuint mask) nothrow @trusted @nogc { 12540 static if (NANOVG_GL_USE_STATE_FILTER) { 12541 if (gl.stencilMask != mask) { 12542 gl.stencilMask = mask; 12543 glStencilMask(mask); 12544 } 12545 } else { 12546 glStencilMask(mask); 12547 } 12548 } 12549 12550 void glnvg__stencilFunc (GLNVGcontext* gl, GLenum func, GLint ref_, GLuint mask) nothrow @trusted @nogc { 12551 static if (NANOVG_GL_USE_STATE_FILTER) { 12552 if (gl.stencilFunc != func || gl.stencilFuncRef != ref_ || gl.stencilFuncMask != mask) { 12553 gl.stencilFunc = func; 12554 gl.stencilFuncRef = ref_; 12555 gl.stencilFuncMask = mask; 12556 glStencilFunc(func, ref_, mask); 12557 } 12558 } else { 12559 glStencilFunc(func, ref_, mask); 12560 } 12561 } 12562 12563 // texture id is never zero 12564 // sets refcount to one 12565 GLNVGtexture* glnvg__allocTexture (GLNVGcontext* gl) nothrow @trusted @nogc { 12566 GLNVGtexture* tex = null; 12567 12568 int tid = gl.freetexid; 12569 if (tid == -1) { 12570 if (gl.ntextures >= gl.ctextures) { 12571 assert(gl.ntextures == gl.ctextures); 12572 //pragma(msg, GLNVGtexture.sizeof*32); 12573 int ctextures = (gl.ctextures == 0 ? 32 : gl.ctextures+gl.ctextures/2); // 1.5x overallocate 12574 GLNVGtexture* textures = cast(GLNVGtexture*)realloc(gl.textures, GLNVGtexture.sizeof*ctextures); 12575 if (textures is null) assert(0, "NanoVega: out of memory for textures"); 12576 memset(&textures[gl.ctextures], 0, (ctextures-gl.ctextures)*GLNVGtexture.sizeof); 12577 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated more textures (n=%d; c=%d; nc=%d)\n", gl.ntextures, gl.ctextures, ctextures); }} 12578 gl.textures = textures; 12579 gl.ctextures = ctextures; 12580 } 12581 assert(gl.ntextures+1 <= gl.ctextures); 12582 tid = gl.ntextures++; 12583 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf(" got next free texture id %d, ntextures=%d\n", tid+1, gl.ntextures); }} 12584 } else { 12585 gl.freetexid = gl.textures[tid].nextfree; 12586 } 12587 assert(tid <= gl.ntextures); 12588 12589 assert(gl.textures[tid].id == 0); 12590 tex = &gl.textures[tid]; 12591 memset(tex, 0, (*tex).sizeof); 12592 tex.id = tid+1; 12593 tex.rc = 1; 12594 tex.nextfree = -1; 12595 12596 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated texture with id %d (%d)\n", tex.id, tid+1); }} 12597 12598 return tex; 12599 } 12600 12601 GLNVGtexture* glnvg__findTexture (GLNVGcontext* gl, int id) nothrow @trusted @nogc { 12602 if (id <= 0 || id > gl.ntextures) return null; 12603 if (gl.textures[id-1].id == 0) return null; // free one 12604 assert(gl.textures[id-1].id == id); 12605 return &gl.textures[id-1]; 12606 } 12607 12608 bool glnvg__deleteTexture (GLNVGcontext* gl, ref int id) nothrow @trusted @nogc { 12609 if (id <= 0 || id > gl.ntextures) return false; 12610 auto tx = &gl.textures[id-1]; 12611 if (tx.id == 0) { id = 0; return false; } // free one 12612 assert(tx.id == id); 12613 assert(tx.tex != 0); 12614 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("decrefing texture with id %d (%d)\n", tx.id, id); }} 12615 import core.atomic : atomicOp; 12616 if (atomicOp!"-="(tx.rc, 1) == 0) { 12617 import core.thread : ThreadID; 12618 ThreadID mytid; 12619 static if (__VERSION__ < 2076) { 12620 DGNoThrowNoGC(() { 12621 import core.thread; mytid = Thread.getThis.id; 12622 })(); 12623 } else { 12624 try { import core.thread; mytid = Thread.getThis.id; } catch (Exception e) {} 12625 } 12626 if (gl.mainTID == mytid && gl.inFrame) { 12627 // can delete it right now 12628 if ((tx.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tx.tex); 12629 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** deleted texture with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }} 12630 memset(tx, 0, (*tx).sizeof); 12631 //{ import core.stdc.stdio; printf("deleting texture with id %d\n", id); } 12632 tx.nextfree = gl.freetexid; 12633 gl.freetexid = id-1; 12634 } else { 12635 // alas, we aren't doing frame business, so we should postpone deletion 12636 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** POSTPONED texture deletion with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }} 12637 version(aliced) { 12638 { 12639 GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow; 12640 tx.id = 0; // mark it as dead 12641 gl.mustCleanTextures = true; // set "need cleanup" flag 12642 } 12643 } else { 12644 try { 12645 GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow; 12646 tx.id = 0; // mark it as dead 12647 gl.mustCleanTextures = true; // set "need cleanup" flag 12648 } catch (Exception e) {} 12649 } 12650 } 12651 } 12652 id = 0; 12653 return true; 12654 } 12655 12656 void glnvg__dumpShaderError (GLuint shader, const(char)* name, const(char)* type) nothrow @trusted @nogc { 12657 import core.stdc.stdio : fprintf, stderr; 12658 GLchar[512+1] str = 0; 12659 GLsizei len = 0; 12660 glGetShaderInfoLog(shader, 512, &len, str.ptr); 12661 if (len > 512) len = 512; 12662 str[len] = '\0'; 12663 fprintf(stderr, "Shader %s/%s error:\n%s\n", name, type, str.ptr); 12664 } 12665 12666 void glnvg__dumpProgramError (GLuint prog, const(char)* name) nothrow @trusted @nogc { 12667 import core.stdc.stdio : fprintf, stderr; 12668 GLchar[512+1] str = 0; 12669 GLsizei len = 0; 12670 glGetProgramInfoLog(prog, 512, &len, str.ptr); 12671 if (len > 512) len = 512; 12672 str[len] = '\0'; 12673 fprintf(stderr, "Program %s error:\n%s\n", name, str.ptr); 12674 } 12675 12676 void glnvg__resetError(bool force=false) (GLNVGcontext* gl) nothrow @trusted @nogc { 12677 static if (!force) { 12678 if ((gl.flags&NVGContextFlag.Debug) == 0) return; 12679 } 12680 glGetError(); 12681 } 12682 12683 void glnvg__checkError(bool force=false) (GLNVGcontext* gl, const(char)* str) nothrow @trusted @nogc { 12684 GLenum err; 12685 static if (!force) { 12686 if ((gl.flags&NVGContextFlag.Debug) == 0) return; 12687 } 12688 err = glGetError(); 12689 if (err != GL_NO_ERROR) { 12690 import core.stdc.stdio : fprintf, stderr; 12691 fprintf(stderr, "Error %08x after %s\n", err, str); 12692 return; 12693 } 12694 } 12695 12696 bool glnvg__createShader (GLNVGshader* shader, const(char)* name, const(char)* header, const(char)* opts, const(char)* vshader, const(char)* fshader) nothrow @trusted @nogc { 12697 GLint status; 12698 GLuint prog, vert, frag; 12699 const(char)*[3] str; 12700 12701 memset(shader, 0, (*shader).sizeof); 12702 12703 prog = glCreateProgram(); 12704 vert = glCreateShader(GL_VERTEX_SHADER); 12705 frag = glCreateShader(GL_FRAGMENT_SHADER); 12706 str[0] = header; 12707 str[1] = (opts !is null ? opts : ""); 12708 str[2] = vshader; 12709 glShaderSource(vert, 3, cast(const(char)**)str.ptr, null); 12710 12711 glCompileShader(vert); 12712 glGetShaderiv(vert, GL_COMPILE_STATUS, &status); 12713 if (status != GL_TRUE) { 12714 glnvg__dumpShaderError(vert, name, "vert"); 12715 return false; 12716 } 12717 12718 str[0] = header; 12719 str[1] = (opts !is null ? opts : ""); 12720 str[2] = fshader; 12721 glShaderSource(frag, 3, cast(const(char)**)str.ptr, null); 12722 12723 glCompileShader(frag); 12724 glGetShaderiv(frag, GL_COMPILE_STATUS, &status); 12725 if (status != GL_TRUE) { 12726 glnvg__dumpShaderError(frag, name, "frag"); 12727 return false; 12728 } 12729 12730 glAttachShader(prog, vert); 12731 glAttachShader(prog, frag); 12732 12733 glBindAttribLocation(prog, 0, "vertex"); 12734 glBindAttribLocation(prog, 1, "tcoord"); 12735 12736 glLinkProgram(prog); 12737 glGetProgramiv(prog, GL_LINK_STATUS, &status); 12738 if (status != GL_TRUE) { 12739 glnvg__dumpProgramError(prog, name); 12740 return false; 12741 } 12742 12743 shader.prog = prog; 12744 shader.vert = vert; 12745 shader.frag = frag; 12746 12747 return true; 12748 } 12749 12750 void glnvg__deleteShader (GLNVGshader* shader) nothrow @trusted @nogc { 12751 if (shader.prog != 0) glDeleteProgram(shader.prog); 12752 if (shader.vert != 0) glDeleteShader(shader.vert); 12753 if (shader.frag != 0) glDeleteShader(shader.frag); 12754 } 12755 12756 void glnvg__getUniforms (GLNVGshader* shader) nothrow @trusted @nogc { 12757 shader.loc[GLNVGuniformLoc.ViewSize] = glGetUniformLocation(shader.prog, "viewSize"); 12758 shader.loc[GLNVGuniformLoc.Tex] = glGetUniformLocation(shader.prog, "tex"); 12759 shader.loc[GLNVGuniformLoc.Frag] = glGetUniformLocation(shader.prog, "frag"); 12760 shader.loc[GLNVGuniformLoc.TMat] = glGetUniformLocation(shader.prog, "tmat"); 12761 shader.loc[GLNVGuniformLoc.TTr] = glGetUniformLocation(shader.prog, "ttr"); 12762 shader.loc[GLNVGuniformLoc.ClipTex] = glGetUniformLocation(shader.prog, "clipTex"); 12763 } 12764 12765 void glnvg__killFBOs (GLNVGcontext* gl) nothrow @trusted @nogc { 12766 foreach (immutable fidx, ref GLuint fbo; gl.fbo[]) { 12767 if (fbo != 0) { 12768 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); 12769 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); 12770 foreach (ref GLuint tid; gl.fboTex.ptr[fidx][]) if (tid != 0) { glDeleteTextures(1, &tid); tid = 0; } 12771 glDeleteFramebuffers(1, &fbo); 12772 fbo = 0; 12773 } 12774 } 12775 gl.fboWidth = gl.fboHeight = 0; 12776 } 12777 12778 // returns `true` is new FBO was created 12779 // will not unbind buffer, if it was created 12780 bool glnvg__allocFBO (GLNVGcontext* gl, int fidx, bool doclear=true) nothrow @trusted @nogc { 12781 assert(fidx >= 0 && fidx < gl.fbo.length); 12782 assert(gl.fboWidth > 0); 12783 assert(gl.fboHeight > 0); 12784 12785 if (gl.fbo.ptr[fidx] != 0) return false; // nothing to do, this FBO is already initialized 12786 12787 glnvg__resetError(gl); 12788 12789 // allocate FBO object 12790 GLuint fbo = 0; 12791 glGenFramebuffers(1, &fbo); 12792 if (fbo == 0) assert(0, "NanoVega: cannot create FBO"); 12793 glnvg__checkError(gl, "glnvg__allocFBO: glGenFramebuffers"); 12794 glBindFramebuffer(GL_FRAMEBUFFER, fbo); 12795 //scope(exit) glBindFramebuffer(GL_FRAMEBUFFER, 0); 12796 12797 // attach 2D texture to this FBO 12798 GLuint tidColor = 0; 12799 glGenTextures(1, &tidColor); 12800 if (tidColor == 0) assert(0, "NanoVega: cannot create RGBA texture for FBO"); 12801 glBindTexture(GL_TEXTURE_2D, tidColor); 12802 //scope(exit) glBindTexture(GL_TEXTURE_2D, 0); 12803 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 12804 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_S"); 12805 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 12806 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_T"); 12807 //FIXME: linear or nearest? 12808 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 12809 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 12810 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MIN_FILTER"); 12811 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 12812 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 12813 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MAG_FILTER"); 12814 // empty texture 12815 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 12816 // create texture with only one color channel 12817 glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, gl.fboWidth, gl.fboHeight, 0, GL_RED, GL_UNSIGNED_BYTE, null); 12818 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 12819 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (color)"); 12820 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tidColor, 0); 12821 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (color)"); 12822 12823 // attach stencil texture to this FBO 12824 GLuint tidStencil = 0; 12825 version(nanovega_shared_stencil) { 12826 if (gl.fboTex.ptr[0].ptr[0] == 0) { 12827 glGenTextures(1, &tidStencil); 12828 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO"); 12829 gl.fboTex.ptr[0].ptr[0] = tidStencil; 12830 } else { 12831 tidStencil = gl.fboTex.ptr[0].ptr[0]; 12832 } 12833 if (fidx != 0) gl.fboTex.ptr[fidx].ptr[1] = 0; // stencil texture is shared among FBOs 12834 } else { 12835 glGenTextures(1, &tidStencil); 12836 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO"); 12837 gl.fboTex.ptr[0].ptr[0] = tidStencil; 12838 } 12839 glBindTexture(GL_TEXTURE_2D, tidStencil); 12840 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, gl.fboWidth, gl.fboHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, null); 12841 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (stencil)"); 12842 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tidStencil, 0); 12843 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (stencil)"); 12844 12845 { 12846 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); 12847 if (status != GL_FRAMEBUFFER_COMPLETE) { 12848 version(all) { 12849 import core.stdc.stdio; 12850 if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) printf("fucked attachement\n"); 12851 if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) printf("fucked dimensions\n"); 12852 if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) printf("missing attachement\n"); 12853 if (status == GL_FRAMEBUFFER_UNSUPPORTED) printf("unsupported\n"); 12854 } 12855 assert(0, "NanoVega: framebuffer creation failed"); 12856 } 12857 } 12858 12859 // clear 'em all 12860 if (doclear) { 12861 glClearColor(0, 0, 0, 0); 12862 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 12863 } 12864 12865 // save texture ids 12866 gl.fbo.ptr[fidx] = fbo; 12867 gl.fboTex.ptr[fidx].ptr[0] = tidColor; 12868 version(nanovega_shared_stencil) {} else { 12869 gl.fboTex.ptr[fidx].ptr[1] = tidStencil; 12870 } 12871 12872 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture); 12873 12874 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): created with index %d\n", gl.msp-1, fidx); } 12875 12876 return true; 12877 } 12878 12879 // will not unbind buffer 12880 void glnvg__clearFBO (GLNVGcontext* gl, int fidx) nothrow @trusted @nogc { 12881 assert(fidx >= 0 && fidx < gl.fbo.length); 12882 assert(gl.fboWidth > 0); 12883 assert(gl.fboHeight > 0); 12884 assert(gl.fbo.ptr[fidx] != 0); 12885 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[fidx]); 12886 glClearColor(0, 0, 0, 0); 12887 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 12888 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cleared with index %d\n", gl.msp-1, fidx); } 12889 } 12890 12891 // will not unbind buffer 12892 void glnvg__copyFBOToFrom (GLNVGcontext* gl, int didx, int sidx) nothrow @trusted @nogc { 12893 import core.stdc.string : memset; 12894 assert(didx >= 0 && didx < gl.fbo.length); 12895 assert(sidx >= 0 && sidx < gl.fbo.length); 12896 assert(gl.fboWidth > 0); 12897 assert(gl.fboHeight > 0); 12898 assert(gl.fbo.ptr[didx] != 0); 12899 assert(gl.fbo.ptr[sidx] != 0); 12900 if (didx == sidx) return; 12901 12902 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copy FBO: %d -> %d\n", gl.msp-1, sidx, didx); } 12903 12904 glUseProgram(gl.shaderCopyFBO.prog); 12905 12906 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[didx]); 12907 glDisable(GL_CULL_FACE); 12908 glDisable(GL_BLEND); 12909 glDisable(GL_SCISSOR_TEST); 12910 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[sidx].ptr[0]); 12911 // copy texture by drawing full quad 12912 enum x = 0; 12913 enum y = 0; 12914 immutable int w = gl.fboWidth; 12915 immutable int h = gl.fboHeight; 12916 immutable(NVGVertex[4]) vertices = 12917 [NVGVertex(x, y), // top-left 12918 NVGVertex(w, y), // top-right 12919 NVGVertex(w, h), // bottom-right 12920 NVGVertex(x, h)]; // bottom-left 12921 12922 glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices); 12923 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 12924 12925 // restore state (but don't unbind FBO) 12926 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture); 12927 glEnable(GL_CULL_FACE); 12928 glEnable(GL_BLEND); 12929 glUseProgram(gl.shader.prog); 12930 } 12931 12932 void glnvg__resetFBOClipTextureCache (GLNVGcontext* gl) nothrow @trusted @nogc { 12933 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): texture cache invalidated (%d)\n", gl.msp-1, gl.lastClipFBO); } 12934 /* 12935 if (gl.lastClipFBO >= 0) { 12936 glActiveTexture(GL_TEXTURE1); 12937 glBindTexture(GL_TEXTURE_2D, 0); 12938 glActiveTexture(GL_TEXTURE0); 12939 } 12940 */ 12941 gl.lastClipFBO = -666; 12942 } 12943 12944 void glnvg__setFBOClipTexture (GLNVGcontext* gl, GLNVGfragUniforms* frag) nothrow @trusted @nogc { 12945 //assert(gl.msp > 0 && gl.msp <= gl.maskStack.length); 12946 if (gl.lastClipFBO != -666) { 12947 // cached 12948 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cached (%d)\n", gl.msp-1, gl.lastClipFBO); } 12949 frag.doclip = (gl.lastClipFBO >= 0 ? 1 : 0); 12950 return; 12951 } 12952 12953 // no cache 12954 int fboidx = -1; 12955 mainloop: foreach_reverse (immutable sp, GLMaskState mst; gl.maskStack.ptr[0..gl.msp]/*; reverse*/) { 12956 final switch (mst) { 12957 case GLMaskState.DontMask: fboidx = -1; break mainloop; 12958 case GLMaskState.Uninitialized: break; 12959 case GLMaskState.Initialized: fboidx = cast(int)sp; break mainloop; 12960 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__setFBOClipTexture()` internal error"); 12961 } 12962 } 12963 12964 if (fboidx < 0) { 12965 // don't mask 12966 gl.lastClipFBO = -1; 12967 frag.doclip = 0; 12968 } else { 12969 // do masking 12970 assert(gl.fbo.ptr[fboidx] != 0); 12971 gl.lastClipFBO = fboidx; 12972 frag.doclip = 1; 12973 } 12974 12975 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): new cache (new:%d)\n", gl.msp-1, gl.lastClipFBO); } 12976 12977 if (gl.lastClipFBO >= 0) { 12978 assert(gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 12979 glActiveTexture(GL_TEXTURE1); 12980 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 12981 glActiveTexture(GL_TEXTURE0); 12982 } 12983 } 12984 12985 // returns index in `gl.fbo`, or -1 for "don't mask" 12986 int glnvg__generateFBOClipTexture (GLNVGcontext* gl) nothrow @trusted @nogc { 12987 assert(gl.msp > 0 && gl.msp <= gl.maskStack.length); 12988 // we need initialized FBO, even for "don't mask" case 12989 // for this, look back in stack, and either copy initialized FBO, 12990 // or stop at first uninitialized one, and clear it 12991 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.Initialized) { 12992 // shortcut 12993 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); } 12994 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[gl.msp-1]); 12995 return gl.msp-1; 12996 } 12997 foreach_reverse (immutable sp; 0..gl.msp/*; reverse*/) { 12998 final switch (gl.maskStack.ptr[sp]) { 12999 case GLMaskState.DontMask: 13000 // clear it 13001 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture\n", gl.msp-1); } 13002 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1); 13003 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared; 13004 return gl.msp-1; 13005 case GLMaskState.Uninitialized: break; // do nothing 13006 case GLMaskState.Initialized: 13007 // i found her! copy to TOS 13008 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copying texture from %d\n", gl.msp-1, cast(int)sp); } 13009 glnvg__allocFBO(gl, gl.msp-1, false); 13010 glnvg__copyFBOToFrom(gl, gl.msp-1, sp); 13011 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized; 13012 return gl.msp-1; 13013 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__generateFBOClipTexture()` internal error"); 13014 } 13015 } 13016 // nothing was initialized, lol 13017 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture (first one)\n", gl.msp-1); } 13018 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1); 13019 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared; 13020 return gl.msp-1; 13021 } 13022 13023 void glnvg__renderPushClip (void* uptr) nothrow @trusted @nogc { 13024 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13025 GLNVGcall* call = glnvg__allocCall(gl); 13026 if (call is null) return; 13027 call.type = GLNVG_PUSHCLIP; 13028 } 13029 13030 void glnvg__renderPopClip (void* uptr) nothrow @trusted @nogc { 13031 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13032 GLNVGcall* call = glnvg__allocCall(gl); 13033 if (call is null) return; 13034 call.type = GLNVG_POPCLIP; 13035 } 13036 13037 void glnvg__renderResetClip (void* uptr) nothrow @trusted @nogc { 13038 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13039 GLNVGcall* call = glnvg__allocCall(gl); 13040 if (call is null) return; 13041 call.type = GLNVG_RESETCLIP; 13042 } 13043 13044 void glnvg__clipDebugDump (void* uptr, bool doit) nothrow @trusted @nogc { 13045 version(nanovega_debug_clipping) { 13046 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13047 GLNVGcall* call = glnvg__allocCall(gl); 13048 call.type = (doit ? GLNVG_CLIP_DDUMP_ON : GLNVG_CLIP_DDUMP_OFF); 13049 } 13050 } 13051 13052 bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc { 13053 import core.stdc.stdio : snprintf; 13054 13055 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13056 enum align_ = 4; 13057 13058 char[64] shaderHeader = void; 13059 //enum shaderHeader = "#define UNIFORM_ARRAY_SIZE 12\n"; 13060 snprintf(shaderHeader.ptr, shaderHeader.length, "#define UNIFORM_ARRAY_SIZE %u\n", cast(uint)GLNVGfragUniforms.UNIFORM_ARRAY_SIZE); 13061 13062 enum fillVertShader = q{ 13063 uniform vec2 viewSize; 13064 attribute vec2 vertex; 13065 attribute vec2 tcoord; 13066 varying vec2 ftcoord; 13067 varying vec2 fpos; 13068 uniform vec4 tmat; /* abcd of affine matrix: xyzw */ 13069 uniform vec2 ttr; /* tx and ty of affine matrix */ 13070 void main (void) { 13071 /* affine transformation */ 13072 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x; 13073 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y; 13074 ftcoord = tcoord; 13075 fpos = vec2(nx, ny); 13076 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1); 13077 } 13078 }; 13079 13080 enum fillFragShader = ` 13081 uniform vec4 frag[UNIFORM_ARRAY_SIZE]; 13082 uniform sampler2D tex; 13083 uniform sampler2D clipTex; 13084 uniform vec2 viewSize; 13085 varying vec2 ftcoord; 13086 varying vec2 fpos; 13087 #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz) 13088 #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz) 13089 #define innerCol frag[6] 13090 #define middleCol frag[7] 13091 #define outerCol frag[7+1] 13092 #define scissorExt frag[8+1].xy 13093 #define scissorScale frag[8+1].zw 13094 #define extent frag[9+1].xy 13095 #define radius frag[9+1].z 13096 #define feather frag[9+1].w 13097 #define strokeMult frag[10+1].x 13098 #define strokeThr frag[10+1].y 13099 #define texType int(frag[10+1].z) 13100 #define type int(frag[10+1].w) 13101 #define doclip int(frag[11+1].x) 13102 #define midp frag[11+1].y 13103 13104 float sdroundrect (in vec2 pt, in vec2 ext, in float rad) { 13105 vec2 ext2 = ext-vec2(rad, rad); 13106 vec2 d = abs(pt)-ext2; 13107 return min(max(d.x, d.y), 0.0)+length(max(d, 0.0))-rad; 13108 } 13109 13110 // Scissoring 13111 float scissorMask (in vec2 p) { 13112 vec2 sc = (abs((scissorMat*vec3(p, 1.0)).xy)-scissorExt); 13113 sc = vec2(0.5, 0.5)-sc*scissorScale; 13114 return clamp(sc.x, 0.0, 1.0)*clamp(sc.y, 0.0, 1.0); 13115 } 13116 13117 #ifdef EDGE_AA 13118 // Stroke - from [0..1] to clipped pyramid, where the slope is 1px. 13119 float strokeMask () { 13120 return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult)*min(1.0, ftcoord.y); 13121 } 13122 #endif 13123 13124 void main (void) { 13125 // clipping 13126 if (doclip != 0) { 13127 /*vec4 clr = texelFetch(clipTex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);*/ 13128 vec4 clr = texture2D(clipTex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y)); 13129 if (clr.r == 0.0) discard; 13130 } 13131 float scissor = scissorMask(fpos); 13132 if (scissor <= 0.0) discard; //k8: is it really faster? 13133 #ifdef EDGE_AA 13134 float strokeAlpha = strokeMask(); 13135 if (strokeAlpha < strokeThr) discard; 13136 #else 13137 float strokeAlpha = 1.0; 13138 #endif 13139 // rendering 13140 vec4 color; 13141 if (type == 0) { /* NSVG_SHADER_FILLCOLOR */ 13142 color = innerCol; 13143 // Combine alpha 13144 color *= strokeAlpha*scissor; 13145 } else if (type == 1) { /* NSVG_SHADER_FILLGRAD */ 13146 // Gradient 13147 // Calculate gradient color using box gradient 13148 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy; 13149 float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0); 13150 if (midp <= 0.0) { 13151 color = mix(innerCol, outerCol, d); 13152 } else { 13153 float gdst = min(midp, 1.0); 13154 if (d < gdst) { 13155 color = mix(innerCol, middleCol, d/gdst); 13156 } else { 13157 color = mix(middleCol, outerCol, (d-gdst)/gdst); 13158 } 13159 } 13160 // Combine alpha 13161 color *= strokeAlpha*scissor; 13162 } else if (type == 2) { /* NSVG_SHADER_FILLIMG */ 13163 // Image 13164 // Calculate color from texture 13165 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy/extent; 13166 color = texture2D(tex, pt); 13167 if (texType == 1) color = vec4(color.xyz*color.w, color.w); 13168 if (texType == 2) color = vec4(color.x); 13169 // Apply color tint and alpha 13170 color *= innerCol; 13171 // Combine alpha 13172 color *= strokeAlpha*scissor; 13173 } else if (type == 3) { /* NSVG_SHADER_SIMPLE */ 13174 // Stencil fill 13175 color = vec4(1, 1, 1, 1); 13176 } else if (type == 4) { /* NSVG_SHADER_IMG */ 13177 // Textured tris 13178 color = texture2D(tex, ftcoord); 13179 if (texType == 1) color = vec4(color.xyz*color.w, color.w); 13180 if (texType == 2) color = vec4(color.x); 13181 color *= scissor; 13182 color *= innerCol; // Apply color tint 13183 } 13184 gl_FragColor = color; 13185 } 13186 `; 13187 13188 enum clipVertShaderFill = q{ 13189 uniform vec2 viewSize; 13190 attribute vec2 vertex; 13191 uniform vec4 tmat; /* abcd of affine matrix: xyzw */ 13192 uniform vec2 ttr; /* tx and ty of affine matrix */ 13193 void main (void) { 13194 /* affine transformation */ 13195 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x; 13196 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y; 13197 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1); 13198 } 13199 }; 13200 13201 enum clipFragShaderFill = q{ 13202 uniform vec2 viewSize; 13203 void main (void) { 13204 gl_FragColor = vec4(1, 1, 1, 1); 13205 } 13206 }; 13207 13208 enum clipVertShaderCopy = q{ 13209 uniform vec2 viewSize; 13210 attribute vec2 vertex; 13211 void main (void) { 13212 gl_Position = vec4(2.0*vertex.x/viewSize.x-1.0, 1.0-2.0*vertex.y/viewSize.y, 0, 1); 13213 } 13214 }; 13215 13216 enum clipFragShaderCopy = q{ 13217 uniform sampler2D tex; 13218 uniform vec2 viewSize; 13219 void main (void) { 13220 //gl_FragColor = texelFetch(tex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0); 13221 gl_FragColor = texture2D(tex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y)); 13222 } 13223 }; 13224 13225 glnvg__checkError(gl, "init"); 13226 13227 string defines = (gl.flags&NVGContextFlag.Antialias ? "#define EDGE_AA 1\n" : null); 13228 if (!glnvg__createShader(&gl.shader, "shader", shaderHeader.ptr, defines.ptr, fillVertShader, fillFragShader)) return false; 13229 if (!glnvg__createShader(&gl.shaderFillFBO, "shaderFillFBO", shaderHeader.ptr, defines.ptr, clipVertShaderFill, clipFragShaderFill)) return false; 13230 if (!glnvg__createShader(&gl.shaderCopyFBO, "shaderCopyFBO", shaderHeader.ptr, defines.ptr, clipVertShaderCopy, clipFragShaderCopy)) return false; 13231 13232 glnvg__checkError(gl, "uniform locations"); 13233 glnvg__getUniforms(&gl.shader); 13234 glnvg__getUniforms(&gl.shaderFillFBO); 13235 glnvg__getUniforms(&gl.shaderCopyFBO); 13236 13237 // Create dynamic vertex array 13238 glGenBuffers(1, &gl.vertBuf); 13239 13240 gl.fragSize = GLNVGfragUniforms.sizeof+align_-GLNVGfragUniforms.sizeof%align_; 13241 13242 glnvg__checkError(gl, "create done"); 13243 13244 glFinish(); 13245 13246 return true; 13247 } 13248 13249 int glnvg__renderCreateTexture (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc { 13250 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13251 GLNVGtexture* tex = glnvg__allocTexture(gl); 13252 13253 if (tex is null) return 0; 13254 13255 glGenTextures(1, &tex.tex); 13256 tex.width = w; 13257 tex.height = h; 13258 tex.type = type; 13259 tex.flags = imageFlags; 13260 glnvg__bindTexture(gl, tex.tex); 13261 13262 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("created texture with id %d; glid=%u\n", tex.id, tex.tex); }} 13263 13264 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 13265 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width); 13266 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13267 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13268 13269 13270 13271 immutable ttype = (type == NVGtexture.RGBA ? GL_RGBA : GL_RED); 13272 glTexImage2D(GL_TEXTURE_2D, 0, ttype, w, h, 0, ttype, GL_UNSIGNED_BYTE, data); 13273 // GL 3.0 and later have support for a dedicated function for generating mipmaps 13274 // it needs to be called after the glTexImage2D call 13275 if (imageFlags & NVGImageFlag.GenerateMipmaps) 13276 glGenerateMipmap(GL_TEXTURE_2D); 13277 13278 immutable tfmin = 13279 (imageFlags & NVGImageFlag.GenerateMipmaps ? GL_LINEAR_MIPMAP_LINEAR : 13280 imageFlags & NVGImageFlag.NoFiltering ? GL_NEAREST : 13281 GL_LINEAR); 13282 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.0); 13283 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tfmin); 13284 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST : GL_LINEAR)); 13285 13286 int flag; 13287 if (imageFlags&NVGImageFlag.RepeatX) 13288 flag = GL_REPEAT; 13289 else if (imageFlags&NVGImageFlag.ClampToBorderX) 13290 flag = GL_CLAMP_TO_BORDER; 13291 else 13292 flag = GL_CLAMP_TO_EDGE; 13293 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, flag); 13294 13295 13296 if (imageFlags&NVGImageFlag.RepeatY) 13297 flag = GL_REPEAT; 13298 else if (imageFlags&NVGImageFlag.ClampToBorderY) 13299 flag = GL_CLAMP_TO_BORDER; 13300 else 13301 flag = GL_CLAMP_TO_EDGE; 13302 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, flag); 13303 13304 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 13305 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 13306 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13307 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13308 13309 glnvg__checkError(gl, "create tex"); 13310 glnvg__bindTexture(gl, 0); 13311 13312 return tex.id; 13313 } 13314 13315 bool glnvg__renderDeleteTexture (void* uptr, int image) nothrow @trusted @nogc { 13316 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13317 return glnvg__deleteTexture(gl, image); 13318 } 13319 13320 bool glnvg__renderTextureIncRef (void* uptr, int image) nothrow @trusted @nogc { 13321 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13322 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13323 if (tex is null) { 13324 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT incref texture with id %d\n", image); }} 13325 return false; 13326 } 13327 import core.atomic : atomicOp; 13328 atomicOp!"+="(tex.rc, 1); 13329 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("texture #%d: incref; newref=%d\n", image, tex.rc); }} 13330 return true; 13331 } 13332 13333 bool glnvg__renderUpdateTexture (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc { 13334 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13335 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13336 13337 if (tex is null) { 13338 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT update texture with id %d\n", image); }} 13339 return false; 13340 } 13341 13342 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("updated texture with id %d; glid=%u\n", tex.id, image, tex.tex); }} 13343 13344 glnvg__bindTexture(gl, tex.tex); 13345 13346 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 13347 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width); 13348 glPixelStorei(GL_UNPACK_SKIP_PIXELS, x); 13349 glPixelStorei(GL_UNPACK_SKIP_ROWS, y); 13350 13351 immutable ttype = (tex.type == NVGtexture.RGBA ? GL_RGBA : GL_RED); 13352 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, ttype, GL_UNSIGNED_BYTE, data); 13353 13354 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 13355 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 13356 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13357 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13358 13359 glnvg__bindTexture(gl, 0); 13360 13361 return true; 13362 } 13363 13364 bool glnvg__renderGetTextureSize (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc { 13365 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13366 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13367 if (tex is null) { 13368 if (w !is null) *w = 0; 13369 if (h !is null) *h = 0; 13370 return false; 13371 } else { 13372 if (w !is null) *w = tex.width; 13373 if (h !is null) *h = tex.height; 13374 return true; 13375 } 13376 } 13377 13378 void glnvg__xformToMat3x4 (float[] m3, const(float)[] t) nothrow @trusted @nogc { 13379 assert(t.length >= 6); 13380 assert(m3.length >= 12); 13381 m3.ptr[0] = t.ptr[0]; 13382 m3.ptr[1] = t.ptr[1]; 13383 m3.ptr[2] = 0.0f; 13384 m3.ptr[3] = 0.0f; 13385 m3.ptr[4] = t.ptr[2]; 13386 m3.ptr[5] = t.ptr[3]; 13387 m3.ptr[6] = 0.0f; 13388 m3.ptr[7] = 0.0f; 13389 m3.ptr[8] = t.ptr[4]; 13390 m3.ptr[9] = t.ptr[5]; 13391 m3.ptr[10] = 1.0f; 13392 m3.ptr[11] = 0.0f; 13393 } 13394 13395 NVGColor glnvg__premulColor() (in auto ref NVGColor c) nothrow @trusted @nogc { 13396 //pragma(inline, true); 13397 NVGColor res = void; 13398 res.r = c.r*c.a; 13399 res.g = c.g*c.a; 13400 res.b = c.b*c.a; 13401 res.a = c.a; 13402 return res; 13403 } 13404 13405 bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* paint, NVGscissor* scissor, float width, float fringe, float strokeThr) nothrow @trusted @nogc { 13406 import core.stdc.math : sqrtf; 13407 GLNVGtexture* tex = null; 13408 NVGMatrix invxform = void; 13409 13410 memset(frag, 0, (*frag).sizeof); 13411 13412 frag.innerCol = glnvg__premulColor(paint.innerColor); 13413 frag.middleCol = glnvg__premulColor(paint.middleColor); 13414 frag.outerCol = glnvg__premulColor(paint.outerColor); 13415 frag.midp = paint.midp; 13416 13417 if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) { 13418 memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof); 13419 frag.scissorExt.ptr[0] = 1.0f; 13420 frag.scissorExt.ptr[1] = 1.0f; 13421 frag.scissorScale.ptr[0] = 1.0f; 13422 frag.scissorScale.ptr[1] = 1.0f; 13423 } else { 13424 //nvgTransformInverse(invxform[], scissor.xform[]); 13425 invxform = scissor.xform.inverted; 13426 glnvg__xformToMat3x4(frag.scissorMat[], invxform.mat[]); 13427 frag.scissorExt.ptr[0] = scissor.extent.ptr[0]; 13428 frag.scissorExt.ptr[1] = scissor.extent.ptr[1]; 13429 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; 13430 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; 13431 } 13432 13433 memcpy(frag.extent.ptr, paint.extent.ptr, frag.extent.sizeof); 13434 frag.strokeMult = (width*0.5f+fringe*0.5f)/fringe; 13435 frag.strokeThr = strokeThr; 13436 13437 if (paint.image.valid) { 13438 tex = glnvg__findTexture(gl, paint.image.id); 13439 if (tex is null) return false; 13440 if ((tex.flags&NVGImageFlag.FlipY) != 0) { 13441 /* 13442 NVGMatrix flipped; 13443 nvgTransformScale(flipped[], 1.0f, -1.0f); 13444 nvgTransformMultiply(flipped[], paint.xform[]); 13445 nvgTransformInverse(invxform[], flipped[]); 13446 */ 13447 /* 13448 NVGMatrix m1 = void, m2 = void; 13449 nvgTransformTranslate(m1[], 0.0f, frag.extent.ptr[1]*0.5f); 13450 nvgTransformMultiply(m1[], paint.xform[]); 13451 nvgTransformScale(m2[], 1.0f, -1.0f); 13452 nvgTransformMultiply(m2[], m1[]); 13453 nvgTransformTranslate(m1[], 0.0f, -frag.extent.ptr[1]*0.5f); 13454 nvgTransformMultiply(m1[], m2[]); 13455 nvgTransformInverse(invxform[], m1[]); 13456 */ 13457 NVGMatrix m1 = NVGMatrix.Translated(0.0f, frag.extent.ptr[1]*0.5f); 13458 m1.mul(paint.xform); 13459 NVGMatrix m2 = NVGMatrix.Scaled(1.0f, -1.0f); 13460 m2.mul(m1); 13461 m1 = NVGMatrix.Translated(0.0f, -frag.extent.ptr[1]*0.5f); 13462 m1.mul(m2); 13463 invxform = m1.inverted; 13464 } else { 13465 //nvgTransformInverse(invxform[], paint.xform[]); 13466 invxform = paint.xform.inverted; 13467 } 13468 frag.type = NSVG_SHADER_FILLIMG; 13469 13470 if (tex.type == NVGtexture.RGBA) { 13471 frag.texType = (tex.flags&NVGImageFlag.Premultiplied ? 0 : 1); 13472 } else { 13473 frag.texType = 2; 13474 } 13475 //printf("frag.texType = %d\n", frag.texType); 13476 } else { 13477 frag.type = (paint.simpleColor ? NSVG_SHADER_FILLCOLOR : NSVG_SHADER_FILLGRAD); 13478 frag.radius = paint.radius; 13479 frag.feather = paint.feather; 13480 //nvgTransformInverse(invxform[], paint.xform[]); 13481 invxform = paint.xform.inverted; 13482 } 13483 13484 glnvg__xformToMat3x4(frag.paintMat[], invxform.mat[]); 13485 13486 return true; 13487 } 13488 13489 void glnvg__setUniforms (GLNVGcontext* gl, int uniformOffset, int image) nothrow @trusted @nogc { 13490 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); 13491 glnvg__setFBOClipTexture(gl, frag); 13492 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.Frag], frag.UNIFORM_ARRAY_SIZE, &(frag.uniformArray.ptr[0].ptr[0])); 13493 glnvg__checkError(gl, "glnvg__setUniforms"); 13494 if (image != 0) { 13495 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13496 glnvg__bindTexture(gl, (tex !is null ? tex.tex : 0)); 13497 glnvg__checkError(gl, "tex paint tex"); 13498 } else { 13499 glnvg__bindTexture(gl, 0); 13500 } 13501 } 13502 13503 void glnvg__finishClip (GLNVGcontext* gl, NVGClipMode clipmode) nothrow @trusted @nogc { 13504 assert(clipmode != NVGClipMode.None); 13505 13506 // fill FBO, clear stencil buffer 13507 //TODO: optimize with bounds? 13508 version(all) { 13509 //glnvg__resetAffine(gl); 13510 //glUseProgram(gl.shaderFillFBO.prog); 13511 glDisable(GL_CULL_FACE); 13512 glDisable(GL_BLEND); 13513 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13514 glEnable(GL_STENCIL_TEST); 13515 if (gl.doClipUnion) { 13516 // for "and" we should clear everything that is NOT stencil-masked 13517 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13518 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13519 } else { 13520 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x00, 0xff); 13521 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13522 } 13523 13524 immutable(NVGVertex[4]) vertices = 13525 [NVGVertex(0, 0, 0, 0), 13526 NVGVertex(0, gl.fboHeight, 0, 0), 13527 NVGVertex(gl.fboWidth, gl.fboHeight, 0, 0), 13528 NVGVertex(gl.fboWidth, 0, 0, 0)]; 13529 13530 glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices); 13531 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 13532 13533 //glnvg__restoreAffine(gl); 13534 } 13535 13536 glBindFramebuffer(GL_FRAMEBUFFER, gl.mainFBO); 13537 glDisable(GL_COLOR_LOGIC_OP); 13538 //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // done above 13539 glEnable(GL_BLEND); 13540 glDisable(GL_STENCIL_TEST); 13541 glEnable(GL_CULL_FACE); 13542 glUseProgram(gl.shader.prog); 13543 13544 // set current FBO as used one 13545 assert(gl.msp > 0 && gl.fbo.ptr[gl.msp-1] > 0 && gl.fboTex.ptr[gl.msp-1].ptr[0] > 0); 13546 if (gl.lastClipFBO != gl.msp-1) { 13547 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); } 13548 gl.lastClipFBO = gl.msp-1; 13549 glActiveTexture(GL_TEXTURE1); 13550 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 13551 glActiveTexture(GL_TEXTURE0); 13552 } 13553 } 13554 13555 void glnvg__setClipUniforms (GLNVGcontext* gl, int uniformOffset, NVGClipMode clipmode) nothrow @trusted @nogc { 13556 assert(clipmode != NVGClipMode.None); 13557 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); 13558 // save uniform offset for `glnvg__finishClip()` 13559 gl.lastClipUniOfs = uniformOffset; 13560 // get FBO index, bind this FBO 13561 immutable int clipTexId = glnvg__generateFBOClipTexture(gl); 13562 assert(clipTexId >= 0); 13563 glUseProgram(gl.shaderFillFBO.prog); 13564 glnvg__checkError(gl, "use"); 13565 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[clipTexId]); 13566 // set logic op for clip 13567 gl.doClipUnion = false; 13568 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.JustCleared) { 13569 // it is cleared to zero, we can just draw a path 13570 glDisable(GL_COLOR_LOGIC_OP); 13571 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized; 13572 } else { 13573 glEnable(GL_COLOR_LOGIC_OP); 13574 final switch (clipmode) { 13575 case NVGClipMode.None: assert(0, "wtf?!"); 13576 case NVGClipMode.Union: glLogicOp(GL_CLEAR); gl.doClipUnion = true; break; // use `GL_CLEAR` to avoid adding another shader mode 13577 case NVGClipMode.Or: glLogicOp(GL_COPY); break; // GL_OR 13578 case NVGClipMode.Xor: glLogicOp(GL_XOR); break; 13579 case NVGClipMode.Sub: glLogicOp(GL_CLEAR); break; 13580 case NVGClipMode.Replace: glLogicOp(GL_COPY); break; 13581 } 13582 } 13583 // set affine matrix 13584 glUniform4fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TMat], 1, gl.lastAffine.mat.ptr); 13585 glnvg__checkError(gl, "affine 0"); 13586 glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TTr], 1, gl.lastAffine.mat.ptr+4); 13587 glnvg__checkError(gl, "affine 1"); 13588 // setup common OpenGL parameters 13589 glDisable(GL_BLEND); 13590 glDisable(GL_CULL_FACE); 13591 glEnable(GL_STENCIL_TEST); 13592 glnvg__stencilMask(gl, 0xff); 13593 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13594 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 13595 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13596 } 13597 13598 void glnvg__renderViewport (void* uptr, int width, int height) nothrow @trusted @nogc { 13599 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13600 gl.inFrame = true; 13601 gl.view.ptr[0] = cast(float)width; 13602 gl.view.ptr[1] = cast(float)height; 13603 // kill FBOs if we need to create new ones (flushing will recreate 'em if necessary) 13604 if (width != gl.fboWidth || height != gl.fboHeight) { 13605 glnvg__killFBOs(gl); 13606 gl.fboWidth = width; 13607 gl.fboHeight = height; 13608 } 13609 gl.msp = 1; 13610 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13611 // texture cleanup 13612 import core.atomic : atomicLoad; 13613 if (atomicLoad(gl.mustCleanTextures)) { 13614 try { 13615 import core.thread : Thread; 13616 static if (__VERSION__ < 2076) { 13617 DGNoThrowNoGC(() { 13618 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13619 })(); 13620 } else { 13621 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13622 } 13623 { 13624 GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow; 13625 gl.mustCleanTextures = false; 13626 foreach (immutable tidx, ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) { 13627 // no need to use atomic ops here, as we're locked 13628 if (tex.rc == 0 && tex.tex != 0 && tex.id == 0) { 13629 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** cleaned up texture with glid=%u\n", tex.tex); }} 13630 import core.stdc.string : memset; 13631 if ((tex.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tex.tex); 13632 memset(&tex, 0, tex.sizeof); 13633 tex.nextfree = gl.freetexid; 13634 gl.freetexid = cast(int)tidx; 13635 } 13636 } 13637 } 13638 } catch (Exception e) {} 13639 } 13640 } 13641 13642 void glnvg__fill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13643 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13644 int npaths = call.pathCount; 13645 13646 if (call.clipmode == NVGClipMode.None) { 13647 // Draw shapes 13648 glEnable(GL_STENCIL_TEST); 13649 glnvg__stencilMask(gl, 0xffU); 13650 glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xffU); 13651 13652 glnvg__setUniforms(gl, call.uniformOffset, 0); 13653 glnvg__checkError(gl, "fill simple"); 13654 13655 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13656 if (call.evenOdd) { 13657 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT); 13658 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT); 13659 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); 13660 } else { 13661 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 13662 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 13663 } 13664 glDisable(GL_CULL_FACE); 13665 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13666 glEnable(GL_CULL_FACE); 13667 13668 // Draw anti-aliased pixels 13669 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13670 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image); 13671 glnvg__checkError(gl, "fill fill"); 13672 13673 if (gl.flags&NVGContextFlag.Antialias) { 13674 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xffU); 13675 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13676 // Draw fringes 13677 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13678 } 13679 13680 // Draw fill 13681 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xffU); 13682 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13683 if (call.evenOdd) { 13684 glDisable(GL_CULL_FACE); 13685 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount); 13686 //foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13687 glEnable(GL_CULL_FACE); 13688 } else { 13689 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount); 13690 } 13691 13692 glDisable(GL_STENCIL_TEST); 13693 } else { 13694 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); // this activates our FBO 13695 glnvg__checkError(gl, "fillclip simple"); 13696 glnvg__stencilFunc(gl, GL_ALWAYS, 0x00, 0xffU); 13697 if (call.evenOdd) { 13698 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT); 13699 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT); 13700 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); 13701 } else { 13702 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 13703 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 13704 } 13705 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13706 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13707 } 13708 } 13709 13710 void glnvg__convexFill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13711 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13712 int npaths = call.pathCount; 13713 13714 if (call.clipmode == NVGClipMode.None) { 13715 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13716 glnvg__checkError(gl, "convex fill"); 13717 if (call.evenOdd) glDisable(GL_CULL_FACE); 13718 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13719 if (gl.flags&NVGContextFlag.Antialias) { 13720 // Draw fringes 13721 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13722 } 13723 if (call.evenOdd) glEnable(GL_CULL_FACE); 13724 } else { 13725 glnvg__setClipUniforms(gl, call.uniformOffset, call.clipmode); // this activates our FBO 13726 glnvg__checkError(gl, "clip convex fill"); 13727 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13728 if (gl.flags&NVGContextFlag.Antialias) { 13729 // Draw fringes 13730 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13731 } 13732 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13733 } 13734 } 13735 13736 void glnvg__stroke (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13737 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13738 int npaths = call.pathCount; 13739 13740 if (call.clipmode == NVGClipMode.None) { 13741 if (gl.flags&NVGContextFlag.StencilStrokes) { 13742 glEnable(GL_STENCIL_TEST); 13743 glnvg__stencilMask(gl, 0xff); 13744 13745 // Fill the stroke base without overlap 13746 glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff); 13747 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 13748 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image); 13749 glnvg__checkError(gl, "stroke fill 0"); 13750 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13751 13752 // Draw anti-aliased pixels. 13753 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13754 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13755 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13756 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13757 13758 // Clear stencil buffer. 13759 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13760 glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff); 13761 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13762 glnvg__checkError(gl, "stroke fill 1"); 13763 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13764 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13765 13766 glDisable(GL_STENCIL_TEST); 13767 13768 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f); 13769 } else { 13770 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13771 glnvg__checkError(gl, "stroke fill"); 13772 // Draw Strokes 13773 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13774 } 13775 } else { 13776 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); 13777 glnvg__checkError(gl, "stroke fill 0"); 13778 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13779 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13780 } 13781 } 13782 13783 void glnvg__triangles (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13784 if (call.clipmode == NVGClipMode.None) { 13785 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13786 glnvg__checkError(gl, "triangles fill"); 13787 glDrawArrays(GL_TRIANGLES, call.triangleOffset, call.triangleCount); 13788 } else { 13789 //TODO(?): use texture as mask? 13790 } 13791 } 13792 13793 void glnvg__affine (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13794 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, call.affine.mat.ptr); 13795 glnvg__checkError(gl, "affine"); 13796 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, call.affine.mat.ptr+4); 13797 glnvg__checkError(gl, "affine"); 13798 //glnvg__setUniforms(gl, call.uniformOffset, call.image); 13799 } 13800 13801 void glnvg__renderCancelInternal (GLNVGcontext* gl, bool clearTextures) nothrow @trusted @nogc { 13802 scope(exit) gl.inFrame = false; 13803 if (clearTextures && gl.inFrame) { 13804 try { 13805 import core.thread : Thread; 13806 static if (__VERSION__ < 2076) { 13807 DGNoThrowNoGC(() { 13808 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13809 })(); 13810 } else { 13811 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13812 } 13813 } catch (Exception e) {} 13814 foreach (ref GLNVGcall c; gl.calls[0..gl.ncalls]) if (c.image > 0) glnvg__deleteTexture(gl, c.image); 13815 } 13816 gl.nverts = 0; 13817 gl.npaths = 0; 13818 gl.ncalls = 0; 13819 gl.nuniforms = 0; 13820 gl.msp = 1; 13821 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13822 } 13823 13824 void glnvg__renderCancel (void* uptr) nothrow @trusted @nogc { 13825 glnvg__renderCancelInternal(cast(GLNVGcontext*)uptr, true); 13826 } 13827 13828 GLenum glnvg_convertBlendFuncFactor (NVGBlendFactor factor) pure nothrow @trusted @nogc { 13829 if (factor == NVGBlendFactor.Zero) return GL_ZERO; 13830 if (factor == NVGBlendFactor.One) return GL_ONE; 13831 if (factor == NVGBlendFactor.SrcColor) return GL_SRC_COLOR; 13832 if (factor == NVGBlendFactor.OneMinusSrcColor) return GL_ONE_MINUS_SRC_COLOR; 13833 if (factor == NVGBlendFactor.DstColor) return GL_DST_COLOR; 13834 if (factor == NVGBlendFactor.OneMinusDstColor) return GL_ONE_MINUS_DST_COLOR; 13835 if (factor == NVGBlendFactor.SrcAlpha) return GL_SRC_ALPHA; 13836 if (factor == NVGBlendFactor.OneMinusSrcAlpha) return GL_ONE_MINUS_SRC_ALPHA; 13837 if (factor == NVGBlendFactor.DstAlpha) return GL_DST_ALPHA; 13838 if (factor == NVGBlendFactor.OneMinusDstAlpha) return GL_ONE_MINUS_DST_ALPHA; 13839 if (factor == NVGBlendFactor.SrcAlphaSaturate) return GL_SRC_ALPHA_SATURATE; 13840 return GL_INVALID_ENUM; 13841 } 13842 13843 GLNVGblend glnvg__buildBlendFunc (NVGCompositeOperationState op) pure nothrow @trusted @nogc { 13844 GLNVGblend res; 13845 res.simple = op.simple; 13846 res.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB); 13847 res.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB); 13848 res.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha); 13849 res.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha); 13850 if (res.simple) { 13851 if (res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) { 13852 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM; 13853 } 13854 } else { 13855 if (res.srcRGB == GL_INVALID_ENUM || res.dstRGB == GL_INVALID_ENUM || res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) { 13856 res.simple = true; 13857 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM; 13858 } 13859 } 13860 return res; 13861 } 13862 13863 void glnvg__blendCompositeOperation() (GLNVGcontext* gl, in auto ref GLNVGblend op) nothrow @trusted @nogc { 13864 //glBlendFuncSeparate(glnvg_convertBlendFuncFactor(op.srcRGB), glnvg_convertBlendFuncFactor(op.dstRGB), glnvg_convertBlendFuncFactor(op.srcAlpha), glnvg_convertBlendFuncFactor(op.dstAlpha)); 13865 static if (NANOVG_GL_USE_STATE_FILTER) { 13866 if (gl.blendFunc.simple == op.simple) { 13867 if (op.simple) { 13868 if (gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return; 13869 } else { 13870 if (gl.blendFunc.srcRGB == op.srcRGB && gl.blendFunc.dstRGB == op.dstRGB && gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return; 13871 } 13872 } 13873 gl.blendFunc = op; 13874 } 13875 if (op.simple) { 13876 if (op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) { 13877 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13878 } else { 13879 glBlendFunc(op.srcAlpha, op.dstAlpha); 13880 } 13881 } else { 13882 if (op.srcRGB == GL_INVALID_ENUM || op.dstRGB == GL_INVALID_ENUM || op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) { 13883 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13884 } else { 13885 glBlendFuncSeparate(op.srcRGB, op.dstRGB, op.srcAlpha, op.dstAlpha); 13886 } 13887 } 13888 } 13889 13890 void glnvg__renderSetAffine (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc { 13891 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13892 GLNVGcall* call; 13893 // if last operation was GLNVG_AFFINE, simply replace the matrix 13894 if (gl.ncalls > 0 && gl.calls[gl.ncalls-1].type == GLNVG_AFFINE) { 13895 call = &gl.calls[gl.ncalls-1]; 13896 } else { 13897 call = glnvg__allocCall(gl); 13898 if (call is null) return; 13899 call.type = GLNVG_AFFINE; 13900 } 13901 call.affine.mat.ptr[0..6] = mat.mat.ptr[0..6]; 13902 } 13903 13904 version(nanovega_debug_clipping) public __gshared bool nanovegaClipDebugDump = false; 13905 13906 void glnvg__renderFlush (void* uptr) nothrow @trusted @nogc { 13907 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13908 if (!gl.inFrame) assert(0, "NanoVega: internal driver error"); 13909 try { 13910 import core.thread : Thread; 13911 static if (__VERSION__ < 2076) { 13912 DGNoThrowNoGC(() { 13913 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13914 })(); 13915 } else { 13916 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13917 } 13918 } catch (Exception e) {} 13919 scope(exit) gl.inFrame = false; 13920 13921 glnvg__resetError!true(gl); 13922 { 13923 int vv = 0; 13924 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &vv); 13925 if (glGetError() || vv < 0) vv = 0; 13926 gl.mainFBO = cast(uint)vv; 13927 } 13928 13929 enum ShaderType { None, Fill, Clip } 13930 auto lastShader = ShaderType.None; 13931 if (gl.ncalls > 0) { 13932 gl.msp = 1; 13933 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13934 13935 // Setup require GL state. 13936 glUseProgram(gl.shader.prog); 13937 13938 glActiveTexture(GL_TEXTURE1); 13939 glBindTexture(GL_TEXTURE_2D, 0); 13940 glActiveTexture(GL_TEXTURE0); 13941 glnvg__resetFBOClipTextureCache(gl); 13942 13943 //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13944 static if (NANOVG_GL_USE_STATE_FILTER) { 13945 gl.blendFunc.simple = true; 13946 gl.blendFunc.srcRGB = gl.blendFunc.dstRGB = gl.blendFunc.srcAlpha = gl.blendFunc.dstAlpha = GL_INVALID_ENUM; 13947 } 13948 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // just in case 13949 glEnable(GL_CULL_FACE); 13950 glCullFace(GL_BACK); 13951 glFrontFace(GL_CCW); 13952 glEnable(GL_BLEND); 13953 glDisable(GL_DEPTH_TEST); 13954 glDisable(GL_SCISSOR_TEST); 13955 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13956 glStencilMask(0xffffffff); 13957 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13958 glStencilFunc(GL_ALWAYS, 0, 0xffffffff); 13959 glActiveTexture(GL_TEXTURE0); 13960 glBindTexture(GL_TEXTURE_2D, 0); 13961 static if (NANOVG_GL_USE_STATE_FILTER) { 13962 gl.boundTexture = 0; 13963 gl.stencilMask = 0xffffffff; 13964 gl.stencilFunc = GL_ALWAYS; 13965 gl.stencilFuncRef = 0; 13966 gl.stencilFuncMask = 0xffffffff; 13967 } 13968 glnvg__checkError(gl, "OpenGL setup"); 13969 13970 // Upload vertex data 13971 glBindBuffer(GL_ARRAY_BUFFER, gl.vertBuf); 13972 // ensure that there's space for at least 4 vertices in case we need to draw a quad (e. g. framebuffer copy) 13973 glBufferData(GL_ARRAY_BUFFER, (gl.nverts < 4 ? 4 : gl.nverts) * NVGVertex.sizeof, gl.verts, GL_STREAM_DRAW); 13974 glEnableVertexAttribArray(0); 13975 glEnableVertexAttribArray(1); 13976 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)cast(usize)0); 13977 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)(0+2*float.sizeof)); 13978 glnvg__checkError(gl, "vertex data uploading"); 13979 13980 // Set view and texture just once per frame. 13981 glUniform1i(gl.shader.loc[GLNVGuniformLoc.Tex], 0); 13982 if (gl.shader.loc[GLNVGuniformLoc.ClipTex] != -1) { 13983 //{ import core.stdc.stdio; printf("%d\n", gl.shader.loc[GLNVGuniformLoc.ClipTex]); } 13984 glUniform1i(gl.shader.loc[GLNVGuniformLoc.ClipTex], 1); 13985 } 13986 if (gl.shader.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shader.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 13987 glnvg__checkError(gl, "render shader setup"); 13988 13989 // Reset affine transformations. 13990 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, NVGMatrix.IdentityMat.ptr); 13991 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, NVGMatrix.IdentityMat.ptr+4); 13992 glnvg__checkError(gl, "affine setup"); 13993 13994 // set clip shaders params 13995 // fill 13996 glUseProgram(gl.shaderFillFBO.prog); 13997 glnvg__checkError(gl, "clip shaders setup (fill 0)"); 13998 if (gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 13999 glnvg__checkError(gl, "clip shaders setup (fill 1)"); 14000 // copy 14001 glUseProgram(gl.shaderCopyFBO.prog); 14002 glnvg__checkError(gl, "clip shaders setup (copy 0)"); 14003 if (gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 14004 glnvg__checkError(gl, "clip shaders setup (copy 1)"); 14005 //glUniform1i(gl.shaderFillFBO.loc[GLNVGuniformLoc.Tex], 0); 14006 glUniform1i(gl.shaderCopyFBO.loc[GLNVGuniformLoc.Tex], 0); 14007 glnvg__checkError(gl, "clip shaders setup (copy 2)"); 14008 // restore render shader 14009 glUseProgram(gl.shader.prog); 14010 14011 //{ 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]); } 14012 14013 gl.lastAffine.identity; 14014 14015 foreach (int i; 0..gl.ncalls) { 14016 GLNVGcall* call = &gl.calls[i]; 14017 switch (call.type) { 14018 case GLNVG_FILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__fill(gl, call); break; 14019 case GLNVG_CONVEXFILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__convexFill(gl, call); break; 14020 case GLNVG_STROKE: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__stroke(gl, call); break; 14021 case GLNVG_TRIANGLES: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__triangles(gl, call); break; 14022 case GLNVG_AFFINE: gl.lastAffine = call.affine; glnvg__affine(gl, call); break; 14023 // clip region management 14024 case GLNVG_PUSHCLIP: 14025 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]); } 14026 if (gl.msp >= gl.maskStack.length) assert(0, "NanoVega: mask stack overflow in OpenGL backend"); 14027 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.DontMask) { 14028 gl.maskStack.ptr[gl.msp++] = GLMaskState.DontMask; 14029 } else { 14030 gl.maskStack.ptr[gl.msp++] = GLMaskState.Uninitialized; 14031 } 14032 // no need to reset FBO cache here, as nothing was changed 14033 break; 14034 case GLNVG_POPCLIP: 14035 if (gl.msp <= 1) assert(0, "NanoVega: mask stack underflow in OpenGL backend"); 14036 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]); } 14037 --gl.msp; 14038 assert(gl.msp > 0); 14039 //{ import core.stdc.stdio; printf("popped; new msp is %d; state is %d\n", gl.msp, gl.maskStack.ptr[gl.msp]); } 14040 // check popped item 14041 final switch (gl.maskStack.ptr[gl.msp]) { 14042 case GLMaskState.DontMask: 14043 // if last FBO was "don't mask", reset cache if current is not "don't mask" 14044 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) { 14045 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14046 glnvg__resetFBOClipTextureCache(gl); 14047 } 14048 break; 14049 case GLMaskState.Uninitialized: 14050 // if last FBO texture was uninitialized, it means that nothing was changed, 14051 // so we can keep using cached FBO 14052 break; 14053 case GLMaskState.Initialized: 14054 // if last FBO was initialized, it means that something was definitely changed 14055 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14056 glnvg__resetFBOClipTextureCache(gl); 14057 break; 14058 case GLMaskState.JustCleared: assert(0, "NanoVega: internal FBO stack error"); 14059 } 14060 break; 14061 case GLNVG_RESETCLIP: 14062 // mark current mask as "don't mask" 14063 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]); } 14064 if (gl.msp > 0) { 14065 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) { 14066 gl.maskStack.ptr[gl.msp-1] = GLMaskState.DontMask; 14067 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14068 glnvg__resetFBOClipTextureCache(gl); 14069 } 14070 } 14071 break; 14072 case GLNVG_CLIP_DDUMP_ON: 14073 version(nanovega_debug_clipping) nanovegaClipDebugDump = true; 14074 break; 14075 case GLNVG_CLIP_DDUMP_OFF: 14076 version(nanovega_debug_clipping) nanovegaClipDebugDump = false; 14077 break; 14078 case GLNVG_NONE: break; 14079 default: 14080 { 14081 import core.stdc.stdio; stderr.fprintf("NanoVega FATAL: invalid command in OpenGL backend: %d\n", call.type); 14082 } 14083 assert(0, "NanoVega: invalid command in OpenGL backend (fatal internal error)"); 14084 } 14085 // and free texture, why not 14086 glnvg__deleteTexture(gl, call.image); 14087 } 14088 14089 glDisableVertexAttribArray(0); 14090 glDisableVertexAttribArray(1); 14091 glDisable(GL_CULL_FACE); 14092 glBindBuffer(GL_ARRAY_BUFFER, 0); 14093 glUseProgram(0); 14094 glnvg__bindTexture(gl, 0); 14095 } 14096 14097 // this will do all necessary cleanup 14098 glnvg__renderCancelInternal(gl, false); // no need to clear textures 14099 } 14100 14101 int glnvg__maxVertCount (const(NVGpath)* paths, int npaths) nothrow @trusted @nogc { 14102 int count = 0; 14103 foreach (int i; 0..npaths) { 14104 count += paths[i].nfill; 14105 count += paths[i].nstroke; 14106 } 14107 return count; 14108 } 14109 14110 GLNVGcall* glnvg__allocCall (GLNVGcontext* gl) nothrow @trusted @nogc { 14111 GLNVGcall* ret = null; 14112 if (gl.ncalls+1 > gl.ccalls) { 14113 GLNVGcall* calls; 14114 int ccalls = glnvg__maxi(gl.ncalls+1, 128)+gl.ccalls/2; // 1.5x Overallocate 14115 calls = cast(GLNVGcall*)realloc(gl.calls, GLNVGcall.sizeof*ccalls); 14116 if (calls is null) return null; 14117 gl.calls = calls; 14118 gl.ccalls = ccalls; 14119 } 14120 ret = &gl.calls[gl.ncalls++]; 14121 memset(ret, 0, GLNVGcall.sizeof); 14122 return ret; 14123 } 14124 14125 int glnvg__allocPaths (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14126 int ret = 0; 14127 if (gl.npaths+n > gl.cpaths) { 14128 GLNVGpath* paths; 14129 int cpaths = glnvg__maxi(gl.npaths+n, 128)+gl.cpaths/2; // 1.5x Overallocate 14130 paths = cast(GLNVGpath*)realloc(gl.paths, GLNVGpath.sizeof*cpaths); 14131 if (paths is null) return -1; 14132 gl.paths = paths; 14133 gl.cpaths = cpaths; 14134 } 14135 ret = gl.npaths; 14136 gl.npaths += n; 14137 return ret; 14138 } 14139 14140 int glnvg__allocVerts (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14141 int ret = 0; 14142 if (gl.nverts+n > gl.cverts) { 14143 NVGVertex* verts; 14144 int cverts = glnvg__maxi(gl.nverts+n, 4096)+gl.cverts/2; // 1.5x Overallocate 14145 verts = cast(NVGVertex*)realloc(gl.verts, NVGVertex.sizeof*cverts); 14146 if (verts is null) return -1; 14147 gl.verts = verts; 14148 gl.cverts = cverts; 14149 } 14150 ret = gl.nverts; 14151 gl.nverts += n; 14152 return ret; 14153 } 14154 14155 int glnvg__allocFragUniforms (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14156 int ret = 0, structSize = gl.fragSize; 14157 if (gl.nuniforms+n > gl.cuniforms) { 14158 ubyte* uniforms; 14159 int cuniforms = glnvg__maxi(gl.nuniforms+n, 128)+gl.cuniforms/2; // 1.5x Overallocate 14160 uniforms = cast(ubyte*)realloc(gl.uniforms, structSize*cuniforms); 14161 if (uniforms is null) return -1; 14162 gl.uniforms = uniforms; 14163 gl.cuniforms = cuniforms; 14164 } 14165 ret = gl.nuniforms*structSize; 14166 gl.nuniforms += n; 14167 return ret; 14168 } 14169 14170 GLNVGfragUniforms* nvg__fragUniformPtr (GLNVGcontext* gl, int i) nothrow @trusted @nogc { 14171 return cast(GLNVGfragUniforms*)&gl.uniforms[i]; 14172 } 14173 14174 void glnvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc { 14175 vtx.x = x; 14176 vtx.y = y; 14177 vtx.u = u; 14178 vtx.v = v; 14179 } 14180 14181 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 { 14182 if (npaths < 1) return; 14183 14184 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14185 GLNVGcall* call = glnvg__allocCall(gl); 14186 NVGVertex* quad; 14187 GLNVGfragUniforms* frag; 14188 int maxverts, offset; 14189 14190 if (call is null) return; 14191 14192 call.type = GLNVG_FILL; 14193 call.evenOdd = evenOdd; 14194 call.clipmode = clipmode; 14195 //if (clipmode != NVGClipMode.None) { import core.stdc.stdio; printf("CLIP!\n"); } 14196 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14197 call.triangleCount = 4; 14198 call.pathOffset = glnvg__allocPaths(gl, npaths); 14199 if (call.pathOffset == -1) goto error; 14200 call.pathCount = npaths; 14201 call.image = paint.image.id; 14202 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14203 14204 if (npaths == 1 && paths[0].convex) { 14205 call.type = GLNVG_CONVEXFILL; 14206 call.triangleCount = 0; // Bounding box fill quad not needed for convex fill 14207 } 14208 14209 // Allocate vertices for all the paths. 14210 maxverts = glnvg__maxVertCount(paths, npaths)+call.triangleCount; 14211 offset = glnvg__allocVerts(gl, maxverts); 14212 if (offset == -1) goto error; 14213 14214 foreach (int i; 0..npaths) { 14215 GLNVGpath* copy = &gl.paths[call.pathOffset+i]; 14216 const(NVGpath)* path = &paths[i]; 14217 memset(copy, 0, GLNVGpath.sizeof); 14218 if (path.nfill > 0) { 14219 copy.fillOffset = offset; 14220 copy.fillCount = path.nfill; 14221 memcpy(&gl.verts[offset], path.fill, NVGVertex.sizeof*path.nfill); 14222 offset += path.nfill; 14223 } 14224 if (path.nstroke > 0) { 14225 copy.strokeOffset = offset; 14226 copy.strokeCount = path.nstroke; 14227 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke); 14228 offset += path.nstroke; 14229 } 14230 } 14231 14232 // Setup uniforms for draw calls 14233 if (call.type == GLNVG_FILL) { 14234 import core.stdc.string : memcpy; 14235 // Quad 14236 call.triangleOffset = offset; 14237 quad = &gl.verts[call.triangleOffset]; 14238 glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f); 14239 glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f); 14240 glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f); 14241 glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f); 14242 // Get uniform 14243 call.uniformOffset = glnvg__allocFragUniforms(gl, 2); 14244 if (call.uniformOffset == -1) goto error; 14245 // Simple shader for stencil 14246 frag = nvg__fragUniformPtr(gl, call.uniformOffset); 14247 memset(frag, 0, (*frag).sizeof); 14248 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f); 14249 memcpy(nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), frag, (*frag).sizeof); 14250 frag.strokeThr = -1.0f; 14251 frag.type = NSVG_SHADER_SIMPLE; 14252 // Fill shader 14253 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, fringe, fringe, -1.0f); 14254 } else { 14255 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14256 if (call.uniformOffset == -1) goto error; 14257 // Fill shader 14258 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f); 14259 } 14260 14261 return; 14262 14263 error: 14264 // We get here if call alloc was ok, but something else is not. 14265 // Roll back the last call to prevent drawing it. 14266 if (gl.ncalls > 0) --gl.ncalls; 14267 } 14268 14269 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 { 14270 if (npaths < 1) return; 14271 14272 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14273 GLNVGcall* call = glnvg__allocCall(gl); 14274 int maxverts, offset; 14275 14276 if (call is null) return; 14277 14278 call.type = GLNVG_STROKE; 14279 call.clipmode = clipmode; 14280 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14281 call.pathOffset = glnvg__allocPaths(gl, npaths); 14282 if (call.pathOffset == -1) goto error; 14283 call.pathCount = npaths; 14284 call.image = paint.image.id; 14285 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14286 14287 // Allocate vertices for all the paths. 14288 maxverts = glnvg__maxVertCount(paths, npaths); 14289 offset = glnvg__allocVerts(gl, maxverts); 14290 if (offset == -1) goto error; 14291 14292 foreach (int i; 0..npaths) { 14293 GLNVGpath* copy = &gl.paths[call.pathOffset+i]; 14294 const(NVGpath)* path = &paths[i]; 14295 memset(copy, 0, GLNVGpath.sizeof); 14296 if (path.nstroke) { 14297 copy.strokeOffset = offset; 14298 copy.strokeCount = path.nstroke; 14299 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke); 14300 offset += path.nstroke; 14301 } 14302 } 14303 14304 if (gl.flags&NVGContextFlag.StencilStrokes) { 14305 // Fill shader 14306 call.uniformOffset = glnvg__allocFragUniforms(gl, 2); 14307 if (call.uniformOffset == -1) goto error; 14308 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); 14309 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f); 14310 } else { 14311 // Fill shader 14312 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14313 if (call.uniformOffset == -1) goto error; 14314 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); 14315 } 14316 14317 return; 14318 14319 error: 14320 // We get here if call alloc was ok, but something else is not. 14321 // Roll back the last call to prevent drawing it. 14322 if (gl.ncalls > 0) --gl.ncalls; 14323 } 14324 14325 void glnvg__renderTriangles (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc { 14326 if (nverts < 1) return; 14327 14328 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14329 GLNVGcall* call = glnvg__allocCall(gl); 14330 GLNVGfragUniforms* frag; 14331 14332 if (call is null) return; 14333 14334 call.type = GLNVG_TRIANGLES; 14335 call.clipmode = clipmode; 14336 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14337 call.image = paint.image.id; 14338 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14339 14340 // Allocate vertices for all the paths. 14341 call.triangleOffset = glnvg__allocVerts(gl, nverts); 14342 if (call.triangleOffset == -1) goto error; 14343 call.triangleCount = nverts; 14344 14345 memcpy(&gl.verts[call.triangleOffset], verts, NVGVertex.sizeof*nverts); 14346 14347 // Fill shader 14348 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14349 if (call.uniformOffset == -1) goto error; 14350 frag = nvg__fragUniformPtr(gl, call.uniformOffset); 14351 glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f); 14352 frag.type = NSVG_SHADER_IMG; 14353 14354 return; 14355 14356 error: 14357 // We get here if call alloc was ok, but something else is not. 14358 // Roll back the last call to prevent drawing it. 14359 if (gl.ncalls > 0) --gl.ncalls; 14360 } 14361 14362 void glnvg__renderDelete (void* uptr) nothrow @trusted @nogc { 14363 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14364 if (gl is null) return; 14365 14366 glnvg__killFBOs(gl); 14367 glnvg__deleteShader(&gl.shader); 14368 glnvg__deleteShader(&gl.shaderFillFBO); 14369 glnvg__deleteShader(&gl.shaderCopyFBO); 14370 14371 if (gl.vertBuf != 0) glDeleteBuffers(1, &gl.vertBuf); 14372 14373 foreach (ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) { 14374 if (tex.id != 0 && (tex.flags&NVGImageFlag.NoDelete) == 0) { 14375 assert(tex.tex != 0); 14376 glDeleteTextures(1, &tex.tex); 14377 } 14378 } 14379 free(gl.textures); 14380 14381 free(gl.paths); 14382 free(gl.verts); 14383 free(gl.uniforms); 14384 free(gl.calls); 14385 14386 free(gl); 14387 } 14388 14389 14390 /** Creates NanoVega contexts for OpenGL2+. 14391 * 14392 * Specify creation flags as additional arguments, like this: 14393 * `nvgCreateContext(NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes);` 14394 * 14395 * If you won't specify any flags, defaults will be used: 14396 * `[NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes]`. 14397 * 14398 * Group: context_management 14399 */ 14400 public NVGContext nvgCreateContext (const(NVGContextFlag)[] flagList...) nothrow @trusted @nogc { 14401 version(aliced) { 14402 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA; 14403 } else { 14404 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes; 14405 } 14406 uint flags = 0; 14407 if (flagList.length != 0) { 14408 foreach (immutable flg; flagList) flags |= (flg != NVGContextFlag.Default ? flg : DefaultFlags); 14409 } else { 14410 flags = DefaultFlags; 14411 } 14412 NVGparams params = void; 14413 NVGContext ctx = null; 14414 version(nanovg_builtin_opengl_bindings) nanovgInitOpenGL(); // why not? 14415 version(nanovg_bindbc_opengl_bindings) nanovgInitOpenGL(); 14416 GLNVGcontext* gl = cast(GLNVGcontext*)malloc(GLNVGcontext.sizeof); 14417 if (gl is null) goto error; 14418 memset(gl, 0, GLNVGcontext.sizeof); 14419 14420 memset(¶ms, 0, params.sizeof); 14421 params.renderCreate = &glnvg__renderCreate; 14422 params.renderCreateTexture = &glnvg__renderCreateTexture; 14423 params.renderTextureIncRef = &glnvg__renderTextureIncRef; 14424 params.renderDeleteTexture = &glnvg__renderDeleteTexture; 14425 params.renderUpdateTexture = &glnvg__renderUpdateTexture; 14426 params.renderGetTextureSize = &glnvg__renderGetTextureSize; 14427 params.renderViewport = &glnvg__renderViewport; 14428 params.renderCancel = &glnvg__renderCancel; 14429 params.renderFlush = &glnvg__renderFlush; 14430 params.renderPushClip = &glnvg__renderPushClip; 14431 params.renderPopClip = &glnvg__renderPopClip; 14432 params.renderResetClip = &glnvg__renderResetClip; 14433 params.renderFill = &glnvg__renderFill; 14434 params.renderStroke = &glnvg__renderStroke; 14435 params.renderTriangles = &glnvg__renderTriangles; 14436 params.renderSetAffine = &glnvg__renderSetAffine; 14437 params.renderDelete = &glnvg__renderDelete; 14438 params.userPtr = gl; 14439 params.edgeAntiAlias = (flags&NVGContextFlag.Antialias ? true : false); 14440 if (flags&(NVGContextFlag.FontAA|NVGContextFlag.FontNoAA)) { 14441 params.fontAA = (flags&NVGContextFlag.FontNoAA ? NVG_INVERT_FONT_AA : !NVG_INVERT_FONT_AA); 14442 } else { 14443 params.fontAA = NVG_INVERT_FONT_AA; 14444 } 14445 14446 gl.flags = flags; 14447 gl.freetexid = -1; 14448 14449 ctx = createInternal(¶ms); 14450 if (ctx is null) goto error; 14451 14452 static if (__VERSION__ < 2076) { 14453 DGNoThrowNoGC(() { import core.thread; gl.mainTID = Thread.getThis.id; })(); 14454 } else { 14455 try { import core.thread; gl.mainTID = Thread.getThis.id; } catch (Exception e) {} 14456 } 14457 14458 return ctx; 14459 14460 error: 14461 // 'gl' is freed by nvgDeleteInternal. 14462 if (ctx !is null) ctx.deleteInternal(); 14463 return null; 14464 } 14465 14466 /// Using OpenGL texture id creates GLNVGtexture and return its id. 14467 /// Group: images 14468 public int glCreateImageFromHandleGL2 (NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc { 14469 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr; 14470 GLNVGtexture* tex = glnvg__allocTexture(gl); 14471 14472 if (tex is null) return 0; 14473 14474 tex.type = NVGtexture.RGBA; 14475 tex.tex = textureId; 14476 tex.flags = imageFlags; 14477 tex.width = w; 14478 tex.height = h; 14479 14480 return tex.id; 14481 } 14482 14483 /// Create NVGImage from OpenGL texture id. 14484 /// Group: images 14485 public NVGImage glCreateImageFromOpenGLTexture(NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc { 14486 auto id = glCreateImageFromHandleGL2(ctx, textureId, w, h, imageFlags); 14487 14488 NVGImage res; 14489 if (id > 0) { 14490 res.id = id; 14491 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); } 14492 res.ctx = ctx; 14493 ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount 14494 } 14495 return res; 14496 } 14497 14498 /// Returns OpenGL texture id for NanoVega image. 14499 /// Group: images 14500 public GLuint glImageHandleGL2 (NVGContext ctx, int image) nothrow @trusted @nogc { 14501 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr; 14502 GLNVGtexture* tex = glnvg__findTexture(gl, image); 14503 return tex.tex; 14504 } 14505 14506 14507 // ////////////////////////////////////////////////////////////////////////// // 14508 private: 14509 14510 static if (NanoVegaHasFontConfig) { 14511 version(nanovg_builtin_fontconfig_bindings) { 14512 pragma(lib, "fontconfig"); 14513 14514 private extern(C) nothrow @trusted @nogc { 14515 enum FC_FILE = "file"; /* String */ 14516 alias FcBool = int; 14517 alias FcChar8 = char; 14518 struct FcConfig; 14519 struct FcPattern; 14520 alias FcMatchKind = int; 14521 enum : FcMatchKind { 14522 FcMatchPattern, 14523 FcMatchFont, 14524 FcMatchScan 14525 } 14526 alias FcResult = int; 14527 enum : FcResult { 14528 FcResultMatch, 14529 FcResultNoMatch, 14530 FcResultTypeMismatch, 14531 FcResultNoId, 14532 FcResultOutOfMemory 14533 } 14534 FcBool FcInit (); 14535 FcBool FcConfigSubstituteWithPat (FcConfig* config, FcPattern* p, FcPattern* p_pat, FcMatchKind kind); 14536 void FcDefaultSubstitute (FcPattern* pattern); 14537 FcBool FcConfigSubstitute (FcConfig* config, FcPattern* p, FcMatchKind kind); 14538 FcPattern* FcFontMatch (FcConfig* config, FcPattern* p, FcResult* result); 14539 FcPattern* FcNameParse (const(FcChar8)* name); 14540 void FcPatternDestroy (FcPattern* p); 14541 FcResult FcPatternGetString (const(FcPattern)* p, const(char)* object, int n, FcChar8** s); 14542 } 14543 } 14544 14545 __gshared bool fontconfigAvailable = false; 14546 // initialize fontconfig 14547 shared static this () { 14548 if (FcInit()) { 14549 fontconfigAvailable = true; 14550 } else { 14551 import core.stdc.stdio : stderr, fprintf; 14552 stderr.fprintf("***NanoVega WARNING: cannot init fontconfig!\n"); 14553 } 14554 } 14555 } 14556 14557 14558 // ////////////////////////////////////////////////////////////////////////// // 14559 public enum BaphometDims = 512.0f; // baphomet icon is 512x512 ([0..511]) 14560 14561 private static immutable ubyte[7641] baphometPath = [ 14562 0x01,0x04,0x06,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x08,0xa0,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43, 14563 0x00,0x80,0xff,0x43,0xa2,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x08,0x00,0x80,0xff, 14564 0x43,0x7a,0x89,0xe5,0x42,0xa0,0x1d,0xc6,0x43,0x00,0x00,0x00,0x00,0x30,0x89,0x7f,0x43,0x00,0x00,0x00, 14565 0x00,0x08,0x7a,0x89,0xe5,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7a,0x89,0xe5,0x42,0x00,0x00, 14566 0x00,0x00,0x30,0x89,0x7f,0x43,0x08,0x00,0x00,0x00,0x00,0xa2,0x1d,0xc6,0x43,0x7a,0x89,0xe5,0x42,0x00, 14567 0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x09,0x06,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd, 14568 0x43,0x08,0x16,0x68,0xb3,0x43,0x72,0x87,0xdd,0x43,0x71,0x87,0xdd,0x43,0x17,0x68,0xb3,0x43,0x71,0x87, 14569 0xdd,0x43,0x30,0x89,0x7f,0x43,0x08,0x71,0x87,0xdd,0x43,0xd2,0x2f,0x18,0x43,0x16,0x68,0xb3,0x43,0x35, 14570 0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x35,0xe2,0x87,0x42,0x08,0xd1,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42, 14571 0x35,0xe2,0x87,0x42,0xd2,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x08,0x35,0xe2,0x87, 14572 0x42,0x17,0x68,0xb3,0x43,0xd1,0x2f,0x18,0x43,0x72,0x87,0xdd,0x43,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd, 14573 0x43,0x09,0x06,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x07,0xa4,0x3f,0x7f,0x43,0x0b,0x86,0xdc,0x43, 14574 0x07,0x6c,0xb9,0xb2,0x43,0xe8,0xd1,0xca,0x42,0x07,0x6e,0x4d,0xa0,0x42,0xa9,0x10,0x9c,0x43,0x07,0xb7, 14575 0x40,0xd7,0x43,0xa9,0x10,0x9c,0x43,0x07,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x09,0x06,0x98,0x42, 14576 0x74,0x43,0xb1,0x8d,0x68,0x43,0x08,0xd7,0x24,0x79,0x43,0xba,0x83,0x6e,0x43,0xa9,0x16,0x7c,0x43,0x56, 14577 0xa1,0x76,0x43,0x74,0x2a,0x7d,0x43,0x44,0x73,0x80,0x43,0x08,0x55,0xd1,0x7e,0x43,0xe3,0xea,0x76,0x43, 14578 0xbc,0x18,0x81,0x43,0x7f,0xa8,0x6e,0x43,0x8f,0x0a,0x84,0x43,0x02,0xfc,0x68,0x43,0x09,0x06,0x92,0x29, 14579 0x8d,0x43,0x73,0xc3,0x67,0x43,0x08,0xa4,0xd9,0x8e,0x43,0xf2,0xa6,0x7a,0x43,0x8f,0x22,0x88,0x43,0x75, 14580 0x2a,0x7d,0x43,0x42,0x7f,0x82,0x43,0x08,0xc8,0x88,0x43,0x09,0x06,0xc1,0x79,0x74,0x43,0x50,0x64,0x89, 14581 0x43,0x08,0x68,0x2d,0x72,0x43,0xee,0x21,0x81,0x43,0xcd,0x97,0x55,0x43,0xe6,0xf1,0x7b,0x43,0x91,0xec, 14582 0x5d,0x43,0xa8,0xc7,0x6a,0x43,0x09,0x06,0xfa,0xa5,0x52,0x43,0x60,0x97,0x7c,0x43,0x08,0x19,0xff,0x50, 14583 0x43,0xe9,0x6e,0x8a,0x43,0xb0,0xbd,0x70,0x43,0x4c,0x51,0x82,0x43,0x04,0xeb,0x69,0x43,0x66,0x0f,0x8e, 14584 0x43,0x09,0x06,0x17,0xbf,0x71,0x43,0x2c,0x58,0x94,0x43,0x08,0x1c,0x96,0x6e,0x43,0x61,0x68,0x99,0x43, 14585 0x2d,0x3a,0x6e,0x43,0xc8,0x81,0x9e,0x43,0xb7,0x9b,0x72,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0x30,0xdb, 14586 0x82,0x43,0xdb,0xe9,0x93,0x43,0x08,0x11,0x82,0x84,0x43,0x61,0x68,0x99,0x43,0xe8,0x4a,0x84,0x43,0x8e, 14587 0xa6,0x9e,0x43,0x42,0x7f,0x82,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0xc4,0x02,0x85,0x43,0xd1,0x0b,0x92, 14588 0x43,0x08,0xd6,0xb2,0x86,0x43,0x34,0x1e,0x92,0x43,0x4f,0x58,0x87,0x43,0xa4,0xf1,0x92,0x43,0x03,0xd9, 14589 0x87,0x43,0x7b,0xc6,0x94,0x43,0x09,0x06,0x87,0x3e,0x64,0x43,0x31,0x3b,0x93,0x43,0x08,0x3b,0xbf,0x64, 14590 0x43,0x6f,0xf9,0x91,0x43,0x96,0x0b,0x67,0x43,0xc5,0x4a,0x91,0x43,0xcf,0xfe,0x6a,0x43,0x31,0x2f,0x91, 14591 0x43,0x09,0x06,0x16,0x74,0xb5,0x43,0x08,0xec,0x8e,0x43,0x08,0x1b,0x4b,0xb2,0x43,0xee,0x5d,0x8b,0x43, 14592 0x48,0x4d,0xad,0x43,0x12,0xa6,0x8a,0x43,0xf3,0xd7,0xa7,0x43,0x74,0xb8,0x8a,0x43,0x08,0x8c,0xb2,0xa0, 14593 0x43,0xcd,0xf8,0x8a,0x43,0x68,0x46,0x9b,0x43,0x79,0x8f,0x87,0x43,0x49,0xc9,0x96,0x43,0xe9,0x3e,0x82, 14594 0x43,0x08,0x60,0x5c,0x97,0x43,0xa1,0xde,0x8b,0x43,0x4e,0xa0,0x93,0x43,0x31,0x3b,0x93,0x43,0x9f,0xea, 14595 0x8d,0x43,0x27,0x8d,0x99,0x43,0x08,0x07,0xe0,0x8c,0x43,0x06,0x34,0x9b,0x43,0x38,0xe9,0x8c,0x43,0x46, 14596 0x0a,0x9e,0x43,0x3d,0xcc,0x8b,0x43,0xb2,0x06,0xa2,0x43,0x08,0xf1,0x40,0x8a,0x43,0xb0,0x12,0xa4,0x43, 14597 0x39,0xd1,0x88,0x43,0x76,0x43,0xa6,0x43,0xfa,0x06,0x88,0x43,0xa4,0x75,0xa9,0x43,0x08,0x19,0x6c,0x88, 14598 0x43,0x9f,0x9e,0xac,0x43,0x66,0xeb,0x87,0x43,0x44,0x76,0xb0,0x43,0x6b,0xce,0x86,0x43,0x3b,0xbc,0xb4, 14599 0x43,0x08,0xa9,0x8c,0x85,0x43,0x06,0xd0,0xb5,0x43,0xfa,0xee,0x83,0x43,0x74,0xa3,0xb6,0x43,0x3d,0x90, 14600 0x81,0x43,0x31,0xf6,0xb6,0x43,0x08,0x9d,0x61,0x7d,0x43,0xee,0x48,0xb7,0x43,0x3b,0x1f,0x75,0x43,0xcf, 14601 0xe3,0xb6,0x43,0xee,0x6f,0x6d,0x43,0x68,0xe2,0xb5,0x43,0x08,0xd4,0xed,0x6b,0x43,0x87,0x2f,0xb2,0x43, 14602 0x0e,0xc9,0x6b,0x43,0xa7,0x7c,0xae,0x43,0x98,0xfa,0x67,0x43,0xab,0x53,0xab,0x43,0x08,0x25,0x2c,0x64, 14603 0x43,0x33,0xa2,0xa8,0x43,0x40,0x96,0x61,0x43,0xc3,0xc2,0xa5,0x43,0x64,0xde,0x60,0x43,0xfa,0xa2,0xa2, 14604 0x43,0x08,0xb0,0x5d,0x60,0x43,0x06,0x4c,0x9f,0x43,0x9a,0xca,0x5f,0x43,0x38,0x3d,0x9b,0x43,0x3b,0x8f, 14605 0x5c,0x43,0x85,0xb0,0x98,0x43,0x08,0x42,0x36,0x51,0x43,0x3d,0xf0,0x91,0x43,0xcd,0x4f,0x49,0x43,0xdb, 14606 0xb9,0x8b,0x43,0xe0,0xdb,0x44,0x43,0x42,0x8b,0x84,0x43,0x08,0x7e,0xc9,0x44,0x43,0x8a,0x57,0x8d,0x43, 14607 0xbc,0x6c,0x0f,0x43,0x23,0x62,0x8e,0x43,0xf5,0x17,0x07,0x43,0xc5,0x3e,0x8f,0x43,0x09,0x06,0xe0,0xea, 14608 0x76,0x43,0xab,0xef,0xc5,0x43,0x08,0x12,0x00,0x79,0x43,0xab,0xcb,0xbf,0x43,0x79,0xb9,0x6d,0x43,0x7e, 14609 0x8d,0xba,0x43,0xee,0x6f,0x6d,0x43,0x98,0xeb,0xb5,0x43,0x08,0xe0,0x02,0x7b,0x43,0x5f,0x1c,0xb8,0x43, 14610 0x85,0x2c,0x82,0x43,0xe9,0x65,0xb8,0x43,0xd6,0xb2,0x86,0x43,0xc6,0x05,0xb5,0x43,0x08,0x03,0xcd,0x85, 14611 0x43,0x5a,0x39,0xb9,0x43,0xe4,0x4f,0x81,0x43,0xdb,0xd4,0xbf,0x43,0xdf,0x6c,0x82,0x43,0xbc,0x93,0xc5, 14612 0x43,0x09,0x06,0xf0,0xd0,0x22,0x43,0x5d,0x19,0x08,0x43,0x08,0xbc,0xab,0x49,0x43,0x4a,0x35,0x29,0x43, 14613 0xcb,0xf7,0x65,0x43,0xce,0x37,0x45,0x43,0x0e,0x99,0x63,0x43,0x67,0xc6,0x5c,0x43,0x09,0x06,0x05,0x94, 14614 0xab,0x43,0xc2,0x13,0x04,0x43,0x08,0x9f,0x26,0x98,0x43,0x11,0x42,0x25,0x43,0x97,0x00,0x8a,0x43,0x32, 14615 0x32,0x41,0x43,0xf5,0x2f,0x8b,0x43,0xc7,0xc0,0x58,0x43,0x09,0x06,0x8f,0x85,0x48,0x43,0xe0,0xa8,0x8c, 14616 0x43,0x08,0x55,0xaa,0x48,0x43,0xe0,0xa8,0x8c,0x43,0x6b,0x3d,0x49,0x43,0xc1,0x43,0x8c,0x43,0x31,0x62, 14617 0x49,0x43,0xc1,0x43,0x8c,0x43,0x08,0x2f,0xe3,0x2f,0x43,0xad,0xe7,0x98,0x43,0xff,0x0d,0x0d,0x43,0xad, 14618 0xf3,0x9a,0x43,0xf0,0xaf,0xcc,0x42,0x74,0x00,0x97,0x43,0x08,0xbb,0xa2,0xf7,0x42,0x93,0x4d,0x93,0x43, 14619 0x5e,0x19,0x08,0x43,0x5a,0x2a,0x87,0x43,0x23,0x6e,0x10,0x43,0x42,0x97,0x86,0x43,0x08,0xca,0xe8,0x33, 14620 0x43,0x1b,0x3c,0x80,0x43,0x80,0xe8,0x4d,0x43,0xda,0xf4,0x70,0x43,0xae,0x0e,0x4f,0x43,0x2b,0x1b,0x65, 14621 0x43,0x08,0x66,0x96,0x54,0x43,0xa3,0xe1,0x3b,0x43,0x4e,0xc4,0x19,0x43,0xa0,0x1a,0x16,0x43,0x10,0xe2, 14622 0x14,0x43,0x26,0x14,0xe0,0x42,0x08,0x5c,0x91,0x1c,0x43,0xcb,0x27,0xee,0x42,0xa9,0x40,0x24,0x43,0x71, 14623 0x3b,0xfc,0x42,0xf3,0xef,0x2b,0x43,0x8b,0x27,0x05,0x43,0x08,0xe2,0x4b,0x2c,0x43,0x48,0x86,0x07,0x43, 14624 0x79,0x62,0x2f,0x43,0x05,0xe5,0x09,0x43,0x55,0x32,0x34,0x43,0xa0,0xd2,0x09,0x43,0x08,0x74,0xa3,0x36, 14625 0x43,0x3a,0xd1,0x08,0x43,0x7e,0x81,0x38,0x43,0x09,0xd4,0x0a,0x43,0x0d,0xba,0x39,0x43,0xa0,0xea,0x0d, 14626 0x43,0x08,0x6f,0xe4,0x3d,0x43,0x43,0xc7,0x0e,0x43,0xd6,0xe5,0x3e,0x43,0xc4,0x4a,0x11,0x43,0x55,0x7a, 14627 0x40,0x43,0x59,0x72,0x13,0x43,0x08,0x55,0x92,0x44,0x43,0xbf,0x73,0x14,0x43,0x23,0x95,0x46,0x43,0xa5, 14628 0x09,0x17,0x43,0xe0,0xf3,0x48,0x43,0xfe,0x55,0x19,0x43,0x08,0xcd,0x4f,0x49,0x43,0xaa,0x10,0x1c,0x43, 14629 0x61,0x77,0x4b,0x43,0xfe,0x6d,0x1d,0x43,0x80,0xe8,0x4d,0x43,0x2b,0x94,0x1e,0x43,0x08,0x58,0xc9,0x51, 14630 0x43,0x41,0x27,0x1f,0x43,0x9b,0x82,0x53,0x43,0x35,0x72,0x20,0x43,0x53,0xf2,0x54,0x43,0x88,0xcf,0x21, 14631 0x43,0x08,0x7b,0x29,0x55,0x43,0xe8,0x0a,0x25,0x43,0xb2,0x2d,0x58,0x43,0xef,0xe8,0x26,0x43,0x9b,0xb2, 14632 0x5b,0x43,0xd0,0x8f,0x28,0x43,0x08,0x5f,0xef,0x5f,0x43,0xeb,0x11,0x2a,0x43,0xfd,0xdc,0x5f,0x43,0x6e, 14633 0x95,0x2c,0x43,0x3b,0xa7,0x60,0x43,0x2b,0xf4,0x2e,0x43,0x08,0x06,0xbb,0x61,0x43,0xfd,0xe5,0x31,0x43, 14634 0xe7,0x61,0x63,0x43,0xef,0x30,0x33,0x43,0x53,0x52,0x65,0x43,0xa3,0xb1,0x33,0x43,0x08,0x12,0xa0,0x68, 14635 0x43,0x7f,0x69,0x34,0x43,0x40,0xc6,0x69,0x43,0x64,0xff,0x36,0x43,0x7e,0x90,0x6a,0x43,0x71,0xcc,0x39, 14636 0x43,0x08,0xbc,0x5a,0x6b,0x43,0x51,0x73,0x3b,0x43,0xc1,0x49,0x6c,0x43,0xa5,0xd0,0x3c,0x43,0xe0,0xba, 14637 0x6e,0x43,0xb8,0x74,0x3c,0x43,0x08,0x6b,0x1c,0x73,0x43,0x13,0xc1,0x3e,0x43,0x40,0xf6,0x71,0x43,0xce, 14638 0x1f,0x41,0x43,0x55,0x89,0x72,0x43,0x8d,0x7e,0x43,0x43,0x08,0x68,0x2d,0x72,0x43,0x89,0xae,0x4b,0x43, 14639 0xc1,0x79,0x74,0x43,0xcb,0x78,0x4c,0x43,0x55,0xa1,0x76,0x43,0x5b,0xb1,0x4d,0x43,0x08,0xa2,0x38,0x7a, 14640 0x43,0xd1,0x56,0x4e,0x43,0x85,0xb6,0x78,0x43,0xb1,0x15,0x54,0x43,0x83,0xc7,0x77,0x43,0x89,0x0e,0x5c, 14641 0x43,0x08,0xcf,0x46,0x77,0x43,0x0f,0x81,0x5f,0x43,0x1a,0xde,0x7a,0x43,0xce,0xc7,0x5d,0x43,0x42,0x73, 14642 0x80,0x43,0x99,0xc3,0x5a,0x43,0x08,0x85,0x2c,0x82,0x43,0xf6,0xe6,0x59,0x43,0x81,0x3d,0x81,0x43,0x16, 14643 0x10,0x50,0x43,0xd6,0x8e,0x80,0x43,0x5b,0x99,0x49,0x43,0x08,0xc4,0xea,0x80,0x43,0x22,0x95,0x46,0x43, 14644 0xfa,0xe2,0x81,0x43,0xda,0xec,0x43,0x43,0x78,0x77,0x83,0x43,0xe4,0xb2,0x41,0x43,0x08,0x8a,0x27,0x85, 14645 0x43,0x86,0x77,0x3e,0x43,0x0c,0x9f,0x85,0x43,0x07,0xf4,0x3b,0x43,0x8f,0x16,0x86,0x43,0xe6,0x82,0x39, 14646 0x43,0x08,0x85,0x44,0x86,0x43,0x37,0xd9,0x35,0x43,0x1e,0x4f,0x87,0x43,0xe1,0x7b,0x34,0x43,0xdf,0x90, 14647 0x88,0x43,0xb6,0x55,0x33,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0xe5,0x31,0x43,0xfa,0x12,0x8a,0x43,0xbf, 14648 0x03,0x2d,0x43,0x19,0x78,0x8a,0x43,0x45,0x5e,0x2c,0x43,0x08,0x03,0xf1,0x8b,0x43,0xac,0x47,0x29,0x43, 14649 0x2f,0x17,0x8d,0x43,0x45,0x46,0x28,0x43,0xc8,0x21,0x8e,0x43,0x30,0xb3,0x27,0x43,0x08,0xa9,0xc8,0x8f, 14650 0x43,0xef,0xe8,0x26,0x43,0xbf,0x5b,0x90,0x43,0x5b,0xc1,0x24,0x43,0x10,0xca,0x90,0x43,0xa0,0x62,0x22, 14651 0x43,0x08,0x26,0x5d,0x91,0x43,0xbb,0xcc,0x1f,0x43,0xf0,0x70,0x92,0x43,0x78,0x13,0x1e,0x43,0x77,0xd7, 14652 0x93,0x43,0x73,0x24,0x1d,0x43,0x08,0x65,0x3f,0x96,0x43,0xce,0x58,0x1b,0x43,0xbe,0x7f,0x96,0x43,0xbf, 14653 0x8b,0x18,0x43,0x60,0x5c,0x97,0x43,0xb6,0xad,0x16,0x43,0x08,0xba,0xa8,0x99,0x43,0x78,0xcb,0x11,0x43, 14654 0x49,0xe1,0x9a,0x43,0x78,0xcb,0x11,0x43,0x01,0x51,0x9c,0x43,0x73,0xdc,0x10,0x43,0x08,0x72,0x24,0x9d, 14655 0x43,0xd2,0xff,0x0f,0x43,0x1c,0xd3,0x9d,0x43,0x07,0xec,0x0e,0x43,0xeb,0xc9,0x9d,0x43,0xe8,0x7a,0x0c, 14656 0x43,0x08,0x60,0x80,0x9d,0x43,0xd7,0xbe,0x08,0x43,0x4d,0xe8,0x9f,0x43,0x86,0x50,0x08,0x43,0x25,0xbd, 14657 0xa1,0x43,0x5b,0x2a,0x07,0x43,0x08,0x99,0x7f,0xa3,0x43,0xc9,0xf1,0x05,0x43,0x48,0x1d,0xa5,0x43,0x86, 14658 0x38,0x04,0x43,0x6c,0x71,0xa6,0x43,0x18,0x59,0x01,0x43,0x08,0x32,0x96,0xa6,0x43,0x6e,0x64,0xff,0x42, 14659 0x48,0x29,0xa7,0x43,0xed,0xcf,0xfd,0x42,0x5f,0xbc,0xa7,0x43,0x71,0x3b,0xfc,0x42,0x08,0xf3,0xe3,0xa9, 14660 0x43,0xf7,0x7d,0xf7,0x42,0xd8,0x6d,0xaa,0x43,0x45,0xe5,0xf2,0x42,0x48,0x41,0xab,0x43,0xcb,0x27,0xee, 14661 0x42,0x08,0x24,0xf9,0xab,0x43,0x52,0x6a,0xe9,0x42,0xee,0x0c,0xad,0x43,0x4c,0x8c,0xe7,0x42,0x1b,0x33, 14662 0xae,0x43,0xcc,0xf7,0xe5,0x42,0x08,0xaa,0x6b,0xaf,0x43,0xe8,0x61,0xe3,0x42,0x90,0xf5,0xaf,0x43,0xc9, 14663 0xf0,0xe0,0x42,0xe0,0x63,0xb0,0x43,0xe5,0x5a,0xde,0x42,0x08,0xaa,0x83,0xb3,0x43,0x29,0x2d,0x09,0x43, 14664 0x6a,0xfe,0x8e,0x43,0xb8,0x74,0x3c,0x43,0xd5,0x06,0x95,0x43,0xe6,0x79,0x67,0x43,0x08,0x2f,0x53,0x97, 14665 0x43,0xe9,0xb0,0x74,0x43,0xa8,0x28,0xa0,0x43,0x43,0xfd,0x76,0x43,0x83,0x28,0xad,0x43,0x17,0x59,0x81, 14666 0x43,0x08,0x3d,0xe7,0xbf,0x43,0x4b,0x8d,0x8c,0x43,0xae,0x96,0xba,0x43,0x66,0x27,0x92,0x43,0x15,0xe0, 14667 0xc7,0x43,0x6f,0x11,0x96,0x43,0x08,0x7e,0x5d,0xb2,0x43,0xdb,0x01,0x98,0x43,0x9e,0x56,0xa0,0x43,0x80, 14668 0xc1,0x97,0x43,0x69,0x2e,0x97,0x43,0x31,0x17,0x8d,0x43,0x09,0x06,0xab,0xa7,0x39,0x43,0x67,0x0f,0x0e, 14669 0x43,0x08,0xdb,0xbc,0x3b,0x43,0xe8,0x92,0x10,0x43,0xb5,0x85,0x3b,0x43,0x97,0x3c,0x14,0x43,0xab,0xa7, 14670 0x39,0x43,0x0c,0x0b,0x18,0x43,0x09,0x06,0xca,0x30,0x40,0x43,0x30,0x3b,0x13,0x43,0x08,0x17,0xc8,0x43, 14671 0x43,0xa5,0x09,0x17,0x43,0x7e,0xc9,0x44,0x43,0x1a,0xd8,0x1a,0x43,0x9d,0x22,0x43,0x43,0x8d,0xa6,0x1e, 14672 0x43,0x09,0x06,0xc8,0x78,0x4c,0x43,0xed,0xc9,0x1d,0x43,0x08,0x0b,0x32,0x4e,0x43,0x22,0xce,0x20,0x43, 14673 0x23,0xc5,0x4e,0x43,0x58,0xd2,0x23,0x43,0x0b,0x32,0x4e,0x43,0x2b,0xc4,0x26,0x43,0x09,0x06,0xec,0x08, 14674 0x58,0x43,0xc7,0xb1,0x26,0x43,0x08,0x02,0x9c,0x58,0x43,0xef,0x00,0x2b,0x43,0xd9,0x64,0x58,0x43,0x02, 14675 0xbd,0x2e,0x43,0x10,0x51,0x57,0x43,0x37,0xc1,0x31,0x43,0x09,0x06,0xcb,0xdf,0x61,0x43,0x4a,0x65,0x31, 14676 0x43,0x08,0xbe,0x2a,0x63,0x43,0xbd,0x33,0x35,0x43,0x32,0xe1,0x62,0x43,0x56,0x4a,0x38,0x43,0xde,0x83, 14677 0x61,0x43,0x3c,0xe0,0x3a,0x43,0x09,0x06,0x1c,0x7e,0x6a,0x43,0x5b,0x39,0x39,0x43,0x08,0x31,0x11,0x6b, 14678 0x43,0x0c,0xd2,0x3d,0x43,0x1c,0x7e,0x6a,0x43,0x13,0xd9,0x42,0x43,0xd9,0xc4,0x68,0x43,0xcb,0x60,0x48, 14679 0x43,0x09,0x06,0xe5,0xc1,0x73,0x43,0x16,0xf8,0x4b,0x43,0x08,0xa6,0xf7,0x72,0x43,0xb1,0xfd,0x4f,0x43, 14680 0x3b,0x07,0x71,0x43,0x4a,0x14,0x53,0x43,0xa2,0xf0,0x6d,0x43,0x7c,0x29,0x55,0x43,0x09,0x06,0x00,0x8d, 14681 0xa6,0x43,0xef,0x21,0x01,0x43,0x08,0x52,0xfb,0xa6,0x43,0xce,0xc8,0x02,0x43,0xe6,0x16,0xa7,0x43,0x51, 14682 0x4c,0x05,0x43,0x3b,0x68,0xa6,0x43,0x4c,0x75,0x08,0x43,0x09,0x06,0xde,0x20,0xa1,0x43,0x86,0x50,0x08, 14683 0x43,0x08,0xd4,0x4e,0xa1,0x43,0xd3,0xe7,0x0b,0x43,0xb5,0xe9,0xa0,0x43,0x59,0x5a,0x0f,0x43,0xba,0xcc, 14684 0x9f,0x43,0x54,0x83,0x12,0x43,0x09,0x06,0x77,0xfb,0x99,0x43,0x6c,0x16,0x13,0x43,0x08,0xde,0xfc,0x9a, 14685 0x43,0x4a,0xbd,0x14,0x43,0x06,0x34,0x9b,0x43,0xfe,0x55,0x19,0x43,0x13,0xe9,0x99,0x43,0x41,0x27,0x1f, 14686 0x43,0x09,0x06,0x46,0xce,0x93,0x43,0x26,0xa5,0x1d,0x43,0x08,0xe7,0xaa,0x94,0x43,0xbb,0xcc,0x1f,0x43, 14687 0x18,0xb4,0x94,0x43,0xa8,0x40,0x24,0x43,0xe2,0xbb,0x93,0x43,0x21,0xfe,0x28,0x43,0x09,0x06,0xb1,0x8e, 14688 0x8d,0x43,0xa8,0x58,0x28,0x43,0x08,0x19,0x90,0x8e,0x43,0x54,0x13,0x2b,0x43,0xa4,0xd9,0x8e,0x43,0x84, 14689 0x40,0x31,0x43,0x46,0xaa,0x8d,0x43,0x29,0x24,0x37,0x43,0x09,0x06,0xd6,0xbe,0x88,0x43,0xef,0x30,0x33, 14690 0x43,0x08,0x0c,0xb7,0x89,0x43,0x0e,0xa2,0x35,0x43,0xc0,0x37,0x8a,0x43,0x7a,0xaa,0x3b,0x43,0xbb,0x48, 14691 0x89,0x43,0xbb,0x7b,0x41,0x43,0x09,0x06,0x3a,0xad,0x82,0x43,0xc4,0x59,0x43,0x43,0x08,0xd2,0xb7,0x83, 14692 0x43,0x2b,0x5b,0x44,0x43,0x35,0xd6,0x85,0x43,0x48,0xf5,0x49,0x43,0x42,0x97,0x86,0x43,0xc4,0xa1,0x4f, 14693 0x43,0x09,0x06,0x9c,0xb3,0x80,0x43,0x48,0x55,0x5a,0x43,0x08,0xff,0xc5,0x80,0x43,0x09,0x73,0x55,0x43, 14694 0x93,0xe1,0x80,0x43,0x0f,0x39,0x53,0x43,0xf1,0xbe,0x7e,0x43,0x18,0xe7,0x4c,0x43,0x09,0x06,0xe0,0x02, 14695 0x7b,0x43,0x92,0xec,0x5d,0x43,0x08,0x09,0x3a,0x7b,0x43,0xf0,0xf7,0x58,0x43,0x09,0x3a,0x7b,0x43,0xe6, 14696 0x31,0x5b,0x43,0xe0,0x02,0x7b,0x43,0xa8,0x4f,0x56,0x43,0x09,0x06,0x39,0x4f,0x7d,0x43,0x3e,0x8f,0x5c, 14697 0x43,0x08,0xe9,0xe0,0x7c,0x43,0x03,0x9c,0x58,0x43,0x1e,0x2b,0x81,0x43,0x7f,0x30,0x5a,0x43,0xff,0x73, 14698 0x7d,0x43,0xf6,0xb6,0x51,0x43,0x09,0x06,0x5c,0xb8,0x52,0x43,0x28,0x21,0x87,0x43,0x08,0xae,0x3e,0x57, 14699 0x43,0x12,0x9a,0x88,0x43,0x23,0xf5,0x56,0x43,0x04,0xf1,0x8b,0x43,0x25,0xfc,0x5b,0x43,0x85,0x74,0x8e, 14700 0x43,0x08,0x2f,0xf2,0x61,0x43,0x8e,0x52,0x90,0x43,0xd9,0xdc,0x6c,0x43,0x85,0x74,0x8e,0x43,0xc6,0x20, 14701 0x69,0x43,0x3d,0xd8,0x8d,0x43,0x08,0x6d,0x8c,0x5a,0x43,0xf5,0x3b,0x8d,0x43,0x3d,0x77,0x58,0x43,0xa1, 14702 0xc6,0x87,0x43,0xf8,0xed,0x5e,0x43,0x5e,0x0d,0x86,0x43,0x09,0x06,0xde,0xcc,0x92,0x43,0xf7,0x17,0x87, 14703 0x43,0x08,0xb6,0x89,0x90,0x43,0xae,0x87,0x88,0x43,0x4a,0xa5,0x90,0x43,0xa1,0xde,0x8b,0x43,0xf9,0x2a, 14704 0x8e,0x43,0x23,0x62,0x8e,0x43,0x08,0xf5,0x2f,0x8b,0x43,0x5c,0x49,0x90,0x43,0x35,0xd6,0x85,0x43,0x8e, 14705 0x46,0x8e,0x43,0x3d,0xb4,0x87,0x43,0x47,0xaa,0x8d,0x43,0x08,0x6a,0xfe,0x8e,0x43,0xff,0x0d,0x8d,0x43, 14706 0xbb,0x6c,0x8f,0x43,0xf7,0x17,0x87,0x43,0x5c,0x31,0x8c,0x43,0xb2,0x5e,0x85,0x43,0x09,0x06,0x60,0x38, 14707 0x91,0x43,0x69,0x5d,0x7a,0x43,0x08,0x34,0x1e,0x92,0x43,0x1e,0x5b,0x89,0x43,0x04,0x63,0x7e,0x43,0x5e, 14708 0x01,0x84,0x43,0x59,0x2a,0x87,0x43,0x0d,0xcf,0x8d,0x43,0x09,0x03,0x04,0x06,0x5a,0x18,0x63,0x43,0x82, 14709 0x79,0x8b,0x43,0x08,0x25,0x2c,0x64,0x43,0x82,0x79,0x8b,0x43,0x2a,0x1b,0x65,0x43,0x9d,0xef,0x8a,0x43, 14710 0x2a,0x1b,0x65,0x43,0xc1,0x37,0x8a,0x43,0x08,0x2a,0x1b,0x65,0x43,0x17,0x89,0x89,0x43,0x25,0x2c,0x64, 14711 0x43,0x31,0xff,0x88,0x43,0x5a,0x18,0x63,0x43,0x31,0xff,0x88,0x43,0x08,0xf3,0x16,0x62,0x43,0x31,0xff, 14712 0x88,0x43,0xee,0x27,0x61,0x43,0x17,0x89,0x89,0x43,0xee,0x27,0x61,0x43,0xc1,0x37,0x8a,0x43,0x08,0xee, 14713 0x27,0x61,0x43,0x9d,0xef,0x8a,0x43,0xf3,0x16,0x62,0x43,0x82,0x79,0x8b,0x43,0x5a,0x18,0x63,0x43,0x82, 14714 0x79,0x8b,0x43,0x09,0x06,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x08,0x34,0xee,0x89,0x43,0x82,0x79, 14715 0x8b,0x43,0x85,0x5c,0x8a,0x43,0x9d,0xef,0x8a,0x43,0x85,0x5c,0x8a,0x43,0xc1,0x37,0x8a,0x43,0x08,0x85, 14716 0x5c,0x8a,0x43,0x17,0x89,0x89,0x43,0x34,0xee,0x89,0x43,0x31,0xff,0x88,0x43,0x4f,0x64,0x89,0x43,0x31, 14717 0xff,0x88,0x43,0x08,0x9c,0xe3,0x88,0x43,0x31,0xff,0x88,0x43,0x19,0x6c,0x88,0x43,0x17,0x89,0x89,0x43, 14718 0x19,0x6c,0x88,0x43,0xc1,0x37,0x8a,0x43,0x08,0x19,0x6c,0x88,0x43,0x9d,0xef,0x8a,0x43,0x9c,0xe3,0x88, 14719 0x43,0x82,0x79,0x8b,0x43,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x09,0x02,0x04,0x06,0x19,0x60,0x86, 14720 0x43,0xec,0xed,0xa3,0x43,0x08,0x35,0xd6,0x85,0x43,0x76,0x43,0xa6,0x43,0x93,0xe1,0x80,0x43,0x57,0x02, 14721 0xac,0x43,0x61,0xd8,0x80,0x43,0x87,0x17,0xae,0x43,0x08,0xa5,0x85,0x80,0x43,0xc3,0xfe,0xaf,0x43,0xce, 14722 0xbc,0x80,0x43,0x83,0x40,0xb1,0x43,0xa5,0x91,0x82,0x43,0x79,0x6e,0xb1,0x43,0x08,0x23,0x26,0x84,0x43, 14723 0x40,0x93,0xb1,0x43,0x30,0xe7,0x84,0x43,0xbe,0x1b,0xb1,0x43,0x11,0x82,0x84,0x43,0xab,0x6b,0xaf,0x43, 14724 0x08,0xb7,0x41,0x84,0x43,0x3b,0x98,0xae,0x43,0xb7,0x41,0x84,0x43,0xc3,0xf2,0xad,0x43,0xa1,0xae,0x83, 14725 0x43,0x83,0x28,0xad,0x43,0x08,0xb2,0x52,0x83,0x43,0x80,0x39,0xac,0x43,0x81,0x49,0x83,0x43,0xf0,0x00, 14726 0xab,0x43,0xe4,0x67,0x85,0x43,0x76,0x4f,0xa8,0x43,0x08,0x9c,0xd7,0x86,0x43,0xd1,0x83,0xa6,0x43,0xec, 14727 0x45,0x87,0x43,0x01,0x75,0xa2,0x43,0x19,0x60,0x86,0x43,0xec,0xed,0xa3,0x43,0x09,0x06,0xd9,0xdc,0x6c, 14728 0x43,0x14,0x25,0xa4,0x43,0x08,0xa2,0xf0,0x6d,0x43,0x9f,0x7a,0xa6,0x43,0x47,0xec,0x77,0x43,0x80,0x39, 14729 0xac,0x43,0xa9,0xfe,0x77,0x43,0xb0,0x4e,0xae,0x43,0x08,0x23,0xa4,0x78,0x43,0xea,0x35,0xb0,0x43,0xd2, 14730 0x35,0x78,0x43,0xab,0x77,0xb1,0x43,0xc1,0x79,0x74,0x43,0xa2,0xa5,0xb1,0x43,0x08,0xc6,0x50,0x71,0x43, 14731 0x68,0xca,0xb1,0x43,0xab,0xce,0x6f,0x43,0xe7,0x52,0xb1,0x43,0xea,0x98,0x70,0x43,0xd4,0xa2,0xaf,0x43, 14732 0x08,0x9d,0x19,0x71,0x43,0x96,0xd8,0xae,0x43,0x9d,0x19,0x71,0x43,0xec,0x29,0xae,0x43,0xca,0x3f,0x72, 14733 0x43,0xab,0x5f,0xad,0x43,0x08,0xa6,0xf7,0x72,0x43,0xa7,0x70,0xac,0x43,0x09,0x0a,0x73,0x43,0x17,0x38, 14734 0xab,0x43,0x44,0xcd,0x6e,0x43,0x9f,0x86,0xa8,0x43,0x08,0xd4,0xed,0x6b,0x43,0xf8,0xba,0xa6,0x43,0x31, 14735 0x11,0x6b,0x43,0x2a,0xac,0xa2,0x43,0xd9,0xdc,0x6c,0x43,0x14,0x25,0xa4,0x43,0x09,0x01,0x05,0x06,0x66, 14736 0x5d,0x7a,0x43,0x74,0xeb,0xc2,0x43,0x08,0x09,0x22,0x77,0x43,0x50,0xbb,0xc7,0x43,0xe9,0xe0,0x7c,0x43, 14737 0xf5,0x86,0xc9,0x43,0x8f,0x94,0x7a,0x43,0xc5,0x95,0xcd,0x43,0x09,0x06,0x08,0x98,0x80,0x43,0x6b,0x19, 14738 0xc3,0x43,0x08,0xb7,0x35,0x82,0x43,0x79,0xf2,0xc7,0x43,0xf1,0xbe,0x7e,0x43,0x1e,0xbe,0xc9,0x43,0x73, 14739 0x7c,0x80,0x43,0xec,0xcc,0xcd,0x43,0x09,0x06,0x28,0xab,0x7d,0x43,0xae,0xde,0xc6,0x43,0x08,0x1e,0xcd, 14740 0x7b,0x43,0x8a,0xa2,0xc9,0x43,0x30,0x89,0x7f,0x43,0x5c,0x94,0xcc,0x43,0x28,0xab,0x7d,0x43,0x42,0x2a, 14741 0xcf,0x43,0x09,0x01,0x05,0x06,0x24,0x14,0xe0,0x42,0xf5,0x77,0x97,0x43,0x08,0xf7,0x1d,0xe7,0x42,0x74, 14742 0x00,0x97,0x43,0x4d,0x93,0xec,0x42,0xdb,0xf5,0x95,0x43,0x29,0x4b,0xed,0x42,0xcd,0x34,0x95,0x43,0x09, 14743 0x06,0x29,0x7b,0xf5,0x42,0x6f,0x1d,0x98,0x43,0x08,0xe4,0xf1,0xfb,0x42,0x61,0x5c,0x97,0x43,0xdb,0x7d, 14744 0x01,0x43,0xb2,0xbe,0x95,0x43,0x55,0x23,0x02,0x43,0xe7,0xaa,0x94,0x43,0x09,0x06,0x98,0xdc,0x03,0x43, 14745 0xbe,0x8b,0x98,0x43,0x08,0x66,0xdf,0x05,0x43,0x47,0xe6,0x97,0x43,0xae,0x87,0x08,0x43,0x98,0x48,0x96, 14746 0x43,0x61,0x08,0x09,0x43,0xd6,0x06,0x95,0x43,0x09,0x06,0x31,0x0b,0x0b,0x43,0x8e,0x82,0x98,0x43,0x08, 14747 0xdb,0xc5,0x0d,0x43,0x80,0xc1,0x97,0x43,0xd6,0xee,0x10,0x43,0xa9,0xec,0x95,0x43,0x79,0xcb,0x11,0x43, 14748 0x55,0x8f,0x94,0x43,0x09,0x06,0xd1,0x2f,0x18,0x43,0xdb,0x01,0x98,0x43,0x08,0xad,0xe7,0x18,0x43,0x38, 14749 0x25,0x97,0x43,0x8a,0x9f,0x19,0x43,0x80,0xb5,0x95,0x43,0xd6,0x1e,0x19,0x43,0xe0,0xd8,0x94,0x43,0x09, 14750 0x06,0x9a,0x5b,0x1d,0x43,0x58,0x8a,0x97,0x43,0x08,0x01,0x5d,0x1e,0x43,0xf1,0x88,0x96,0x43,0x2f,0x83, 14751 0x1f,0x43,0x19,0xb4,0x94,0x43,0x19,0xf0,0x1e,0x43,0x6f,0x05,0x94,0x43,0x09,0x06,0x0b,0x53,0x24,0x43, 14752 0xae,0xdb,0x96,0x43,0x08,0x25,0xd5,0x25,0x43,0x50,0xac,0x95,0x43,0x53,0xfb,0x26,0x43,0x8a,0x7b,0x93, 14753 0x43,0x76,0x43,0x26,0x43,0xb7,0x95,0x92,0x43,0x09,0x06,0x76,0x5b,0x2a,0x43,0x47,0xda,0x95,0x43,0x08, 14754 0xf3,0xef,0x2b,0x43,0x10,0xe2,0x94,0x43,0x6d,0x95,0x2c,0x43,0xae,0xc3,0x92,0x43,0x68,0xa6,0x2b,0x43, 14755 0x47,0xc2,0x91,0x43,0x09,0x06,0x36,0xc1,0x31,0x43,0x2c,0x58,0x94,0x43,0x08,0x8c,0x1e,0x33,0x43,0x31, 14756 0x3b,0x93,0x43,0x79,0x7a,0x33,0x43,0xff,0x25,0x91,0x43,0xd9,0x9d,0x32,0x43,0xc1,0x5b,0x90,0x43,0x09, 14757 0x06,0x25,0x35,0x36,0x43,0x31,0x3b,0x93,0x43,0x08,0x3f,0xb7,0x37,0x43,0xc1,0x67,0x92,0x43,0xe0,0x93, 14758 0x38,0x43,0xae,0xb7,0x90,0x43,0x7e,0x81,0x38,0x43,0x0d,0xdb,0x8f,0x43,0x09,0x06,0xb5,0x85,0x3b,0x43, 14759 0xe4,0xaf,0x91,0x43,0x08,0xcf,0x07,0x3d,0x43,0x9d,0x13,0x91,0x43,0xbc,0x63,0x3d,0x43,0x47,0xb6,0x8f, 14760 0x43,0xe5,0x9a,0x3d,0x43,0x74,0xd0,0x8e,0x43,0x09,0x06,0xae,0xc6,0x42,0x43,0xa4,0xd9,0x8e,0x43,0x08, 14761 0xca,0x48,0x44,0x43,0xfa,0x2a,0x8e,0x43,0xa2,0x11,0x44,0x43,0x9d,0xfb,0x8c,0x43,0x55,0x92,0x44,0x43, 14762 0x0d,0xc3,0x8b,0x43,0x09,0x06,0x39,0x10,0xc3,0x43,0x34,0x36,0x96,0x43,0x08,0x92,0x44,0xc1,0x43,0xe4, 14763 0xc7,0x95,0x43,0x6f,0xf0,0xbf,0x43,0x4b,0xbd,0x94,0x43,0x47,0xb9,0xbf,0x43,0x0b,0xf3,0x93,0x43,0x09, 14764 0x06,0x8f,0x49,0xbe,0x43,0xb7,0xad,0x96,0x43,0x08,0x11,0xb5,0xbc,0x43,0x77,0xe3,0x95,0x43,0x9c,0xf2, 14765 0xba,0x43,0xfa,0x4e,0x94,0x43,0xae,0x96,0xba,0x43,0x31,0x3b,0x93,0x43,0x09,0x06,0xdb,0xb0,0xb9,0x43, 14766 0x10,0xee,0x96,0x43,0x08,0x42,0xa6,0xb8,0x43,0xc8,0x51,0x96,0x43,0x50,0x5b,0xb7,0x43,0x19,0xb4,0x94, 14767 0x43,0xf7,0x1a,0xb7,0x43,0x58,0x72,0x93,0x43,0x09,0x06,0xf2,0x2b,0xb6,0x43,0x10,0xee,0x96,0x43,0x08, 14768 0x9d,0xce,0xb4,0x43,0x04,0x2d,0x96,0x43,0xed,0x30,0xb3,0x43,0x2c,0x58,0x94,0x43,0xce,0xcb,0xb2,0x43, 14769 0xd6,0xfa,0x92,0x43,0x09,0x06,0x5a,0x09,0xb1,0x43,0x19,0xc0,0x96,0x43,0x08,0x6c,0xad,0xb0,0x43,0x77, 14770 0xe3,0x95,0x43,0x7e,0x51,0xb0,0x43,0xc0,0x73,0x94,0x43,0xd8,0x91,0xb0,0x43,0x1e,0x97,0x93,0x43,0x09, 14771 0x06,0x48,0x4d,0xad,0x43,0xbe,0x7f,0x96,0x43,0x08,0x95,0xcc,0xac,0x43,0x58,0x7e,0x95,0x43,0x4d,0x30, 14772 0xac,0x43,0x80,0xa9,0x93,0x43,0xd8,0x79,0xac,0x43,0xd6,0xfa,0x92,0x43,0x09,0x06,0x90,0xd1,0xa9,0x43, 14773 0x14,0xd1,0x95,0x43,0x08,0x83,0x10,0xa9,0x43,0xb7,0xa1,0x94,0x43,0x3b,0x74,0xa8,0x43,0xf1,0x70,0x92, 14774 0x43,0x29,0xd0,0xa8,0x43,0x1e,0x8b,0x91,0x43,0x09,0x06,0x5a,0xcd,0xa6,0x43,0x8a,0x87,0x95,0x43,0x08, 14775 0x1c,0x03,0xa6,0x43,0x23,0x86,0x94,0x43,0x5f,0xb0,0xa5,0x43,0xc1,0x67,0x92,0x43,0xe1,0x27,0xa6,0x43, 14776 0x8a,0x6f,0x91,0x43,0x09,0x06,0xd4,0x5a,0xa3,0x43,0x2c,0x58,0x94,0x43,0x08,0x29,0xac,0xa2,0x43,0x31, 14777 0x3b,0x93,0x43,0x32,0x7e,0xa2,0x43,0xff,0x25,0x91,0x43,0x83,0xec,0xa2,0x43,0x8e,0x52,0x90,0x43,0x09, 14778 0x06,0xf8,0x96,0xa0,0x43,0x1e,0x97,0x93,0x43,0x08,0xeb,0xd5,0x9f,0x43,0x7b,0xba,0x92,0x43,0x99,0x67, 14779 0x9f,0x43,0x9d,0x13,0x91,0x43,0x99,0x67,0x9f,0x43,0xfa,0x36,0x90,0x43,0x09,0x06,0xeb,0xc9,0x9d,0x43, 14780 0xc8,0x39,0x92,0x43,0x08,0xde,0x08,0x9d,0x43,0xb2,0xa6,0x91,0x43,0xe6,0xda,0x9c,0x43,0x2c,0x40,0x90, 14781 0x43,0x52,0xbf,0x9c,0x43,0x5a,0x5a,0x8f,0x43,0x09,0x06,0x37,0x3d,0x9b,0x43,0x85,0x80,0x90,0x43,0x08, 14782 0x2a,0x7c,0x9a,0x43,0xdb,0xd1,0x8f,0x43,0xf0,0xa0,0x9a,0x43,0x7d,0xa2,0x8e,0x43,0x65,0x57,0x9a,0x43, 14783 0xee,0x69,0x8d,0x43,0x09,0x02,0x04,0x06,0x2a,0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x08,0x0d,0x8a,0x31, 14784 0x42,0x9f,0x0e,0x94,0x43,0xf3,0x1f,0x34,0x42,0x3d,0xfc,0x93,0x43,0x63,0xff,0x36,0x42,0xa9,0xe0,0x93, 14785 0x43,0x08,0xb5,0x34,0x5d,0x42,0x0b,0xf3,0x93,0x43,0x6d,0xa4,0x5e,0x42,0x03,0x39,0x98,0x43,0xe7,0x31, 14786 0x5b,0x42,0x93,0x89,0x9d,0x43,0x08,0x02,0x9c,0x58,0x42,0xd4,0x5a,0xa3,0x43,0x38,0x70,0x53,0x42,0x14, 14787 0x49,0xaa,0x43,0xf8,0xed,0x5e,0x42,0x83,0x28,0xad,0x43,0x08,0xea,0x68,0x68,0x42,0x20,0x22,0xaf,0x43, 14788 0x12,0xb8,0x6c,0x42,0xb5,0x49,0xb1,0x43,0x2a,0x4b,0x6d,0x42,0x0d,0x96,0xb3,0x43,0x07,0x2a,0x4b,0x6d, 14789 0x42,0xc6,0x05,0xb5,0x43,0x08,0x87,0x6e,0x6c,0x42,0x68,0xee,0xb7,0x43,0x1c,0x66,0x66,0x42,0x31,0x0e, 14790 0xbb,0x43,0x57,0x11,0x5e,0x42,0x8f,0x49,0xbe,0x43,0x08,0x66,0x96,0x54,0x42,0xb9,0x5c,0xb8,0x43,0x2c, 14791 0x2b,0x3c,0x42,0x68,0xd6,0xb3,0x43,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x07,0x2a,0xf4,0x2e,0x42, 14792 0x61,0xa4,0xa3,0x43,0x08,0x55,0x1a,0x30,0x42,0xf0,0xd0,0xa2,0x43,0xf8,0xf6,0x30,0x42,0xb2,0x06,0xa2, 14793 0x43,0x98,0xd3,0x31,0x42,0xd6,0x4e,0xa1,0x43,0x08,0x1c,0x6f,0x38,0x42,0x2a,0x94,0x9e,0x43,0xc1,0x22, 14794 0x36,0x42,0xf5,0x9b,0x9d,0x43,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x57, 14795 0xa2,0x9b,0x43,0x08,0xab,0x8f,0x35,0x42,0x8a,0xab,0x9b,0x43,0xe9,0x71,0x3a,0x42,0xb2,0xe2,0x9b,0x43, 14796 0xb7,0x74,0x3c,0x42,0x34,0x5a,0x9c,0x43,0x08,0x23,0x7d,0x42,0x42,0x0b,0x2f,0x9e,0x43,0xe5,0x9a,0x3d, 14797 0x42,0x38,0x6d,0xa3,0x43,0x36,0xd9,0x35,0x42,0xf3,0xd7,0xa7,0x43,0x08,0x12,0x61,0x2e,0x42,0xb0,0x42, 14798 0xac,0x43,0x63,0xff,0x36,0x42,0xdd,0x74,0xaf,0x43,0x1e,0xa6,0x45,0x42,0x44,0x82,0xb2,0x43,0x08,0x74, 14799 0x1b,0x4b,0x42,0x79,0x7a,0xb3,0x43,0x10,0x21,0x4f,0x42,0x2a,0x18,0xb5,0x43,0xdb,0x4c,0x54,0x42,0x91, 14800 0x19,0xb6,0x43,0x08,0xee,0x3f,0x65,0x42,0x5f,0x28,0xba,0x43,0xa7,0xaf,0x66,0x42,0xb9,0x50,0xb6,0x43, 14801 0x14,0x58,0x5c,0x42,0xca,0xdc,0xb1,0x43,0x08,0x2c,0x8b,0x4c,0x42,0x4e,0x30,0xac,0x43,0x19,0xcf,0x48, 14802 0x42,0x2a,0xd0,0xa8,0x43,0xbc,0xab,0x49,0x42,0xa9,0x4c,0xa6,0x43,0x08,0x61,0x5f,0x47,0x42,0xfa,0xa2, 14803 0xa2,0x43,0xa7,0xaf,0x66,0x42,0x85,0x98,0x94,0x43,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x07,0x2a, 14804 0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x09,0x06,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x08,0xdc,0xe3, 14805 0xf1,0x41,0xe9,0x9e,0x92,0x43,0xd2,0xe7,0x0b,0x42,0xd6,0x06,0x95,0x43,0x2a,0xf4,0x2e,0x42,0x04,0x21, 14806 0x94,0x43,0x07,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x08,0x87,0x17,0x2e,0x42,0xc3,0x62,0x95,0x43, 14807 0xe7,0x3a,0x2d,0x42,0xf5,0x6b,0x95,0x43,0x44,0x5e,0x2c,0x42,0xf5,0x6b,0x95,0x43,0x08,0xd1,0x47,0x1c, 14808 0x42,0x19,0xc0,0x96,0x43,0x66,0xdf,0x05,0x42,0x38,0x19,0x95,0x43,0x12,0x6a,0x00,0x42,0xb2,0xbe,0x95, 14809 0x43,0x08,0xbb,0x6b,0xea,0x41,0xd6,0x12,0x97,0x43,0x2d,0x82,0xfa,0x41,0x61,0x74,0x9b,0x43,0x7e,0x72, 14810 0x06,0x42,0x8a,0xab,0x9b,0x43,0x08,0xc8,0x39,0x12,0x42,0x4e,0xd0,0x9b,0x43,0x53,0xe3,0x22,0x42,0xc3, 14811 0x86,0x9b,0x43,0x2a,0xf4,0x2e,0x42,0x57,0xa2,0x9b,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43, 14812 0x08,0x01,0xa5,0x2a,0x42,0xa4,0x2d,0x9d,0x43,0x96,0x9c,0x24,0x42,0x06,0x40,0x9d,0x43,0x8a,0xb7,0x1d, 14813 0x42,0x9a,0x5b,0x9d,0x43,0x08,0x6b,0x16,0x13,0x42,0xcd,0x64,0x9d,0x43,0x42,0xc7,0x0e,0x42,0x9a,0x5b, 14814 0x9d,0x43,0x23,0x26,0x04,0x42,0xcd,0x64,0x9d,0x43,0x08,0xe6,0x91,0xeb,0x41,0x38,0x49,0x9d,0x43,0x73, 14815 0x7b,0xdb,0x41,0xf5,0x83,0x99,0x43,0x7f,0x60,0xe2,0x41,0x0b,0x0b,0x98,0x43,0x08,0x7f,0x60,0xe2,0x41, 14816 0xec,0x99,0x95,0x43,0xe3,0x5a,0xde,0x41,0xbe,0x7f,0x96,0x43,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43, 14817 0x07,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x09,0x06,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x08, 14818 0xd4,0x7e,0x29,0x42,0xab,0x6b,0xaf,0x43,0x4e,0x0c,0x26,0x42,0x44,0x6a,0xae,0x43,0x38,0x79,0x25,0x42, 14819 0xd4,0x96,0xad,0x43,0x08,0x25,0xbd,0x21,0x42,0xe2,0x4b,0xac,0x43,0x49,0x35,0x29,0x42,0x9a,0x97,0xa7, 14820 0x43,0x2a,0xf4,0x2e,0x42,0x61,0xa4,0xa3,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x09,0x06, 14821 0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x08,0x86,0x20,0x80,0x43,0x57,0x41,0xe6,0x43,0x7d,0x4e,0x80, 14822 0x43,0x25,0x38,0xe6,0x43,0xa5,0x85,0x80,0x43,0xf3,0x2e,0xe6,0x43,0x08,0x35,0xca,0x83,0x43,0xd4,0xc9, 14823 0xe5,0x43,0x9c,0xd7,0x86,0x43,0x44,0x91,0xe4,0x43,0xd5,0xca,0x8a,0x43,0x91,0x1c,0xe6,0x43,0x08,0x53, 14824 0x5f,0x8c,0x43,0xf8,0x1d,0xe7,0x43,0x2f,0x17,0x8d,0x43,0x4e,0x7b,0xe8,0x43,0x92,0x29,0x8d,0x43,0x2f, 14825 0x22,0xea,0x43,0x07,0x92,0x29,0x8d,0x43,0x44,0xb5,0xea,0x43,0x08,0xfe,0x0d,0x8d,0x43,0x2a,0x4b,0xed, 14826 0x43,0xe3,0x8b,0x8b,0x43,0x55,0x7d,0xf0,0x43,0xec,0x51,0x89,0x43,0x72,0x0b,0xf4,0x43,0x08,0xcd,0xd4, 14827 0x84,0x43,0x9d,0x55,0xfb,0x43,0xc9,0xe5,0x83,0x43,0x74,0x1e,0xfb,0x43,0x73,0x94,0x84,0x43,0x5a,0x90, 14828 0xf7,0x43,0x08,0xe8,0x62,0x88,0x43,0xfd,0x30,0xee,0x43,0x39,0xc5,0x86,0x43,0xdd,0xbf,0xeb,0x43,0x35, 14829 0xbe,0x81,0x43,0x40,0xde,0xed,0x43,0x08,0x4f,0x34,0x81,0x43,0x36,0x0c,0xee,0x43,0x08,0x98,0x80,0x43, 14830 0xfd,0x30,0xee,0x43,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x40,0xec, 14831 0x43,0x08,0x35,0xbe,0x81,0x43,0x06,0xf7,0xeb,0x43,0x15,0x65,0x83,0x43,0x49,0xa4,0xeb,0x43,0x1e,0x43, 14832 0x85,0x43,0xbe,0x5a,0xeb,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0x18,0xea,0x43,0x42,0x97,0x86,0x43,0x5f, 14833 0x67,0xf4,0x43,0xa9,0x98,0x87,0x43,0xd4,0x1d,0xf4,0x43,0x08,0x5c,0x25,0x8a,0x43,0xcf,0x16,0xef,0x43, 14834 0x46,0xaa,0x8d,0x43,0x5a,0x3c,0xe9,0x43,0x19,0x6c,0x88,0x43,0x53,0x5e,0xe7,0x43,0x08,0xc4,0x02,0x85, 14835 0x43,0x96,0x0b,0xe7,0x43,0x85,0x2c,0x82,0x43,0x83,0x67,0xe7,0x43,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7, 14836 0x43,0x07,0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x09,0x06,0xfd,0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43, 14837 0x08,0xfa,0x6c,0x78,0x43,0xd1,0xc2,0xe0,0x43,0x25,0x5c,0x6c,0x43,0x25,0x44,0xe8,0x43,0x1d,0xe5,0x7f, 14838 0x43,0x87,0x4a,0xe6,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,0x43,0x08,0xa6,0x27,0x7b,0x43,0x91, 14839 0x28,0xe8,0x43,0xbc,0xa2,0x77,0x43,0xb0,0x8d,0xe8,0x43,0xc6,0x68,0x75,0x43,0x57,0x4d,0xe8,0x43,0x08, 14840 0xe0,0xd2,0x72,0x43,0xab,0x9e,0xe7,0x43,0x50,0x9a,0x71,0x43,0x2a,0x27,0xe7,0x43,0xea,0x98,0x70,0x43, 14841 0x57,0x35,0xe4,0x43,0x08,0x94,0x3b,0x6f,0x43,0x14,0x7c,0xe2,0x43,0xff,0x13,0x6d,0x43,0x06,0xbb,0xe1, 14842 0x43,0xcf,0xfe,0x6a,0x43,0x06,0xbb,0xe1,0x43,0x08,0x44,0x9d,0x66,0x43,0x77,0x8e,0xe2,0x43,0x3b,0xef, 14843 0x6c,0x43,0x91,0x10,0xe4,0x43,0xfd,0x24,0x6c,0x43,0xb0,0x81,0xe6,0x43,0x08,0x96,0x23,0x6b,0x43,0xee, 14844 0x57,0xe9,0x43,0xca,0x0f,0x6a,0x43,0x5f,0x37,0xec,0x43,0x55,0x71,0x6e,0x43,0x9f,0x01,0xed,0x43,0x08, 14845 0xdb,0xfb,0x75,0x43,0x3b,0xef,0xec,0x43,0x09,0x3a,0x7b,0x43,0xb0,0xa5,0xec,0x43,0x1d,0xe5,0x7f,0x43, 14846 0x91,0x40,0xec,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x08,0xa9,0x16,0x7c,0x43,0xb0,0xb1, 14847 0xee,0x43,0x47,0xec,0x77,0x43,0xd9,0xe8,0xee,0x43,0x1e,0x9d,0x73,0x43,0xcf,0x16,0xef,0x43,0x08,0x0e, 14848 0xc9,0x6b,0x43,0xee,0x7b,0xef,0x43,0x7e,0x90,0x6a,0x43,0xfd,0x30,0xee,0x43,0x01,0xfc,0x68,0x43,0x4e, 14849 0x93,0xec,0x43,0x08,0x31,0xf9,0x66,0x43,0x4e,0x87,0xea,0x43,0x31,0x11,0x6b,0x43,0xd4,0xd5,0xe7,0x43, 14850 0xd9,0xc4,0x68,0x43,0xd4,0xc9,0xe5,0x43,0x08,0xe5,0x79,0x67,0x43,0x77,0x9a,0xe4,0x43,0x44,0x9d,0x66, 14851 0x43,0xab,0x86,0xe3,0x43,0x7e,0x78,0x66,0x43,0x0b,0xaa,0xe2,0x43,0x07,0x7e,0x78,0x66,0x43,0x57,0x29, 14852 0xe2,0x43,0x08,0xa7,0xaf,0x66,0x43,0xbe,0x1e,0xe1,0x43,0x87,0x56,0x68,0x43,0x77,0x82,0xe0,0x43,0xfd, 14853 0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,0x09,0x06,0xc4,0x41,0xbf,0x43,0x85,0xc0,0x72,0x42,0x08,0x73,0xdf, 14854 0xc0,0x43,0xf4,0x76,0x72,0x42,0x97,0x33,0xc2,0x43,0x85,0xc0,0x72,0x42,0xb2,0xb5,0xc3,0x43,0x64,0x56, 14855 0x75,0x42,0x08,0x03,0x24,0xc4,0x43,0x5e,0x7f,0x78,0x42,0xfa,0x51,0xc4,0x43,0x01,0x85,0x7c,0x42,0x5c, 14856 0x64,0xc4,0x43,0xa0,0xb3,0x80,0x42,0x07,0x5c,0x64,0xc4,0x43,0x10,0x93,0x83,0x42,0x08,0xc8,0x48,0xc4, 14857 0x43,0x1c,0x78,0x8a,0x42,0x27,0x6c,0xc3,0x43,0xaf,0xcf,0x94,0x42,0x23,0x7d,0xc2,0x43,0x99,0x9c,0xa4, 14858 0x42,0x08,0x3d,0xe7,0xbf,0x43,0xfb,0xfd,0xb5,0x42,0xb3,0x9d,0xbf,0x43,0x88,0x17,0xae,0x42,0xc4,0x41, 14859 0xbf,0x43,0x69,0x76,0xa3,0x42,0x07,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x08,0x4f,0x8b,0xbf,0x43, 14860 0xed,0x81,0x91,0x42,0xe4,0xa6,0xbf,0x43,0x5d,0x61,0x94,0x42,0xfa,0x39,0xc0,0x43,0x3b,0x49,0x9d,0x42, 14861 0x08,0x2b,0x43,0xc0,0x43,0x28,0xed,0xa9,0x42,0x61,0x3b,0xc1,0x43,0x00,0x9e,0xa5,0x42,0xe4,0xb2,0xc1, 14862 0x43,0x5d,0x91,0x9c,0x42,0x08,0x78,0xce,0xc1,0x43,0xfd,0x36,0x90,0x42,0x22,0x89,0xc4,0x43,0x81,0x72, 14863 0x86,0x42,0xae,0xc6,0xc2,0x43,0xa0,0xb3,0x80,0x42,0x08,0x54,0x86,0xc2,0x43,0x58,0xd1,0x7e,0x42,0x30, 14864 0x32,0xc1,0x43,0xce,0x5e,0x7b,0x42,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x07,0xc4,0x41,0xbf,0x43, 14865 0x85,0xc0,0x72,0x42,0x09,0x06,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x08,0x35,0xfd,0xbb,0x43,0xa4, 14866 0xa1,0x5c,0x42,0x5e,0x34,0xbc,0x43,0x9d,0x2a,0x70,0x42,0x5e,0x40,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08, 14867 0x4c,0x9c,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,0xef,0xbe,0x43,0x0e,0x0a,0x73,0x42,0xc4,0x41,0xbf,0x43, 14868 0x85,0xc0,0x72,0x42,0x07,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x08,0xcd,0x13,0xbf,0x43,0xe8,0xf1, 14869 0x7b,0x42,0xd6,0xe5,0xbe,0x43,0x71,0x3b,0x7c,0x42,0xdf,0xb7,0xbe,0x43,0x71,0x3b,0x7c,0x42,0x08,0x08, 14870 0xe3,0xbc,0x43,0xa4,0x61,0x7d,0x42,0x28,0x3c,0xbb,0x43,0x91,0x45,0x69,0x42,0x28,0x3c,0xbb,0x43,0x58, 14871 0x71,0x6e,0x42,0x08,0xce,0xfb,0xba,0x43,0xd5,0x35,0x78,0x42,0x59,0x45,0xbb,0x43,0x58,0x23,0x82,0x42, 14872 0xa1,0xe1,0xbb,0x43,0xd7,0xbe,0x88,0x42,0x08,0xc9,0x18,0xbc,0x43,0xaf,0x9f,0x8c,0x42,0x1e,0x76,0xbd, 14873 0x43,0x51,0x7c,0x8d,0x42,0xd6,0xe5,0xbe,0x43,0xf4,0x58,0x8e,0x42,0x08,0x9c,0x0a,0xbf,0x43,0x45,0xc7, 14874 0x8e,0x42,0x30,0x26,0xbf,0x43,0x96,0x35,0x8f,0x42,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x07,0xc4, 14875 0x41,0xbf,0x43,0x69,0x76,0xa3,0x42,0x08,0x08,0xef,0xbe,0x43,0xb1,0xd6,0x99,0x42,0xe8,0x89,0xbe,0x43, 14876 0xde,0xc5,0x8d,0x42,0xc0,0x46,0xbc,0x43,0xc2,0x5b,0x90,0x42,0x08,0x9c,0xf2,0xba,0x43,0x86,0x80,0x90, 14877 0x42,0xf2,0x43,0xba,0x43,0xe8,0x73,0x87,0x42,0x8f,0x31,0xba,0x43,0xb6,0xf4,0x7d,0x42,0x07,0x8f,0x31, 14878 0xba,0x43,0x21,0xc6,0x76,0x42,0x08,0xc0,0x3a,0xba,0x43,0x5f,0x48,0x6b,0x42,0xae,0x96,0xba,0x43,0xe3, 14879 0x83,0x61,0x42,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x09,0x06,0xea,0x74,0xea,0x43,0x61,0x44,0x93, 14880 0x43,0x08,0x24,0x5c,0xec,0x43,0x31,0x3b,0x93,0x43,0xfb,0x30,0xee,0x43,0x93,0x4d,0x93,0x43,0x0d,0xe1, 14881 0xef,0x43,0x80,0xa9,0x93,0x43,0x08,0x8f,0x58,0xf0,0x43,0xd1,0x17,0x94,0x43,0xb7,0x8f,0xf0,0x43,0x10, 14882 0xe2,0x94,0x43,0xea,0x98,0xf0,0x43,0xa9,0xec,0x95,0x43,0x07,0xea,0x98,0xf0,0x43,0x38,0x25,0x97,0x43, 14883 0x08,0x23,0x74,0xf0,0x43,0x9f,0x32,0x9a,0x43,0x5a,0x60,0xef,0x43,0x53,0xcb,0x9e,0x43,0x2d,0x3a,0xee, 14884 0x43,0xfd,0x91,0xa3,0x43,0x08,0xa2,0xf0,0xed,0x43,0xdd,0x38,0xa5,0x43,0x17,0xa7,0xed,0x43,0xbe,0xdf, 14885 0xa6,0x43,0x5a,0x54,0xed,0x43,0x9f,0x86,0xa8,0x43,0x08,0xfc,0x24,0xec,0x43,0xca,0xc4,0xad,0x43,0x48, 14886 0xa4,0xeb,0x43,0x40,0x6f,0xab,0x43,0x28,0x3f,0xeb,0x43,0x1c,0x0f,0xa8,0x43,0x08,0x1f,0x6d,0xeb,0x43, 14887 0x72,0x48,0xa3,0x43,0x67,0x09,0xec,0x43,0xd1,0x53,0x9e,0x43,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,0x43, 14888 0x07,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x08,0x7e,0x90,0xea,0x43,0x8a,0x9f,0x99,0x43,0x12,0xac, 14889 0xea,0x43,0xbc,0xa8,0x99,0x43,0xa7,0xc7,0xea,0x43,0xbc,0xa8,0x99,0x43,0x08,0x51,0x76,0xeb,0x43,0x9f, 14890 0x32,0x9a,0x43,0x5e,0x37,0xec,0x43,0x49,0xed,0x9c,0x43,0xb0,0xa5,0xec,0x43,0x2a,0xa0,0xa0,0x43,0x08, 14891 0x09,0xe6,0xec,0x43,0xd1,0x77,0xa4,0x43,0x28,0x4b,0xed,0x43,0x61,0xa4,0xa3,0x43,0xab,0xc2,0xed,0x43, 14892 0x8e,0xb2,0xa0,0x43,0x08,0x70,0xe7,0xed,0x43,0xde,0x08,0x9d,0x43,0x87,0x86,0xf0,0x43,0x2f,0x53,0x97, 14893 0x43,0x87,0x7a,0xee,0x43,0xec,0x99,0x95,0x43,0x08,0xca,0x27,0xee,0x43,0xff,0x3d,0x95,0x43,0x74,0xca, 14894 0xec,0x43,0x55,0x8f,0x94,0x43,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x07,0xea,0x74,0xea,0x43,0x61, 14895 0x44,0x93,0x43,0x09,0x06,0x05,0xd3,0xe5,0x43,0x19,0x9c,0x90,0x43,0x08,0x09,0xc2,0xe6,0x43,0xd1,0xff, 14896 0x8f,0x43,0x4d,0x6f,0xe6,0x43,0x74,0xe8,0x92,0x43,0x3b,0xd7,0xe8,0x43,0xc3,0x56,0x93,0x43,0x08,0x1f, 14897 0x61,0xe9,0x43,0x93,0x4d,0x93,0x43,0x05,0xeb,0xe9,0x43,0x93,0x4d,0x93,0x43,0xea,0x74,0xea,0x43,0x61, 14898 0x44,0x93,0x43,0x07,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x08,0x24,0x50,0xea,0x43,0xe7,0xaa,0x94, 14899 0x43,0x2d,0x22,0xea,0x43,0xe7,0xaa,0x94,0x43,0x36,0xf4,0xe9,0x43,0xe7,0xaa,0x94,0x43,0x08,0xa2,0xcc, 14900 0xe7,0x43,0xe0,0xd8,0x94,0x43,0xd4,0xc9,0xe5,0x43,0x19,0xa8,0x92,0x43,0xd4,0xc9,0xe5,0x43,0x27,0x69, 14901 0x93,0x43,0x08,0x17,0x77,0xe5,0x43,0xe0,0xd8,0x94,0x43,0x67,0xe5,0xe5,0x43,0x47,0xda,0x95,0x43,0x43, 14902 0x9d,0xe6,0x43,0xe2,0xd3,0x97,0x43,0x08,0x9d,0xdd,0xe6,0x43,0xad,0xe7,0x98,0x43,0x09,0xce,0xe8,0x43, 14903 0xff,0x55,0x99,0x43,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x07,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b, 14904 0x43,0x08,0x71,0xcf,0xe9,0x43,0x53,0xb3,0x9a,0x43,0xa7,0xbb,0xe8,0x43,0xdb,0x0d,0x9a,0x43,0xc6,0x14, 14905 0xe7,0x43,0xdb,0x0d,0x9a,0x43,0x08,0x48,0x80,0xe5,0x43,0xdb,0x0d,0x9a,0x43,0x0a,0xb6,0xe4,0x43,0xc3, 14906 0x6e,0x97,0x43,0x76,0x9a,0xe4,0x43,0x74,0xf4,0x94,0x43,0x07,0x76,0x9a,0xe4,0x43,0x79,0xd7,0x93,0x43, 14907 0x08,0xd8,0xac,0xe4,0x43,0x66,0x27,0x92,0x43,0x29,0x1b,0xe5,0x43,0xe0,0xc0,0x90,0x43,0x05,0xd3,0xe5, 14908 0x43,0x19,0x9c,0x90,0x43,0x09,0x06,0x1b,0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x08,0x71,0x0b,0xf4,0x42, 14909 0x00,0x0e,0x8d,0x42,0x8c,0x0f,0x01,0x43,0x3e,0xc0,0x89,0x42,0xf3,0x28,0x06,0x43,0x48,0x9e,0x8b,0x42, 14910 0x08,0x15,0x89,0x09,0x43,0x00,0x0e,0x8d,0x42,0xe0,0x9c,0x0a,0x43,0xc1,0x8b,0x98,0x42,0xa6,0xc1,0x0a, 14911 0x43,0x02,0xa5,0xaa,0x42,0x07,0xa6,0xc1,0x0a,0x43,0xf9,0xf6,0xb0,0x42,0x08,0xa6,0xc1,0x0a,0x43,0x47, 14912 0x8e,0xb4,0x42,0x42,0xaf,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0xe0,0x9c,0x0a,0x43,0xba,0x74,0xbc,0x42,0x08, 14913 0xa1,0xd2,0x09,0x43,0x40,0x47,0xd0,0x42,0x0d,0xab,0x07,0x43,0x91,0xb5,0xd0,0x42,0x3b,0xb9,0x04,0x43, 14914 0xec,0x71,0xba,0x42,0x08,0xe5,0x5b,0x03,0x43,0xe3,0x33,0xa8,0x42,0x63,0xd8,0x00,0x43,0xce,0x70,0x9f, 14915 0x42,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x07,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,0x42,0x08,0xed, 14916 0x6f,0xed,0x42,0x73,0x24,0x9d,0x42,0xd8,0x0c,0xf5,0x42,0x99,0x6c,0x9c,0x42,0x27,0xab,0xfd,0x42,0xea, 14917 0xda,0x9c,0x42,0x08,0x36,0xca,0x03,0x43,0x2b,0x94,0x9e,0x42,0x68,0xc7,0x01,0x43,0x8f,0xbe,0xa2,0x42, 14918 0xfa,0x06,0x08,0x43,0x73,0xb4,0xb5,0x42,0x08,0x8e,0x2e,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0x9d,0xe3,0x08, 14919 0x43,0xd7,0x1e,0x99,0x42,0x28,0x15,0x05,0x43,0x32,0x3b,0x93,0x42,0x08,0x63,0xf0,0x04,0x43,0x70,0xed, 14920 0x8f,0x42,0x71,0x0b,0xf4,0x42,0x32,0x3b,0x93,0x42,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x07,0x1b, 14921 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x09,0x06,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,0x08,0x8e,0x55, 14922 0xc0,0x42,0xb8,0x4d,0x86,0x42,0x60,0xbf,0xd7,0x42,0x3e,0xf0,0x91,0x42,0x63,0xf6,0xe4,0x42,0x70,0xed, 14923 0x8f,0x42,0x08,0x7a,0x89,0xe5,0x42,0xac,0xc8,0x8f,0x42,0xcc,0xf7,0xe5,0x42,0xac,0xc8,0x8f,0x42,0x1b, 14924 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x07,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x08,0x63,0xf6,0xe4, 14925 0x42,0x3b,0x19,0x95,0x42,0xe6,0x61,0xe3,0x42,0x00,0x3e,0x95,0x42,0xf4,0x16,0xe2,0x42,0xc4,0x62,0x95, 14926 0x42,0x08,0x6e,0x74,0xd6,0x42,0x15,0xd1,0x95,0x42,0x97,0x63,0xca,0x42,0xaf,0xcf,0x94,0x42,0xfb,0x2d, 14927 0xbe,0x42,0x86,0x80,0x90,0x42,0x08,0x97,0x03,0xba,0x42,0xce,0x10,0x8f,0x42,0x5e,0x28,0xba,0x42,0x3e, 14928 0xf0,0x91,0x42,0xf2,0x4f,0xbc,0x42,0x45,0xf7,0x96,0x42,0x08,0x27,0x54,0xbf,0x42,0x73,0x24,0x9d,0x42, 14929 0xa5,0xe8,0xc0,0x42,0x86,0xe0,0xa0,0x42,0xe4,0xca,0xc5,0x42,0xed,0x11,0xaa,0x42,0x08,0x54,0xaa,0xc8, 14930 0x42,0x86,0x40,0xb1,0x42,0x59,0x81,0xc5,0x42,0xa1,0x11,0xc4,0x42,0x3e,0xe7,0xbf,0x42,0xfb,0x8d,0xce, 14931 0x42,0x08,0xb4,0x6d,0xb7,0x42,0x30,0xc2,0xd9,0x42,0x46,0xf5,0xc9,0x42,0xdf,0x53,0xd9,0x42,0x38,0x40, 14932 0xcb,0x42,0x62,0x8f,0xcf,0x42,0x08,0x7d,0xf9,0xcc,0x42,0xec,0xa1,0xc2,0x42,0x07,0x43,0xcd,0x42,0x6c, 14933 0xdd,0xb8,0x42,0x2b,0x8b,0xcc,0x42,0x92,0xf5,0xaf,0x42,0x08,0xf9,0x8d,0xce,0x42,0x41,0x57,0xa7,0x42, 14934 0x5b,0xb8,0xd2,0x42,0xae,0x2f,0xa5,0x42,0x18,0x2f,0xd9,0x42,0x13,0x2a,0xa1,0x42,0x08,0x41,0x7e,0xdd, 14935 0x42,0xe3,0x03,0xa0,0x42,0x2e,0xf2,0xe1,0x42,0x7c,0x02,0x9f,0x42,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e, 14936 0x42,0x07,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x08,0x4d,0x63,0xe4,0x42,0x00,0x9e,0xa5,0x42,0xf4, 14937 0x16,0xe2,0x42,0x15,0x31,0xa6,0x42,0x99,0xca,0xdf,0x42,0x2b,0xc4,0xa6,0x42,0x08,0xc0,0x82,0xc6,0x42, 14938 0xc4,0xc2,0xa5,0x42,0x57,0xe1,0xd5,0x42,0x91,0xb5,0xd0,0x42,0x54,0xda,0xd0,0x42,0x97,0x93,0xd2,0x42, 14939 0x08,0x9c,0x3a,0xc7,0x42,0x17,0x58,0xdc,0x42,0x9c,0x0a,0xbf,0x42,0x6e,0xa4,0xde,0x42,0x90,0x25,0xb8, 14940 0x42,0xdf,0x53,0xd9,0x42,0x08,0x59,0x21,0xb5,0x42,0xf2,0xdf,0xd4,0x42,0x51,0x43,0xb3,0x42,0x91,0xb5, 14941 0xd0,0x42,0xc5,0x29,0xbb,0x42,0x0e,0x1a,0xca,0x42,0x08,0x65,0x36,0xc4,0x42,0xd0,0x07,0xbd,0x42,0x3e, 14942 0xe7,0xbf,0x42,0x37,0x09,0xbe,0x42,0x0c,0xea,0xc1,0x42,0xcd,0xd0,0xaf,0x42,0x08,0x2b,0x5b,0xc4,0x42, 14943 0x18,0x08,0xa3,0x42,0x67,0xa6,0xab,0x42,0x99,0x3c,0x94,0x42,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42, 14944 0x09,]; 14945 14946 private struct ThePath { 14947 public: 14948 enum Command { 14949 Bounds, // always first, has 4 args (x0, y0, x1, y1) 14950 StrokeMode, 14951 FillMode, 14952 StrokeFillMode, 14953 NormalStroke, 14954 ThinStroke, 14955 MoveTo, 14956 LineTo, 14957 CubicTo, // cubic bezier 14958 EndPath, 14959 } 14960 14961 public: 14962 const(ubyte)[] path; 14963 uint ppos; 14964 14965 public: 14966 this (const(void)[] apath) pure nothrow @trusted @nogc { 14967 path = cast(const(ubyte)[])apath; 14968 } 14969 14970 @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (ppos >= path.length); } 14971 14972 Command getCommand () nothrow @trusted @nogc { 14973 pragma(inline, true); 14974 if (ppos >= cast(uint)path.length) assert(0, "invalid path"); 14975 return cast(Command)(path.ptr[ppos++]); 14976 } 14977 14978 // number of (x,y) pairs for this command 14979 static int argCount (in Command cmd) nothrow @safe @nogc { 14980 version(aliced) pragma(inline, true); 14981 if (cmd == Command.Bounds) return 2; 14982 else if (cmd == Command.MoveTo || cmd == Command.LineTo) return 1; 14983 else if (cmd == Command.CubicTo) return 3; 14984 else return 0; 14985 } 14986 14987 void skipArgs (int argc) nothrow @trusted @nogc { 14988 pragma(inline, true); 14989 ppos += cast(uint)(float.sizeof*2*argc); 14990 } 14991 14992 float getFloat () nothrow @trusted @nogc { 14993 pragma(inline, true); 14994 if (ppos >= cast(uint)path.length || cast(uint)path.length-ppos < float.sizeof) assert(0, "invalid path"); 14995 version(LittleEndian) { 14996 float res = *cast(const(float)*)(&path.ptr[ppos]); 14997 ppos += cast(uint)float.sizeof; 14998 return res; 14999 } else { 15000 static assert(float.sizeof == 4); 15001 uint xp = path.ptr[ppos]|(path.ptr[ppos+1]<<8)|(path.ptr[ppos+2]<<16)|(path.ptr[ppos+3]<<24); 15002 ppos += cast(uint)float.sizeof; 15003 return *cast(const(float)*)(&xp); 15004 } 15005 } 15006 } 15007 15008 // this will add baphomet's background path to the current NanoVega path, so you can fill it. 15009 public void addBaphometBack (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc { 15010 if (nvg is null) return; 15011 15012 auto path = ThePath(baphometPath); 15013 15014 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15015 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15016 15017 bool inPath = false; 15018 while (!path.empty) { 15019 auto cmd = path.getCommand(); 15020 switch (cmd) { 15021 case ThePath.Command.MoveTo: 15022 inPath = true; 15023 immutable float ex = getScaledX(); 15024 immutable float ey = getScaledY(); 15025 nvg.moveTo(ex, ey); 15026 break; 15027 case ThePath.Command.LineTo: 15028 inPath = true; 15029 immutable float ex = getScaledX(); 15030 immutable float ey = getScaledY(); 15031 nvg.lineTo(ex, ey); 15032 break; 15033 case ThePath.Command.CubicTo: // cubic bezier 15034 inPath = true; 15035 immutable float x1 = getScaledX(); 15036 immutable float y1 = getScaledY(); 15037 immutable float x2 = getScaledX(); 15038 immutable float y2 = getScaledY(); 15039 immutable float ex = getScaledX(); 15040 immutable float ey = getScaledY(); 15041 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15042 break; 15043 case ThePath.Command.EndPath: 15044 if (inPath) return; 15045 break; 15046 default: 15047 path.skipArgs(path.argCount(cmd)); 15048 break; 15049 } 15050 } 15051 } 15052 15053 // this will add baphomet's pupil paths to the current NanoVega path, so you can fill it. 15054 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 { 15055 // pupils starts with "fill-and-stroke" mode 15056 if (nvg is null) return; 15057 15058 auto path = ThePath(baphometPath); 15059 15060 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15061 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15062 15063 bool inPath = false; 15064 bool pupLeft = true; 15065 while (!path.empty) { 15066 auto cmd = path.getCommand(); 15067 switch (cmd) { 15068 case ThePath.Command.StrokeFillMode: inPath = true; break; 15069 case ThePath.Command.MoveTo: 15070 if (!inPath) goto default; 15071 static if (!left) { if (pupLeft) goto default; } 15072 static if (!right) { if (!pupLeft) goto default; } 15073 immutable float ex = getScaledX(); 15074 immutable float ey = getScaledY(); 15075 nvg.moveTo(ex, ey); 15076 break; 15077 case ThePath.Command.LineTo: 15078 if (!inPath) goto default; 15079 static if (!left) { if (pupLeft) goto default; } 15080 static if (!right) { if (!pupLeft) goto default; } 15081 immutable float ex = getScaledX(); 15082 immutable float ey = getScaledY(); 15083 nvg.lineTo(ex, ey); 15084 break; 15085 case ThePath.Command.CubicTo: // cubic bezier 15086 if (!inPath) goto default; 15087 static if (!left) { if (pupLeft) goto default; } 15088 static if (!right) { if (!pupLeft) goto default; } 15089 immutable float x1 = getScaledX(); 15090 immutable float y1 = getScaledY(); 15091 immutable float x2 = getScaledX(); 15092 immutable float y2 = getScaledY(); 15093 immutable float ex = getScaledX(); 15094 immutable float ey = getScaledY(); 15095 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15096 break; 15097 case ThePath.Command.EndPath: 15098 if (inPath) { 15099 if (pupLeft) pupLeft = false; else return; 15100 } 15101 break; 15102 default: 15103 path.skipArgs(path.argCount(cmd)); 15104 break; 15105 } 15106 } 15107 } 15108 15109 // mode: 'f' to allow fills; 's' to allow strokes; 'w' to allow stroke widths; 'c' to replace fills with strokes 15110 public void renderBaphomet(string mode="fs") (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc { 15111 template hasChar(char ch, string s) { 15112 static if (s.length == 0) enum hasChar = false; 15113 else static if (s[0] == ch) enum hasChar = true; 15114 else enum hasChar = hasChar!(ch, s[1..$]); 15115 } 15116 enum AllowStroke = hasChar!('s', mode); 15117 enum AllowFill = hasChar!('f', mode); 15118 enum AllowWidth = hasChar!('w', mode); 15119 enum Contour = hasChar!('c', mode); 15120 //static assert(AllowWidth || AllowFill); 15121 15122 if (nvg is null) return; 15123 15124 auto path = ThePath(baphometPath); 15125 15126 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15127 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15128 15129 int mode = 0; 15130 int sw = ThePath.Command.NormalStroke; 15131 nvg.beginPath(); 15132 while (!path.empty) { 15133 auto cmd = path.getCommand(); 15134 switch (cmd) { 15135 case ThePath.Command.StrokeMode: mode = ThePath.Command.StrokeMode; break; 15136 case ThePath.Command.FillMode: mode = ThePath.Command.FillMode; break; 15137 case ThePath.Command.StrokeFillMode: mode = ThePath.Command.StrokeFillMode; break; 15138 case ThePath.Command.NormalStroke: sw = ThePath.Command.NormalStroke; break; 15139 case ThePath.Command.ThinStroke: sw = ThePath.Command.ThinStroke; break; 15140 case ThePath.Command.MoveTo: 15141 immutable float ex = getScaledX(); 15142 immutable float ey = getScaledY(); 15143 nvg.moveTo(ex, ey); 15144 break; 15145 case ThePath.Command.LineTo: 15146 immutable float ex = getScaledX(); 15147 immutable float ey = getScaledY(); 15148 nvg.lineTo(ex, ey); 15149 break; 15150 case ThePath.Command.CubicTo: // cubic bezier 15151 immutable float x1 = getScaledX(); 15152 immutable float y1 = getScaledY(); 15153 immutable float x2 = getScaledX(); 15154 immutable float y2 = getScaledY(); 15155 immutable float ex = getScaledX(); 15156 immutable float ey = getScaledY(); 15157 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15158 break; 15159 case ThePath.Command.EndPath: 15160 if (mode == ThePath.Command.FillMode || mode == ThePath.Command.StrokeFillMode) { 15161 static if (AllowFill || Contour) { 15162 static if (Contour) { 15163 if (mode == ThePath.Command.FillMode) { nvg.strokeWidth = 1; nvg.stroke(); } 15164 } else { 15165 nvg.fill(); 15166 } 15167 } 15168 } 15169 if (mode == ThePath.Command.StrokeMode || mode == ThePath.Command.StrokeFillMode) { 15170 static if (AllowStroke || Contour) { 15171 static if (AllowWidth) { 15172 if (sw == ThePath.Command.NormalStroke) nvg.strokeWidth = 1; 15173 else if (sw == ThePath.Command.ThinStroke) nvg.strokeWidth = 0.5; 15174 else assert(0, "wtf?!"); 15175 } 15176 nvg.stroke(); 15177 } 15178 } 15179 nvg.newPath(); 15180 break; 15181 default: 15182 path.skipArgs(path.argCount(cmd)); 15183 break; 15184 } 15185 } 15186 nvg.newPath(); 15187 }