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 Creating drawing context 112 ======================== 113 114 The drawing context is created using platform specific constructor function. 115 116 --- 117 NVGContext vg = nvgCreateContext(); 118 --- 119 120 $(WARNING You must use created context ONLY in that thread where you created it. 121 There is no way to "transfer" context between threads. Trying to do so 122 will lead to UB.) 123 124 $(WARNING Never issue any commands outside of [beginFrame]/[endFrame]. Trying to 125 do so will lead to UB.) 126 127 128 Drawing shapes with NanoVega 129 ============================ 130 131 Drawing a simple shape using NanoVega consists of four steps: 132 $(LIST 133 * begin a new shape, 134 * define the path to draw, 135 * set fill or stroke, 136 * and finally fill or stroke the path. 137 ) 138 139 --- 140 vg.beginPath(); 141 vg.rect(100, 100, 120, 30); 142 vg.fillColor(nvgRGBA(255, 192, 0, 255)); 143 vg.fill(); 144 --- 145 146 Calling [beginPath] will clear any existing paths and start drawing from blank slate. 147 There are number of number of functions to define the path to draw, such as rectangle, 148 rounded rectangle and ellipse, or you can use the common moveTo, lineTo, bezierTo and 149 arcTo API to compose the paths step by step. 150 151 152 Understanding Composite Paths 153 ============================= 154 155 Because of the way the rendering backend is built in NanoVega, drawing a composite path, 156 that is path consisting from multiple paths defining holes and fills, is a bit more 157 involved. NanoVega uses non-zero filling rule and by default, and paths are wound in counter 158 clockwise order. Keep that in mind when drawing using the low level draw API. In order to 159 wind one of the predefined shapes as a hole, you should call `pathWinding(NVGSolidity.Hole)`, 160 or `pathWinding(NVGSolidity.Solid)` $(B after) defining the path. 161 162 --- 163 vg.beginPath(); 164 vg.rect(100, 100, 120, 30); 165 vg.circle(120, 120, 5); 166 vg.pathWinding(NVGSolidity.Hole); // mark circle as a hole 167 vg.fillColor(nvgRGBA(255, 192, 0, 255)); 168 vg.fill(); 169 --- 170 171 172 Rendering is wrong, what to do? 173 =============================== 174 175 $(LIST 176 * make sure you have created NanoVega context using [nvgCreateContext] call 177 * make sure you have initialised OpenGL with $(B stencil buffer) 178 * make sure you have cleared stencil buffer 179 * make sure all rendering calls happen between [beginFrame] and [endFrame] 180 * to enable more checks for OpenGL errors, add `NVGContextFlag.Debug` flag to [nvgCreateContext] 181 ) 182 183 184 OpenGL state touched by the backend 185 =================================== 186 187 The OpenGL back-end touches following states: 188 189 When textures are uploaded or updated, the following pixel store is set to defaults: 190 `GL_UNPACK_ALIGNMENT`, `GL_UNPACK_ROW_LENGTH`, `GL_UNPACK_SKIP_PIXELS`, `GL_UNPACK_SKIP_ROWS`. 191 Texture binding is also affected. Texture updates can happen when the user loads images, 192 or when new font glyphs are added. Glyphs are added as needed between calls to [beginFrame] 193 and [endFrame]. 194 195 The data for the whole frame is buffered and flushed in [endFrame]. 196 The following code illustrates the OpenGL state touched by the rendering code: 197 198 --- 199 glUseProgram(prog); 200 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 201 glEnable(GL_CULL_FACE); 202 glCullFace(GL_BACK); 203 glFrontFace(GL_CCW); 204 glEnable(GL_BLEND); 205 glDisable(GL_DEPTH_TEST); 206 glDisable(GL_SCISSOR_TEST); 207 glDisable(GL_COLOR_LOGIC_OP); 208 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 209 glStencilMask(0xffffffff); 210 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 211 glStencilFunc(GL_ALWAYS, 0, 0xffffffff); 212 glActiveTexture(GL_TEXTURE1); 213 glActiveTexture(GL_TEXTURE0); 214 glBindBuffer(GL_UNIFORM_BUFFER, buf); 215 glBindVertexArray(arr); 216 glBindBuffer(GL_ARRAY_BUFFER, buf); 217 glBindTexture(GL_TEXTURE_2D, tex); 218 glUniformBlockBinding(... , GLNVG_FRAG_BINDING); 219 --- 220 221 Symbol_groups: 222 223 context_management = 224 ## Context Management 225 226 Functions to create and destory NanoVega context. 227 228 frame_management = 229 ## Frame Management 230 231 To start drawing with NanoVega context, you have to "begin frame", and then 232 "end frame" to flush your rendering commands to GPU. 233 234 composite_operation = 235 ## Composite Operation 236 237 The composite operations in NanoVega are modeled after HTML Canvas API, and 238 the blend func is based on OpenGL (see corresponding manuals for more info). 239 The colors in the blending state have premultiplied alpha. 240 241 color_utils = 242 ## Color Utils 243 244 Colors in NanoVega are stored as ARGB. Zero alpha means "transparent color". 245 246 matrices = 247 ## Matrices and Transformations 248 249 The paths, gradients, patterns and scissor region are transformed by an transformation 250 matrix at the time when they are passed to the API. 251 The current transformation matrix is an affine matrix: 252 253 ---------------------- 254 [sx kx tx] 255 [ky sy ty] 256 [ 0 0 1] 257 ---------------------- 258 259 Where: (sx, sy) define scaling, (kx, ky) skewing, and (tx, ty) translation. 260 The last row is assumed to be (0, 0, 1) and is not stored. 261 262 Apart from [resetTransform], each transformation function first creates 263 specific transformation matrix and pre-multiplies the current transformation by it. 264 265 Current coordinate system (transformation) can be saved and restored using [save] and [restore]. 266 267 The following functions can be used to make calculations on 2x3 transformation matrices. 268 A 2x3 matrix is represented as float[6]. 269 270 state_handling = 271 ## State Handling 272 273 NanoVega contains state which represents how paths will be rendered. 274 The state contains transform, fill and stroke styles, text and font styles, 275 and scissor clipping. 276 277 render_styles = 278 ## Render Styles 279 280 Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern. 281 Solid color is simply defined as a color value, different kinds of paints can be created 282 using [linearGradient], [boxGradient], [radialGradient] and [imagePattern]. 283 284 Current render style can be saved and restored using [save] and [restore]. 285 286 Note that if you want "almost perfect" pixel rendering, you should set aspect ratio to 1, 287 and use `integerCoord+0.5f` as pixel coordinates. 288 289 render_transformations = 290 ## Render Transformations 291 292 Transformation matrix management for the current rendering style. Transformations are applied in 293 backwards order. I.e. if you first translate, and then rotate, your path will be rotated around 294 it's origin, and then translated to the destination point. 295 296 scissoring = 297 ## Scissoring 298 299 Scissoring allows you to clip the rendering into a rectangle. This is useful for various 300 user interface cases like rendering a text edit or a timeline. 301 302 images = 303 ## Images 304 305 NanoVega allows you to load image files in various formats (if arsd loaders are in place) to be used for rendering. 306 In addition you can upload your own image. 307 The parameter imageFlagsList is a list of flags defined in [NVGImageFlag]. 308 309 If you will use your image as fill pattern, it will be scaled by default. To make it repeat, pass 310 [NVGImageFlag.RepeatX] and [NVGImageFlag.RepeatY] flags to image creation function respectively. 311 312 paints = 313 ## Paints 314 315 NanoVega supports four types of paints: linear gradient, box gradient, radial gradient and image pattern. 316 These can be used as paints for strokes and fills. 317 318 gpu_affine = 319 ## Render-Time Affine Transformations 320 321 It is possible to set affine transformation matrix for GPU. That matrix will 322 be applied by the shader code. This can be used to quickly translate and rotate 323 saved paths. Call this $(B only) between [beginFrame] and [endFrame]. 324 325 Note that [beginFrame] resets this matrix to identity one. 326 327 $(WARNING Don't use this for scaling or skewing, or your image will be heavily distorted!) 328 329 paths = 330 ## Paths 331 332 Drawing a new shape starts with [beginPath], it clears all the currently defined paths. 333 Then you define one or more paths and sub-paths which describe the shape. The are functions 334 to draw common shapes like rectangles and circles, and lower level step-by-step functions, 335 which allow to define a path curve by curve. 336 337 NanoVega uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise 338 winding and holes should have counter clockwise order. To specify winding of a path you can 339 call [pathWinding]. This is useful especially for the common shapes, which are drawn CCW. 340 341 Finally you can fill the path using current fill style by calling [fill], and stroke it 342 with current stroke style by calling [stroke]. 343 344 The curve segments and sub-paths are transformed by the current transform. 345 346 picking_api = 347 ## Picking API 348 349 This is picking API that works directly on paths, without rasterizing them first. 350 351 [beginFrame] resets picking state. Then you can create paths as usual, but 352 there is a possibility to perform hit checks $(B before) rasterizing a path. 353 Call either id assigning functions ([currFillHitId]/[currStrokeHitId]), or 354 immediate hit test functions ([hitTestCurrFill]/[hitTestCurrStroke]) 355 before rasterizing (i.e. calling [fill] or [stroke]) to perform hover 356 effects, for example. 357 358 Also note that picking API is ignoring GPU affine transformation matrix. 359 You can "untransform" picking coordinates before checking with [gpuUntransformPoint]. 360 361 $(WARNING Picking API completely ignores clipping. If you want to check for 362 clip regions, you have to manually register them as fill/stroke paths, 363 and perform the necessary logic. See [hitTestForId] function.) 364 365 clipping = 366 ## Clipping with paths 367 368 If scissoring is not enough for you, you can clip rendering with arbitrary path, 369 or with combination of paths. Clip region is saved by [save] and restored by 370 [restore] NanoVega functions. You can combine clip paths with various logic 371 operations, see [NVGClipMode]. 372 373 Note that both [clip] and [clipStroke] are ignoring scissoring (i.e. clip mask 374 is created as if there was no scissor set). Actual rendering is affected by 375 scissors, though. 376 377 text_api = 378 ## Text 379 380 NanoVega allows you to load .ttf files and use the font to render text. 381 You have to load some font, and set proper font size before doing anything 382 with text, as there is no "default" font provided by NanoVega. Also, don't 383 forget to check return value of `createFont()`, 'cause NanoVega won't fail 384 if it cannot load font, it will silently try to render nothing. 385 386 The appearance of the text can be defined by setting the current text style 387 and by specifying the fill color. Common text and font settings such as 388 font size, letter spacing and text align are supported. Font blur allows you 389 to create simple text effects such as drop shadows. 390 391 At render time the font face can be set based on the font handles or name. 392 393 Font measure functions return values in local space, the calculations are 394 carried in the same resolution as the final rendering. This is done because 395 the text glyph positions are snapped to the nearest pixels sharp rendering. 396 397 The local space means that values are not rotated or scale as per the current 398 transformation. For example if you set font size to 12, which would mean that 399 line height is 16, then regardless of the current scaling and rotation, the 400 returned line height is always 16. Some measures may vary because of the scaling 401 since aforementioned pixel snapping. 402 403 While this may sound a little odd, the setup allows you to always render the 404 same way regardless of scaling. I.e. following works regardless of scaling: 405 406 ---------------------- 407 string txt = "Text me up."; 408 vg.textBounds(x, y, txt, bounds); 409 vg.beginPath(); 410 vg.roundedRect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1], 6); 411 vg.fill(); 412 ---------------------- 413 414 Note: currently only solid color fill is supported for text. 415 416 font_stash = 417 ## Low-Level Font Engine (FontStash) 418 419 FontStash is used to load fonts, to manage font atlases, and to get various text metrics. 420 You don't need any graphics context to use FontStash, so you can do things like text 421 layouting outside of your rendering code. Loaded fonts are refcounted, so it is cheap 422 to create new FontStash, copy fonts from NanoVega context into it, and use that new 423 FontStash to do some UI layouting, for example. Also note that you can get text metrics 424 without creating glyph bitmaps (by using [FONSTextBoundsIterator], for example); this way 425 you don't need to waste CPU and memory resources to render unneeded images into font atlas, 426 and you can layout alot of text very fast. 427 428 Note that "FontStash" is abbrevated as "FONS". So when you see some API that contains 429 word "fons" in it, this is not a typo, and it should not read "font" intead. 430 431 TODO for Ketmar: write some nice example code here, and finish documenting FontStash API. 432 */ 433 module arsd.nanovega; 434 435 /// This example shows how to do the NanoVega sample without the [NVGWindow] helper class. 436 unittest { 437 import arsd.simpledisplay; 438 439 import arsd.nanovega; 440 441 void main () { 442 NVGContext nvg; // our NanoVega context 443 444 // we need at least OpenGL3 with GLSL to use NanoVega, 445 // so let's tell simpledisplay about that 446 setOpenGLContextVersion(3, 0); 447 448 // now create OpenGL window 449 auto sdmain = new SimpleWindow(800, 600, "NanoVega Simple Sample", OpenGlOptions.yes, Resizability.allowResizing); 450 451 // we need to destroy NanoVega context on window close 452 // stricly speaking, it is not necessary, as nothing fatal 453 // will happen if you'll forget it, but let's be polite. 454 // note that we cannot do that *after* our window was closed, 455 // as we need alive OpenGL context to do proper cleanup. 456 sdmain.onClosing = delegate () { 457 nvg.kill(); 458 }; 459 460 // this is called just before our window will be shown for the first time. 461 // we must create NanoVega context here, as it needs to initialize 462 // internal OpenGL subsystem with valid OpenGL context. 463 sdmain.visibleForTheFirstTime = delegate () { 464 // yes, that's all 465 nvg = nvgCreateContext(); 466 if (nvg is null) assert(0, "cannot initialize NanoVega"); 467 }; 468 469 // this callback will be called when we will need to repaint our window 470 sdmain.redrawOpenGlScene = delegate () { 471 // fix viewport (we can do this in resize event, or here, it doesn't matter) 472 glViewport(0, 0, sdmain.width, sdmain.height); 473 474 // clear window 475 glClearColor(0, 0, 0, 0); 476 glClear(glNVGClearFlags); // use NanoVega API to get flags for OpenGL call 477 478 { 479 nvg.beginFrame(sdmain.width, sdmain.height); // begin rendering 480 scope(exit) nvg.endFrame(); // and flush render queue on exit 481 482 nvg.beginPath(); // start new path 483 nvg.roundedRect(20.5, 30.5, sdmain.width-40, sdmain.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation) 484 // now set filling mode for our rectangle 485 // you can create colors using HTML syntax, or with convenient constants 486 nvg.fillPaint = nvg.linearGradient(20.5, 30.5, sdmain.width-40, sdmain.height-60, NVGColor("#f70"), NVGColor.green); 487 // now fill our rect 488 nvg.fill(); 489 // and draw a nice outline 490 nvg.strokeColor = NVGColor.white; 491 nvg.strokeWidth = 2; 492 nvg.stroke(); 493 // that's all, folks! 494 } 495 }; 496 497 sdmain.eventLoop(0, // no pulse timer required 498 delegate (KeyEvent event) { 499 if (event == "*-Q" || event == "Escape") { sdmain.close(); return; } // quit on Q, Ctrl+Q, and so on 500 }, 501 ); 502 503 flushGui(); // let OS do it's cleanup 504 } 505 } 506 507 private: 508 509 version(aliced) { 510 import iv.meta; 511 import iv.vfs; 512 } else { 513 private alias usize = size_t; 514 // i fear phobos! 515 private template Unqual(T) { 516 static if (is(T U == immutable U)) alias Unqual = U; 517 else static if (is(T U == shared inout const U)) alias Unqual = U; 518 else static if (is(T U == shared inout U)) alias Unqual = U; 519 else static if (is(T U == shared const U)) alias Unqual = U; 520 else static if (is(T U == shared U)) alias Unqual = U; 521 else static if (is(T U == inout const U)) alias Unqual = U; 522 else static if (is(T U == inout U)) alias Unqual = U; 523 else static if (is(T U == const U)) alias Unqual = U; 524 else alias Unqual = T; 525 } 526 private template isAnyCharType(T, bool unqual=false) { 527 static if (unqual) private alias UT = Unqual!T; else private alias UT = T; 528 enum isAnyCharType = is(UT == char) || is(UT == wchar) || is(UT == dchar); 529 } 530 private template isWideCharType(T, bool unqual=false) { 531 static if (unqual) private alias UT = Unqual!T; else private alias UT = T; 532 enum isWideCharType = is(UT == wchar) || is(UT == dchar); 533 } 534 } 535 version(nanovg_disable_vfs) { 536 enum NanoVegaHasIVVFS = false; 537 } else { 538 static if (is(typeof((){import iv.vfs;}))) { 539 enum NanoVegaHasIVVFS = true; 540 import iv.vfs; 541 } else { 542 enum NanoVegaHasIVVFS = false; 543 } 544 } 545 546 // ////////////////////////////////////////////////////////////////////////// // 547 // engine 548 // ////////////////////////////////////////////////////////////////////////// // 549 import core.stdc.stdlib : malloc, realloc, free; 550 import core.stdc.string : memset, memcpy, strlen; 551 import std.math : PI; 552 553 //version = nanovg_force_stb_ttf; 554 555 version(Posix) { 556 version = nanovg_use_freetype; 557 } else { 558 version = nanovg_disable_fontconfig; 559 } 560 561 version (bindbc) { 562 version = nanovg_builtin_fontconfig_bindings; 563 version = nanovg_bindbc_opengl_bindings; 564 version = nanovg_bindbc_freetype_bindings; 565 version(BindFT_Dynamic) 566 static assert(0, "AsumFace was too lazy to write the code for the dynamic bindbc freetype bindings"); 567 else { 568 version(BindFT_Static) {} 569 else 570 static assert(0, "well, duh. you got to pass the BindFT_Static version identifier to the compiler"); 571 } 572 } else version(aliced) { 573 version = nanovg_default_no_font_aa; 574 version = nanovg_builtin_fontconfig_bindings; 575 version = nanovg_builtin_freetype_bindings; 576 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings 577 } else { 578 version = nanovg_builtin_fontconfig_bindings; 579 version = nanovg_builtin_freetype_bindings; 580 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings 581 } 582 583 version(nanovg_disable_fontconfig) { 584 public enum NanoVegaHasFontConfig = false; 585 } else { 586 public enum NanoVegaHasFontConfig = true; 587 version(nanovg_builtin_fontconfig_bindings) {} else import iv.fontconfig; 588 } 589 590 //version = nanovg_bench_flatten; 591 592 /++ 593 Annotation to indicate the marked function is compatible with [arsd.script]. 594 595 596 Any function that takes a [Color] argument will be passed a string instead. 597 598 Scriptable Functions 599 ==================== 600 601 $(UDA_USES) 602 603 $(ALWAYS_DOCUMENT) 604 +/ 605 private enum scriptable = "arsd_jsvar_compatible"; 606 607 public: 608 alias NVG_PI = PI; 609 610 enum NanoVegaHasArsdColor = (is(typeof((){ import arsd.color; }))); 611 enum NanoVegaHasArsdImage = (is(typeof((){ import arsd.color; import arsd.image; }))); 612 613 static if (NanoVegaHasArsdColor) private import arsd.color; 614 static if (NanoVegaHasArsdImage) { 615 private import arsd.image; 616 } else { 617 void stbi_set_unpremultiply_on_load (int flag_true_if_should_unpremultiply) {} 618 void stbi_convert_iphone_png_to_rgb (int flag_true_if_should_convert) {} 619 ubyte* stbi_load (const(char)* filename, int* x, int* y, int* comp, int req_comp) { return null; } 620 ubyte* stbi_load_from_memory (const(void)* buffer, int len, int* x, int* y, int* comp, int req_comp) { return null; } 621 void stbi_image_free (void* retval_from_stbi_load) {} 622 } 623 624 version(nanovg_default_no_font_aa) { 625 __gshared bool NVG_INVERT_FONT_AA = false; 626 } else { 627 __gshared bool NVG_INVERT_FONT_AA = true; 628 } 629 630 631 /// this is branchless for ints on x86, and even for longs on x86_64 632 public ubyte nvgClampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) { 633 static if (__VERSION__ > 2067) pragma(inline, true); 634 static if (T.sizeof == 2 || T.sizeof == 4) { 635 static if (__traits(isUnsigned, T)) { 636 return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24))); 637 } else { 638 n &= -cast(int)(n >= 0); 639 return cast(ubyte)(n|((255-cast(int)n)>>31)); 640 } 641 } else static if (T.sizeof == 1) { 642 static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?"); 643 return cast(ubyte)n; 644 } else static if (T.sizeof == 8) { 645 static if (__traits(isUnsigned, T)) { 646 return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56))); 647 } else { 648 n &= -cast(long)(n >= 0); 649 return cast(ubyte)(n|((255-cast(long)n)>>63)); 650 } 651 } else { 652 static assert(false, "clampToByte: integer too big"); 653 } 654 } 655 656 657 /// NanoVega RGBA color 658 /// Group: color_utils 659 public align(1) struct NVGColor { 660 align(1): 661 public: 662 float[4] rgba = 0; /// default color is transparent (a=1 is opaque) 663 664 public: 665 @property string toString () const @safe { import std.string : format; return "NVGColor(%s,%s,%s,%s)".format(r, g, b, a); } 666 667 public: 668 enum transparent = NVGColor(0.0f, 0.0f, 0.0f, 0.0f); 669 enum k8orange = NVGColor(1.0f, 0.5f, 0.0f, 1.0f); 670 671 enum aliceblue = NVGColor(240, 248, 255); 672 enum antiquewhite = NVGColor(250, 235, 215); 673 enum aqua = NVGColor(0, 255, 255); 674 enum aquamarine = NVGColor(127, 255, 212); 675 enum azure = NVGColor(240, 255, 255); 676 enum beige = NVGColor(245, 245, 220); 677 enum bisque = NVGColor(255, 228, 196); 678 enum black = NVGColor(0, 0, 0); // basic color 679 enum blanchedalmond = NVGColor(255, 235, 205); 680 enum blue = NVGColor(0, 0, 255); // basic color 681 enum blueviolet = NVGColor(138, 43, 226); 682 enum brown = NVGColor(165, 42, 42); 683 enum burlywood = NVGColor(222, 184, 135); 684 enum cadetblue = NVGColor(95, 158, 160); 685 enum chartreuse = NVGColor(127, 255, 0); 686 enum chocolate = NVGColor(210, 105, 30); 687 enum coral = NVGColor(255, 127, 80); 688 enum cornflowerblue = NVGColor(100, 149, 237); 689 enum cornsilk = NVGColor(255, 248, 220); 690 enum crimson = NVGColor(220, 20, 60); 691 enum cyan = NVGColor(0, 255, 255); // basic color 692 enum darkblue = NVGColor(0, 0, 139); 693 enum darkcyan = NVGColor(0, 139, 139); 694 enum darkgoldenrod = NVGColor(184, 134, 11); 695 enum darkgray = NVGColor(169, 169, 169); 696 enum darkgreen = NVGColor(0, 100, 0); 697 enum darkgrey = NVGColor(169, 169, 169); 698 enum darkkhaki = NVGColor(189, 183, 107); 699 enum darkmagenta = NVGColor(139, 0, 139); 700 enum darkolivegreen = NVGColor(85, 107, 47); 701 enum darkorange = NVGColor(255, 140, 0); 702 enum darkorchid = NVGColor(153, 50, 204); 703 enum darkred = NVGColor(139, 0, 0); 704 enum darksalmon = NVGColor(233, 150, 122); 705 enum darkseagreen = NVGColor(143, 188, 143); 706 enum darkslateblue = NVGColor(72, 61, 139); 707 enum darkslategray = NVGColor(47, 79, 79); 708 enum darkslategrey = NVGColor(47, 79, 79); 709 enum darkturquoise = NVGColor(0, 206, 209); 710 enum darkviolet = NVGColor(148, 0, 211); 711 enum deeppink = NVGColor(255, 20, 147); 712 enum deepskyblue = NVGColor(0, 191, 255); 713 enum dimgray = NVGColor(105, 105, 105); 714 enum dimgrey = NVGColor(105, 105, 105); 715 enum dodgerblue = NVGColor(30, 144, 255); 716 enum firebrick = NVGColor(178, 34, 34); 717 enum floralwhite = NVGColor(255, 250, 240); 718 enum forestgreen = NVGColor(34, 139, 34); 719 enum fuchsia = NVGColor(255, 0, 255); 720 enum gainsboro = NVGColor(220, 220, 220); 721 enum ghostwhite = NVGColor(248, 248, 255); 722 enum gold = NVGColor(255, 215, 0); 723 enum goldenrod = NVGColor(218, 165, 32); 724 enum gray = NVGColor(128, 128, 128); // basic color 725 enum green = NVGColor(0, 128, 0); // basic color 726 enum greenyellow = NVGColor(173, 255, 47); 727 enum grey = NVGColor(128, 128, 128); // basic color 728 enum honeydew = NVGColor(240, 255, 240); 729 enum hotpink = NVGColor(255, 105, 180); 730 enum indianred = NVGColor(205, 92, 92); 731 enum indigo = NVGColor(75, 0, 130); 732 enum ivory = NVGColor(255, 255, 240); 733 enum khaki = NVGColor(240, 230, 140); 734 enum lavender = NVGColor(230, 230, 250); 735 enum lavenderblush = NVGColor(255, 240, 245); 736 enum lawngreen = NVGColor(124, 252, 0); 737 enum lemonchiffon = NVGColor(255, 250, 205); 738 enum lightblue = NVGColor(173, 216, 230); 739 enum lightcoral = NVGColor(240, 128, 128); 740 enum lightcyan = NVGColor(224, 255, 255); 741 enum lightgoldenrodyellow = NVGColor(250, 250, 210); 742 enum lightgray = NVGColor(211, 211, 211); 743 enum lightgreen = NVGColor(144, 238, 144); 744 enum lightgrey = NVGColor(211, 211, 211); 745 enum lightpink = NVGColor(255, 182, 193); 746 enum lightsalmon = NVGColor(255, 160, 122); 747 enum lightseagreen = NVGColor(32, 178, 170); 748 enum lightskyblue = NVGColor(135, 206, 250); 749 enum lightslategray = NVGColor(119, 136, 153); 750 enum lightslategrey = NVGColor(119, 136, 153); 751 enum lightsteelblue = NVGColor(176, 196, 222); 752 enum lightyellow = NVGColor(255, 255, 224); 753 enum lime = NVGColor(0, 255, 0); 754 enum limegreen = NVGColor(50, 205, 50); 755 enum linen = NVGColor(250, 240, 230); 756 enum magenta = NVGColor(255, 0, 255); // basic color 757 enum maroon = NVGColor(128, 0, 0); 758 enum mediumaquamarine = NVGColor(102, 205, 170); 759 enum mediumblue = NVGColor(0, 0, 205); 760 enum mediumorchid = NVGColor(186, 85, 211); 761 enum mediumpurple = NVGColor(147, 112, 219); 762 enum mediumseagreen = NVGColor(60, 179, 113); 763 enum mediumslateblue = NVGColor(123, 104, 238); 764 enum mediumspringgreen = NVGColor(0, 250, 154); 765 enum mediumturquoise = NVGColor(72, 209, 204); 766 enum mediumvioletred = NVGColor(199, 21, 133); 767 enum midnightblue = NVGColor(25, 25, 112); 768 enum mintcream = NVGColor(245, 255, 250); 769 enum mistyrose = NVGColor(255, 228, 225); 770 enum moccasin = NVGColor(255, 228, 181); 771 enum navajowhite = NVGColor(255, 222, 173); 772 enum navy = NVGColor(0, 0, 128); 773 enum oldlace = NVGColor(253, 245, 230); 774 enum olive = NVGColor(128, 128, 0); 775 enum olivedrab = NVGColor(107, 142, 35); 776 enum orange = NVGColor(255, 165, 0); 777 enum orangered = NVGColor(255, 69, 0); 778 enum orchid = NVGColor(218, 112, 214); 779 enum palegoldenrod = NVGColor(238, 232, 170); 780 enum palegreen = NVGColor(152, 251, 152); 781 enum paleturquoise = NVGColor(175, 238, 238); 782 enum palevioletred = NVGColor(219, 112, 147); 783 enum papayawhip = NVGColor(255, 239, 213); 784 enum peachpuff = NVGColor(255, 218, 185); 785 enum peru = NVGColor(205, 133, 63); 786 enum pink = NVGColor(255, 192, 203); 787 enum plum = NVGColor(221, 160, 221); 788 enum powderblue = NVGColor(176, 224, 230); 789 enum purple = NVGColor(128, 0, 128); 790 enum red = NVGColor(255, 0, 0); // basic color 791 enum rosybrown = NVGColor(188, 143, 143); 792 enum royalblue = NVGColor(65, 105, 225); 793 enum saddlebrown = NVGColor(139, 69, 19); 794 enum salmon = NVGColor(250, 128, 114); 795 enum sandybrown = NVGColor(244, 164, 96); 796 enum seagreen = NVGColor(46, 139, 87); 797 enum seashell = NVGColor(255, 245, 238); 798 enum sienna = NVGColor(160, 82, 45); 799 enum silver = NVGColor(192, 192, 192); 800 enum skyblue = NVGColor(135, 206, 235); 801 enum slateblue = NVGColor(106, 90, 205); 802 enum slategray = NVGColor(112, 128, 144); 803 enum slategrey = NVGColor(112, 128, 144); 804 enum snow = NVGColor(255, 250, 250); 805 enum springgreen = NVGColor(0, 255, 127); 806 enum steelblue = NVGColor(70, 130, 180); 807 enum tan = NVGColor(210, 180, 140); 808 enum teal = NVGColor(0, 128, 128); 809 enum thistle = NVGColor(216, 191, 216); 810 enum tomato = NVGColor(255, 99, 71); 811 enum turquoise = NVGColor(64, 224, 208); 812 enum violet = NVGColor(238, 130, 238); 813 enum wheat = NVGColor(245, 222, 179); 814 enum white = NVGColor(255, 255, 255); // basic color 815 enum whitesmoke = NVGColor(245, 245, 245); 816 enum yellow = NVGColor(255, 255, 0); // basic color 817 enum yellowgreen = NVGColor(154, 205, 50); 818 819 nothrow @safe @nogc: 820 public: 821 /// 822 this (ubyte ar, ubyte ag, ubyte ab, ubyte aa=255) pure { 823 pragma(inline, true); 824 r = ar/255.0f; 825 g = ag/255.0f; 826 b = ab/255.0f; 827 a = aa/255.0f; 828 } 829 830 /// 831 this (float ar, float ag, float ab, float aa=1.0f) pure { 832 pragma(inline, true); 833 r = ar; 834 g = ag; 835 b = ab; 836 a = aa; 837 } 838 839 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 840 this (uint c) pure { 841 pragma(inline, true); 842 r = (c&0xff)/255.0f; 843 g = ((c>>8)&0xff)/255.0f; 844 b = ((c>>16)&0xff)/255.0f; 845 a = ((c>>24)&0xff)/255.0f; 846 } 847 848 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb" 849 this (const(char)[] srgb) { 850 static int c2d (char ch) pure nothrow @safe @nogc { 851 pragma(inline, true); 852 return 853 ch >= '0' && ch <= '9' ? ch-'0' : 854 ch >= 'A' && ch <= 'F' ? ch-'A'+10 : 855 ch >= 'a' && ch <= 'f' ? ch-'a'+10 : 856 -1; 857 } 858 int[8] digs; 859 int dc = -1; 860 foreach (immutable char ch; srgb) { 861 if (ch <= ' ') continue; 862 if (ch == '#') { 863 if (dc != -1) { dc = -1; break; } 864 dc = 0; 865 } else { 866 if (dc >= digs.length) { dc = -1; break; } 867 if ((digs[dc++] = c2d(ch)) < 0) { dc = -1; break; } 868 } 869 } 870 switch (dc) { 871 case 3: // rgb 872 a = 1.0f; 873 r = digs[0]/15.0f; 874 g = digs[1]/15.0f; 875 b = digs[2]/15.0f; 876 break; 877 case 4: // argb 878 a = digs[0]/15.0f; 879 r = digs[1]/15.0f; 880 g = digs[2]/15.0f; 881 b = digs[3]/15.0f; 882 break; 883 case 6: // rrggbb 884 a = 1.0f; 885 r = (digs[0]*16+digs[1])/255.0f; 886 g = (digs[2]*16+digs[3])/255.0f; 887 b = (digs[4]*16+digs[5])/255.0f; 888 break; 889 case 8: // aarrggbb 890 a = (digs[0]*16+digs[1])/255.0f; 891 r = (digs[2]*16+digs[3])/255.0f; 892 g = (digs[4]*16+digs[5])/255.0f; 893 b = (digs[6]*16+digs[7])/255.0f; 894 break; 895 default: 896 break; 897 } 898 } 899 900 /// Is this color completely opaque? 901 @property bool isOpaque () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] >= 1.0f); } 902 /// Is this color completely transparent? 903 @property bool isTransparent () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] <= 0.0f); } 904 905 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 906 @property uint asUint () const pure { 907 pragma(inline, true); 908 return 909 cast(uint)(r*255)| 910 (cast(uint)(g*255)<<8)| 911 (cast(uint)(b*255)<<16)| 912 (cast(uint)(a*255)<<24); 913 } 914 915 alias asUintABGR = asUint; /// Ditto. 916 917 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 918 static NVGColor fromUint (uint c) pure { pragma(inline, true); return NVGColor(c); } 919 920 alias fromUintABGR = fromUint; /// Ditto. 921 922 /// AARRGGBB 923 @property uint asUintARGB () const pure { 924 pragma(inline, true); 925 return 926 cast(uint)(b*255)| 927 (cast(uint)(g*255)<<8)| 928 (cast(uint)(r*255)<<16)| 929 (cast(uint)(a*255)<<24); 930 } 931 932 /// AARRGGBB 933 static NVGColor fromUintARGB (uint c) pure { pragma(inline, true); return NVGColor((c>>16)&0xff, (c>>8)&0xff, c&0xff, (c>>24)&0xff); } 934 935 @property ref inout(float) r () inout pure @trusted { pragma(inline, true); return rgba.ptr[0]; } /// 936 @property ref inout(float) g () inout pure @trusted { pragma(inline, true); return rgba.ptr[1]; } /// 937 @property ref inout(float) b () inout pure @trusted { pragma(inline, true); return rgba.ptr[2]; } /// 938 @property ref inout(float) a () inout pure @trusted { pragma(inline, true); return rgba.ptr[3]; } /// 939 940 ref NVGColor applyTint() (in auto ref NVGColor tint) nothrow @trusted @nogc { 941 if (tint.a == 0) return this; 942 foreach (immutable idx, ref float v; rgba[0..4]) { 943 v = nvg__clamp(v*tint.rgba.ptr[idx], 0.0f, 1.0f); 944 } 945 return this; 946 } 947 948 NVGHSL asHSL() (bool useWeightedLightness=false) const { pragma(inline, true); return NVGHSL.fromColor(this, useWeightedLightness); } /// 949 static fromHSL() (in auto ref NVGHSL hsl) { pragma(inline, true); return hsl.asColor; } /// 950 951 static if (NanoVegaHasArsdColor) { 952 Color toArsd () const { pragma(inline, true); return Color(cast(int)(r*255), cast(int)(g*255), cast(int)(b*255), cast(int)(a*255)); } /// 953 static NVGColor fromArsd (in Color c) { pragma(inline, true); return NVGColor(c.r, c.g, c.b, c.a); } /// 954 /// 955 this (in Color c) { 956 version(aliced) pragma(inline, true); 957 r = c.r/255.0f; 958 g = c.g/255.0f; 959 b = c.b/255.0f; 960 a = c.a/255.0f; 961 } 962 } 963 } 964 965 966 /// NanoVega A-HSL color 967 /// Group: color_utils 968 public align(1) struct NVGHSL { 969 align(1): 970 float h=0, s=0, l=1, a=1; /// 971 972 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)); } 973 974 nothrow @safe @nogc: 975 public: 976 /// 977 this (float ah, float as, float al, float aa=1) pure { pragma(inline, true); h = ah; s = as; l = al; a = aa; } 978 979 NVGColor asColor () const { pragma(inline, true); return nvgHSLA(h, s, l, a); } /// 980 981 // taken from Adam's arsd.color 982 /** Converts an RGB color into an HSL triplet. 983 * [useWeightedLightness] will try to get a better value for luminosity for the human eye, 984 * which is more sensitive to green than red and more to red than blue. 985 * If it is false, it just does average of the rgb. */ 986 static NVGHSL fromColor() (in auto ref NVGColor c, bool useWeightedLightness=false) pure { 987 NVGHSL res; 988 res.a = c.a; 989 float r1 = c.r; 990 float g1 = c.g; 991 float b1 = c.b; 992 993 float maxColor = r1; 994 if (g1 > maxColor) maxColor = g1; 995 if (b1 > maxColor) maxColor = b1; 996 float minColor = r1; 997 if (g1 < minColor) minColor = g1; 998 if (b1 < minColor) minColor = b1; 999 1000 res.l = (maxColor+minColor)/2; 1001 if (useWeightedLightness) { 1002 // the colors don't affect the eye equally 1003 // this is a little more accurate than plain HSL numbers 1004 res.l = 0.2126*r1+0.7152*g1+0.0722*b1; 1005 } 1006 if (maxColor != minColor) { 1007 if (res.l < 0.5) { 1008 res.s = (maxColor-minColor)/(maxColor+minColor); 1009 } else { 1010 res.s = (maxColor-minColor)/(2.0-maxColor-minColor); 1011 } 1012 if (r1 == maxColor) { 1013 res.h = (g1-b1)/(maxColor-minColor); 1014 } else if(g1 == maxColor) { 1015 res.h = 2.0+(b1-r1)/(maxColor-minColor); 1016 } else { 1017 res.h = 4.0+(r1-g1)/(maxColor-minColor); 1018 } 1019 } 1020 1021 res.h = res.h*60; 1022 if (res.h < 0) res.h += 360; 1023 res.h /= 360; 1024 1025 return res; 1026 } 1027 } 1028 1029 1030 //version = nanovega_debug_image_manager; 1031 //version = nanovega_debug_image_manager_rc; 1032 1033 /** NanoVega image handle. 1034 * 1035 * This is refcounted struct, so you don't need to do anything special to free it once it is allocated. 1036 * 1037 * Group: images 1038 */ 1039 struct NVGImage { 1040 enum isOpaqueStruct = true; 1041 private: 1042 NVGContext ctx; 1043 int id; // backend image id 1044 1045 public: 1046 /// 1047 this() (in auto ref NVGImage src) nothrow @trusted @nogc { 1048 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); } 1049 if (src.id > 0 && src.ctx !is null) { 1050 ctx = cast(NVGContext)src.ctx; 1051 id = src.id; 1052 ctx.nvg__imageIncRef(id); 1053 } 1054 } 1055 1056 /// 1057 ~this () nothrow @trusted @nogc { version(aliced) pragma(inline, true); clear(); } 1058 1059 /// 1060 this (this) nothrow @trusted @nogc { 1061 version(aliced) pragma(inline, true); 1062 if (id > 0 && ctx !is null) { 1063 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p postblit (imgid=%d)\n", &this, id); } 1064 ctx.nvg__imageIncRef(id); 1065 } 1066 } 1067 1068 /// 1069 void opAssign() (in auto ref NVGImage src) nothrow @trusted @nogc { 1070 if (src.id <= 0 || src.ctx is null) { 1071 clear(); 1072 } else { 1073 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); } 1074 if (src.id > 0 && src.ctx !is null) (cast(NVGContext)src.ctx).nvg__imageIncRef(src.id); 1075 if (id > 0 && ctx !is null) ctx.nvg__imageDecRef(id); 1076 ctx = cast(NVGContext)src.ctx; 1077 id = src.id; 1078 } 1079 } 1080 1081 /// Free this image. 1082 void clear () nothrow @trusted @nogc { 1083 if (id > 0 && ctx !is null) { 1084 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p cleared (imgid=%d)\n", &this, id); } 1085 ctx.nvg__imageDecRef(id); 1086 } 1087 id = 0; 1088 ctx = null; 1089 } 1090 1091 /// Is this image valid? 1092 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (id > 0 && ctx.valid); } 1093 1094 /// Is the given image valid and comes from the same context? 1095 @property bool isSameContext (const(NVGContext) actx) const pure nothrow @safe @nogc { pragma(inline, true); return (actx !is null && ctx is actx); } 1096 1097 /// Returns image width, or zero for invalid image. 1098 int width () const nothrow @trusted @nogc { 1099 int w = 0; 1100 if (valid) { 1101 int h = void; 1102 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h); 1103 } 1104 return w; 1105 } 1106 1107 /// Returns image height, or zero for invalid image. 1108 int height () const nothrow @trusted @nogc { 1109 int h = 0; 1110 if (valid) { 1111 int w = void; 1112 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h); 1113 } 1114 return h; 1115 } 1116 } 1117 1118 1119 /// Paint parameters for various fills. Don't change anything here! 1120 /// Group: render_styles 1121 public struct NVGPaint { 1122 enum isOpaqueStruct = true; 1123 1124 NVGMatrix xform; 1125 float[2] extent = 0.0f; 1126 float radius = 0.0f; 1127 float feather = 0.0f; 1128 NVGColor innerColor; /// this can be used to modulate images (fill/font) 1129 NVGColor middleColor; 1130 NVGColor outerColor; 1131 float midp = -1; // middle stop for 3-color gradient 1132 NVGImage image; 1133 bool simpleColor; /// if `true`, only innerColor is used, and this is solid-color paint 1134 1135 this() (in auto ref NVGPaint p) nothrow @trusted @nogc { 1136 xform = p.xform; 1137 extent[] = p.extent[]; 1138 radius = p.radius; 1139 feather = p.feather; 1140 innerColor = p.innerColor; 1141 middleColor = p.middleColor; 1142 midp = p.midp; 1143 outerColor = p.outerColor; 1144 image = p.image; 1145 simpleColor = p.simpleColor; 1146 } 1147 1148 void opAssign() (in auto ref NVGPaint p) nothrow @trusted @nogc { 1149 xform = p.xform; 1150 extent[] = p.extent[]; 1151 radius = p.radius; 1152 feather = p.feather; 1153 innerColor = p.innerColor; 1154 middleColor = p.middleColor; 1155 midp = p.midp; 1156 outerColor = p.outerColor; 1157 image = p.image; 1158 simpleColor = p.simpleColor; 1159 } 1160 1161 void clear () nothrow @trusted @nogc { 1162 version(aliced) pragma(inline, true); 1163 import core.stdc.string : memset; 1164 image.clear(); 1165 memset(&this, 0, this.sizeof); 1166 simpleColor = true; 1167 } 1168 } 1169 1170 /// Path winding. 1171 /// Group: paths 1172 public enum NVGWinding { 1173 CCW = 1, /// Winding for solid shapes 1174 CW = 2, /// Winding for holes 1175 } 1176 1177 /// Path solidity. 1178 /// Group: paths 1179 public enum NVGSolidity { 1180 Solid = 1, /// Solid shape (CCW winding). 1181 Hole = 2, /// Hole (CW winding). 1182 } 1183 1184 /// Line cap style. 1185 /// Group: render_styles 1186 public enum NVGLineCap { 1187 Butt, /// 1188 Round, /// 1189 Square, /// 1190 Bevel, /// 1191 Miter, /// 1192 } 1193 1194 /// Text align. 1195 /// Group: text_api 1196 public align(1) struct NVGTextAlign { 1197 align(1): 1198 /// Horizontal align. 1199 enum H : ubyte { 1200 Left = 0, /// Default, align text horizontally to left. 1201 Center = 1, /// Align text horizontally to center. 1202 Right = 2, /// Align text horizontally to right. 1203 } 1204 1205 /// Vertical align. 1206 enum V : ubyte { 1207 Baseline = 0, /// Default, align text vertically to baseline. 1208 Top = 1, /// Align text vertically to top. 1209 Middle = 2, /// Align text vertically to middle. 1210 Bottom = 3, /// Align text vertically to bottom. 1211 } 1212 1213 pure nothrow @safe @nogc: 1214 public: 1215 this (H h) { pragma(inline, true); value = h; } /// 1216 this (V v) { pragma(inline, true); value = cast(ubyte)(v<<4); } /// 1217 this (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1218 this (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1219 void reset () { pragma(inline, true); value = 0; } /// 1220 void reset (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1221 void reset (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1222 @property: 1223 bool left () const { pragma(inline, true); return ((value&0x0f) == H.Left); } /// 1224 void left (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Left : 0)); } /// 1225 bool center () const { pragma(inline, true); return ((value&0x0f) == H.Center); } /// 1226 void center (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Center : 0)); } /// 1227 bool right () const { pragma(inline, true); return ((value&0x0f) == H.Right); } /// 1228 void right (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Right : 0)); } /// 1229 // 1230 bool baseline () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Baseline); } /// 1231 void baseline (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Baseline<<4 : 0)); } /// 1232 bool top () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Top); } /// 1233 void top (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Top<<4 : 0)); } /// 1234 bool middle () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Middle); } /// 1235 void middle (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Middle<<4 : 0)); } /// 1236 bool bottom () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Bottom); } /// 1237 void bottom (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Bottom<<4 : 0)); } /// 1238 // 1239 H horizontal () const { pragma(inline, true); return cast(H)(value&0x0f); } /// 1240 void horizontal (H v) { pragma(inline, true); value = (value&0xf0)|v; } /// 1241 // 1242 V vertical () const { pragma(inline, true); return cast(V)((value>>4)&0x0f); } /// 1243 void vertical (V v) { pragma(inline, true); value = (value&0x0f)|cast(ubyte)(v<<4); } /// 1244 // 1245 private: 1246 ubyte value = 0; // low nibble: horizontal; high nibble: vertical 1247 } 1248 1249 /// Blending type. 1250 /// Group: composite_operation 1251 public enum NVGBlendFactor { 1252 Zero = 1<<0, /// 1253 One = 1<<1, /// 1254 SrcColor = 1<<2, /// 1255 OneMinusSrcColor = 1<<3, /// 1256 DstColor = 1<<4, /// 1257 OneMinusDstColor = 1<<5, /// 1258 SrcAlpha = 1<<6, /// 1259 OneMinusSrcAlpha = 1<<7, /// 1260 DstAlpha = 1<<8, /// 1261 OneMinusDstAlpha = 1<<9, /// 1262 SrcAlphaSaturate = 1<<10, /// 1263 } 1264 1265 /// Composite operation (HTML5-alike). 1266 /// Group: composite_operation 1267 public enum NVGCompositeOperation { 1268 SourceOver, /// 1269 SourceIn, /// 1270 SourceOut, /// 1271 SourceAtop, /// 1272 DestinationOver, /// 1273 DestinationIn, /// 1274 DestinationOut, /// 1275 DestinationAtop, /// 1276 Lighter, /// 1277 Copy, /// 1278 Xor, /// 1279 } 1280 1281 /// Composite operation state. 1282 /// Group: composite_operation 1283 public struct NVGCompositeOperationState { 1284 bool simple; /// `true`: use `glBlendFunc()` instead of `glBlendFuncSeparate()` 1285 NVGBlendFactor srcRGB; /// 1286 NVGBlendFactor dstRGB; /// 1287 NVGBlendFactor srcAlpha; /// 1288 NVGBlendFactor dstAlpha; /// 1289 } 1290 1291 /// Mask combining more 1292 /// Group: clipping 1293 public enum NVGClipMode { 1294 None, /// normal rendering (i.e. render path instead of modifying clip region) 1295 Union, /// old mask will be masked with the current one; this is the default mode for [clip] 1296 Or, /// new mask will be added to the current one (logical `OR` operation); 1297 Xor, /// new mask will be logically `XOR`ed with the current one 1298 Sub, /// "subtract" current path from mask 1299 Replace, /// replace current mask 1300 Add = Or, /// Synonym 1301 } 1302 1303 /// Glyph position info. 1304 /// Group: text_api 1305 public struct NVGGlyphPosition { 1306 usize strpos; /// Position of the glyph in the input string. 1307 float x; /// The x-coordinate of the logical glyph position. 1308 float minx, maxx; /// The bounds of the glyph shape. 1309 } 1310 1311 /// Text row storage. 1312 /// Group: text_api 1313 public struct NVGTextRow(CT) if (isAnyCharType!CT) { 1314 alias CharType = CT; 1315 const(CT)[] s; 1316 int start; /// Index in the input text where the row starts. 1317 int end; /// Index in the input text where the row ends (one past the last character). 1318 float width; /// Logical width of the row. 1319 float minx, maxx; /// Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending. 1320 /// Get rest of the string. 1321 @property const(CT)[] rest () const pure nothrow @trusted @nogc { pragma(inline, true); return (end <= s.length ? s[end..$] : null); } 1322 /// Get current row. 1323 @property const(CT)[] row () const pure nothrow @trusted @nogc { pragma(inline, true); return s[start..end]; } 1324 @property const(CT)[] string () const pure nothrow @trusted @nogc { pragma(inline, true); return s; } 1325 @property void string(CT) (const(CT)[] v) pure nothrow @trusted @nogc { pragma(inline, true); s = v; } 1326 } 1327 1328 /// Image creation flags. 1329 /// Group: images 1330 public enum NVGImageFlag : uint { 1331 None = 0, /// Nothing special. 1332 GenerateMipmaps = 1<<0, /// Generate mipmaps during creation of the image. 1333 RepeatX = 1<<1, /// Repeat image in X direction. 1334 RepeatY = 1<<2, /// Repeat image in Y direction. 1335 FlipY = 1<<3, /// Flips (inverses) image in Y direction when rendered. 1336 Premultiplied = 1<<4, /// Image data has premultiplied alpha. 1337 ClampToBorderX = 1<<5, /// Clamp image to border (instead of clamping to edge by default) 1338 ClampToBorderY = 1<<6, /// Clamp image to border (instead of clamping to edge by default) 1339 NoFiltering = 1<<8, /// use GL_NEAREST instead of GL_LINEAR. Only affects upscaling if GenerateMipmaps is active. 1340 Nearest = NoFiltering, /// compatibility with original NanoVG 1341 NoDelete = 1<<16,/// Do not delete GL texture handle. 1342 } 1343 1344 alias NVGImageFlags = NVGImageFlag; /// Backwards compatibility for [NVGImageFlag]. 1345 1346 1347 // ////////////////////////////////////////////////////////////////////////// // 1348 private: 1349 1350 static T* xdup(T) (const(T)* ptr, int count) nothrow @trusted @nogc { 1351 import core.stdc.stdlib : malloc; 1352 import core.stdc.string : memcpy; 1353 if (count == 0) return null; 1354 T* res = cast(T*)malloc(T.sizeof*count); 1355 if (res is null) assert(0, "NanoVega: out of memory"); 1356 memcpy(res, ptr, T.sizeof*count); 1357 return res; 1358 } 1359 1360 // Internal Render API 1361 enum NVGtexture { 1362 Alpha = 0x01, 1363 RGBA = 0x02, 1364 } 1365 1366 struct NVGscissor { 1367 NVGMatrix xform; 1368 float[2] extent = -1.0f; 1369 } 1370 1371 /// General NanoVega vertex struct. Contains geometry coordinates, and (sometimes unused) texture coordinates. 1372 public struct NVGVertex { 1373 float x, y, u, v; 1374 } 1375 1376 struct NVGpath { 1377 int first; 1378 int count; 1379 bool closed; 1380 int nbevel; 1381 NVGVertex* fill; 1382 int nfill; 1383 NVGVertex* stroke; 1384 int nstroke; 1385 NVGWinding mWinding; 1386 bool convex; 1387 bool cloned; 1388 1389 @disable this (this); // no copies 1390 void opAssign() (in auto ref NVGpath a) { static assert(0, "no copies!"); } 1391 1392 void clear () nothrow @trusted @nogc { 1393 import core.stdc.stdlib : free; 1394 import core.stdc.string : memset; 1395 if (cloned) { 1396 if (stroke !is null && stroke !is fill) free(stroke); 1397 if (fill !is null) free(fill); 1398 } 1399 memset(&this, 0, this.sizeof); 1400 } 1401 1402 // won't clear current path 1403 void copyFrom (const NVGpath* src) nothrow @trusted @nogc { 1404 import core.stdc.string : memcpy; 1405 assert(src !is null); 1406 memcpy(&this, src, NVGpath.sizeof); 1407 this.fill = xdup(src.fill, src.nfill); 1408 if (src.stroke is src.fill) { 1409 this.stroke = this.fill; 1410 } else { 1411 this.stroke = xdup(src.stroke, src.nstroke); 1412 } 1413 this.cloned = true; 1414 } 1415 1416 public @property const(NVGVertex)[] fillVertices () const pure nothrow @trusted @nogc { 1417 pragma(inline, true); 1418 return (nfill > 0 ? fill[0..nfill] : null); 1419 } 1420 1421 public @property const(NVGVertex)[] strokeVertices () const pure nothrow @trusted @nogc { 1422 pragma(inline, true); 1423 return (nstroke > 0 ? stroke[0..nstroke] : null); 1424 } 1425 1426 public @property NVGWinding winding () const pure nothrow @trusted @nogc { pragma(inline, true); return mWinding; } 1427 public @property bool complex () const pure nothrow @trusted @nogc { pragma(inline, true); return !convex; } 1428 } 1429 1430 1431 struct NVGparams { 1432 void* userPtr; 1433 bool edgeAntiAlias; 1434 bool fontAA; 1435 bool function (void* uptr) nothrow @trusted @nogc renderCreate; 1436 int function (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc renderCreateTexture; 1437 bool function (void* uptr, int image) nothrow @trusted @nogc renderTextureIncRef; 1438 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 1439 bool function (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc renderUpdateTexture; 1440 bool function (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc renderGetTextureSize; 1441 void function (void* uptr, int width, int height) nothrow @trusted @nogc renderViewport; // called in [beginFrame] 1442 void function (void* uptr) nothrow @trusted @nogc renderCancel; 1443 void function (void* uptr) nothrow @trusted @nogc renderFlush; 1444 void function (void* uptr) nothrow @trusted @nogc renderPushClip; // backend should support stack of at least [NVG_MAX_STATES] elements 1445 void function (void* uptr) nothrow @trusted @nogc renderPopClip; // backend should support stack of at least [NVG_MAX_STATES] elements 1446 void function (void* uptr) nothrow @trusted @nogc renderResetClip; // reset current clip region to `non-clipped` 1447 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; 1448 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; 1449 void function (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc renderTriangles; 1450 void function (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc renderSetAffine; 1451 void function (void* uptr) nothrow @trusted @nogc renderDelete; 1452 } 1453 1454 // ////////////////////////////////////////////////////////////////////////// // 1455 private: 1456 1457 enum NVG_INIT_FONTIMAGE_SIZE = 512; 1458 enum NVG_MAX_FONTIMAGE_SIZE = 2048; 1459 enum NVG_MAX_FONTIMAGES = 4; 1460 1461 enum NVG_INIT_COMMANDS_SIZE = 256; 1462 enum NVG_INIT_POINTS_SIZE = 128; 1463 enum NVG_INIT_PATHS_SIZE = 16; 1464 enum NVG_INIT_VERTS_SIZE = 256; 1465 enum NVG_MAX_STATES = 32; 1466 1467 public enum NVG_KAPPA90 = 0.5522847493f; /// Length proportional to radius of a cubic bezier handle for 90deg arcs. 1468 enum NVG_MIN_FEATHER = 0.001f; // it should be greater than zero, 'cause it is used in shader for divisions 1469 1470 enum Command { 1471 MoveTo = 0, 1472 LineTo = 1, 1473 BezierTo = 2, 1474 Close = 3, 1475 Winding = 4, 1476 } 1477 1478 enum PointFlag : int { 1479 Corner = 0x01, 1480 Left = 0x02, 1481 Bevel = 0x04, 1482 InnerBevelPR = 0x08, 1483 } 1484 1485 struct NVGstate { 1486 NVGCompositeOperationState compositeOperation; 1487 bool shapeAntiAlias = true; 1488 NVGPaint fill; 1489 NVGPaint stroke; 1490 float strokeWidth = 1.0f; 1491 float miterLimit = 10.0f; 1492 NVGLineCap lineJoin = NVGLineCap.Miter; 1493 NVGLineCap lineCap = NVGLineCap.Butt; 1494 float alpha = 1.0f; 1495 NVGMatrix xform; 1496 NVGscissor scissor; 1497 float fontSize = 16.0f; 1498 float letterSpacing = 0.0f; 1499 float lineHeight = 1.0f; 1500 float fontBlur = 0.0f; 1501 NVGTextAlign textAlign; 1502 int fontId = 0; 1503 bool evenOddMode = false; // use even-odd filling rule (required for some svgs); otherwise use non-zero fill 1504 // dashing 1505 enum MaxDashes = 32; // max 16 dashes 1506 float[MaxDashes] dashes; 1507 uint dashCount = 0; 1508 uint lastFlattenDashCount = 0; 1509 float dashStart = 0; 1510 float totalDashLen; 1511 bool firstDashIsGap = false; 1512 // dasher state for flattener 1513 bool dasherActive = false; 1514 1515 void clearPaint () nothrow @trusted @nogc { 1516 fill.clear(); 1517 stroke.clear(); 1518 } 1519 } 1520 1521 struct NVGpoint { 1522 float x, y; 1523 float dx, dy; 1524 float len; 1525 float dmx, dmy; 1526 ubyte flags; 1527 } 1528 1529 struct NVGpathCache { 1530 NVGpoint* points; 1531 int npoints; 1532 int cpoints; 1533 NVGpath* paths; 1534 int npaths; 1535 int cpaths; 1536 NVGVertex* verts; 1537 int nverts; 1538 int cverts; 1539 float[4] bounds; 1540 // this is required for saved paths 1541 bool strokeReady; 1542 bool fillReady; 1543 float strokeAlphaMul; 1544 float strokeWidth; 1545 float fringeWidth; 1546 bool evenOddMode; 1547 NVGClipMode clipmode; 1548 // non-saved path will not have this 1549 float* commands; 1550 int ncommands; 1551 1552 @disable this (this); // no copies 1553 void opAssign() (in auto ref NVGpathCache a) { static assert(0, "no copies!"); } 1554 1555 // won't clear current path 1556 void copyFrom (const NVGpathCache* src) nothrow @trusted @nogc { 1557 import core.stdc.stdlib : malloc; 1558 import core.stdc.string : memcpy, memset; 1559 assert(src !is null); 1560 memcpy(&this, src, NVGpathCache.sizeof); 1561 this.points = xdup(src.points, src.npoints); 1562 this.cpoints = src.npoints; 1563 this.verts = xdup(src.verts, src.nverts); 1564 this.cverts = src.nverts; 1565 this.commands = xdup(src.commands, src.ncommands); 1566 if (src.npaths > 0) { 1567 this.paths = cast(NVGpath*)malloc(src.npaths*NVGpath.sizeof); 1568 memset(this.paths, 0, npaths*NVGpath.sizeof); 1569 foreach (immutable pidx; 0..npaths) this.paths[pidx].copyFrom(&src.paths[pidx]); 1570 this.cpaths = src.npaths; 1571 } else { 1572 this.npaths = this.cpaths = 0; 1573 } 1574 } 1575 1576 void clear () nothrow @trusted @nogc { 1577 import core.stdc.stdlib : free; 1578 import core.stdc.string : memset; 1579 if (paths !is null) { 1580 foreach (ref p; paths[0..npaths]) p.clear(); 1581 free(paths); 1582 } 1583 if (points !is null) free(points); 1584 if (verts !is null) free(verts); 1585 if (commands !is null) free(commands); 1586 memset(&this, 0, this.sizeof); 1587 } 1588 } 1589 1590 /// Pointer to opaque NanoVega context structure. 1591 /// Group: context_management 1592 public alias NVGContext = NVGcontextinternal*; 1593 1594 /// FontStash context 1595 /// Group: font_stash 1596 public alias FONSContext = FONScontextInternal*; 1597 1598 /// Returns FontStash context of the given NanoVega context. 1599 /// Group: font_stash 1600 public FONSContext fonsContext (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.fs : null); } 1601 1602 /// Returns scale that should be applied to FontStash parameters due to matrix transformations on the context (or 1) 1603 /// Group: font_stash 1604 public float fonsScale (NVGContext ctx) /*pure*/ nothrow @trusted @nogc { 1605 pragma(inline, true); 1606 return (ctx !is null && ctx.contextAlive && ctx.nstates > 0 ? nvg__getFontScale(&ctx.states.ptr[ctx.nstates-1])*ctx.devicePxRatio : 1); 1607 } 1608 1609 /// Setup FontStash from the given NanoVega context font parameters. Note that this will apply transformation scale too. 1610 /// Returns `false` if FontStash or NanoVega context is not active. 1611 /// Group: font_stash 1612 public bool setupFonsFrom (FONSContext stash, NVGContext ctx) nothrow @trusted @nogc { 1613 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false; 1614 NVGstate* state = nvg__getState(ctx); 1615 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 1616 stash.size = state.fontSize*scale; 1617 stash.spacing = state.letterSpacing*scale; 1618 stash.blur = state.fontBlur*scale; 1619 stash.textAlign = state.textAlign; 1620 stash.fontId = state.fontId; 1621 return true; 1622 } 1623 1624 /// Setup NanoVega context font parameters from the given FontStash. Note that NanoVega can apply transformation scale later. 1625 /// Returns `false` if FontStash or NanoVega context is not active. 1626 /// Group: font_stash 1627 public bool setupCtxFrom (NVGContext ctx, FONSContext stash) nothrow @trusted @nogc { 1628 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false; 1629 NVGstate* state = nvg__getState(ctx); 1630 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 1631 state.fontSize = stash.size; 1632 state.letterSpacing = stash.spacing; 1633 state.fontBlur = stash.blur; 1634 state.textAlign = stash.textAlign; 1635 state.fontId = stash.fontId; 1636 return true; 1637 } 1638 1639 /** Bezier curve rasterizer. 1640 * 1641 * De Casteljau Bezier rasterizer is faster, but currently rasterizing curves with cusps sligtly wrong. 1642 * It doesn't really matter in practice. 1643 * 1644 * AFD tesselator is somewhat slower, but does cusps better. 1645 * 1646 * McSeem rasterizer should have the best quality, bit it is the slowest method. Basically, you will 1647 * never notice any visial difference (and this code is not really debugged), so you probably should 1648 * not use it. It is there for further experiments. 1649 */ 1650 public enum NVGTesselation { 1651 DeCasteljau, /// default: standard well-known tesselation algorithm 1652 AFD, /// adaptive forward differencing 1653 DeCasteljauMcSeem, /// standard well-known tesselation algorithm, with improvements from Maxim Shemanarev; slowest one, but should give best results 1654 } 1655 1656 /// Default tesselator for Bezier curves. 1657 public __gshared NVGTesselation NVG_DEFAULT_TESSELATOR = NVGTesselation.DeCasteljau; 1658 1659 1660 // some public info 1661 1662 /// valid only inside [beginFrame]/[endFrame] 1663 /// Group: context_management 1664 public int width (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mWidth : 0); } 1665 1666 /// valid only inside [beginFrame]/[endFrame] 1667 /// Group: context_management 1668 public int height (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mHeight : 0); } 1669 1670 /// valid only inside [beginFrame]/[endFrame] 1671 /// Group: context_management 1672 public float devicePixelRatio (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.devicePxRatio : float.nan); } 1673 1674 /// Returns `true` if [beginFrame] was called, and neither [endFrame], nor [cancelFrame] were. 1675 /// Group: context_management 1676 public bool inFrame (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.nstates > 0 : false); } 1677 1678 // path autoregistration 1679 1680 /// [pickid] to stop autoregistration. 1681 /// Group: context_management 1682 public enum NVGNoPick = -1; 1683 1684 /// >=0: this pickid will be assigned to all filled/stroked paths 1685 /// Group: context_management 1686 public int pickid (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickId : NVGNoPick); } 1687 1688 /// >=0: this pickid will be assigned to all filled/stroked paths 1689 /// Group: context_management 1690 public void pickid (NVGContext ctx, int v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.pathPickId = v; } 1691 1692 /// pick autoregistration mode; see [NVGPickKind] 1693 /// Group: context_management 1694 public uint pickmode (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickRegistered&NVGPickKind.All : 0); } 1695 1696 /// pick autoregistration mode; see [NVGPickKind] 1697 /// Group: context_management 1698 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); } 1699 1700 // tesselator options 1701 1702 /// Get current Bezier tesselation mode. See [NVGTesselation]. 1703 /// Group: context_management 1704 public NVGTesselation tesselation (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.tesselatortype : NVGTesselation.DeCasteljau); } 1705 1706 /// Set current Bezier tesselation mode. See [NVGTesselation]. 1707 /// Group: context_management 1708 public void tesselation (NVGContext ctx, NVGTesselation v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.tesselatortype = v; } 1709 1710 1711 private struct NVGcontextinternal { 1712 private: 1713 NVGparams params; 1714 float* commands; 1715 int ccommands; 1716 int ncommands; 1717 float commandx, commandy; 1718 NVGstate[NVG_MAX_STATES] states; 1719 int nstates; 1720 NVGpathCache* cache; 1721 public float tessTol; 1722 public float angleTol; // 0.0f -- angle tolerance for McSeem Bezier rasterizer 1723 public float cuspLimit; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps) 1724 float distTol; 1725 public float fringeWidth; 1726 float devicePxRatio; 1727 FONSContext fs; 1728 NVGImage[NVG_MAX_FONTIMAGES] fontImages; 1729 int fontImageIdx; 1730 int drawCallCount; 1731 int fillTriCount; 1732 int strokeTriCount; 1733 int textTriCount; 1734 NVGTesselation tesselatortype; 1735 // picking API 1736 NVGpickScene* pickScene; 1737 int pathPickId; // >=0: register all paths for picking using this id 1738 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 1739 // path recording 1740 NVGPathSet recset; 1741 int recstart; // used to cancel recording 1742 bool recblockdraw; 1743 // internals 1744 NVGMatrix gpuAffine; 1745 int mWidth, mHeight; 1746 // image manager 1747 shared int imageCount; // number of alive images in this context 1748 bool contextAlive; // context can be dead, but still contain some images 1749 1750 @disable this (this); // no copies 1751 void opAssign() (in auto ref NVGcontextinternal a) { static assert(0, "no copies!"); } 1752 1753 // debug feature 1754 public @property int getImageCount () nothrow @trusted @nogc { 1755 import core.atomic; 1756 return atomicLoad(imageCount); 1757 } 1758 } 1759 1760 /** Returns number of tesselated pathes in context. 1761 * 1762 * Tesselated pathes are either triangle strips (for strokes), or 1763 * triangle fans (for fills). Note that NanoVega can generate some 1764 * surprising pathes (like fringe stroke for antialiasing, for example). 1765 * 1766 * One render path can contain vertices both for fill, and for stroke triangles. 1767 */ 1768 public int renderPathCount (NVGContext ctx) pure nothrow @trusted @nogc { 1769 pragma(inline, true); 1770 return (ctx !is null && ctx.contextAlive ? ctx.cache.npaths : 0); 1771 } 1772 1773 /** Get vertices of "fill" triangle fan for the given render path. Can return empty slice. 1774 * 1775 * $(WARNING Returned slice can be invalidated by any other NanoVega API call 1776 * (except the calls to render path accessors), and using it in such 1777 * case is UB. So copy vertices to freshly allocated array if you want 1778 * to keep them for further processing.) 1779 */ 1780 public const(NVGVertex)[] renderPathFillVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1781 pragma(inline, true); 1782 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].fillVertices : null); 1783 } 1784 1785 /** Get vertices of "stroke" triangle strip for the given render path. Can return empty slice. 1786 * 1787 * $(WARNING Returned slice can be invalidated by any other NanoVega API call 1788 * (except the calls to render path accessors), and using it in such 1789 * case is UB. So copy vertices to freshly allocated array if you want 1790 * to keep them for further processing.) 1791 */ 1792 public const(NVGVertex)[] renderPathStrokeVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1793 pragma(inline, true); 1794 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].strokeVertices : null); 1795 1796 } 1797 1798 /// Returns winding for the given render path. 1799 public NVGWinding renderPathWinding (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1800 pragma(inline, true); 1801 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].winding : NVGWinding.CCW); 1802 1803 } 1804 1805 /// Returns "complex path" flag for the given render path. 1806 public bool renderPathComplex (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1807 pragma(inline, true); 1808 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].complex : false); 1809 1810 } 1811 1812 void nvg__imageIncRef (NVGContext ctx, int imgid, bool increfInGL=true) nothrow @trusted @nogc { 1813 if (ctx !is null && imgid > 0) { 1814 import core.atomic : atomicOp; 1815 atomicOp!"+="(ctx.imageCount, 1); 1816 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[++]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); } 1817 if (ctx.contextAlive && increfInGL) ctx.params.renderTextureIncRef(ctx.params.userPtr, imgid); 1818 } 1819 } 1820 1821 void nvg__imageDecRef (NVGContext ctx, int imgid) nothrow @trusted @nogc { 1822 if (ctx !is null && imgid > 0) { 1823 import core.atomic : atomicOp; 1824 int icnt = atomicOp!"-="(ctx.imageCount, 1); 1825 if (icnt < 0) assert(0, "NanoVega: internal image refcounting error"); 1826 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[--]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); } 1827 if (ctx.contextAlive) ctx.params.renderDeleteTexture(ctx.params.userPtr, imgid); 1828 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); } 1829 if (!ctx.contextAlive && icnt == 0) { 1830 // it is finally safe to free context memory 1831 import core.stdc.stdlib : free; 1832 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("killed zombie context %p\n", ctx); } 1833 free(ctx); 1834 } 1835 } 1836 } 1837 1838 1839 public import core.stdc.math : 1840 nvg__sqrtf = sqrtf, 1841 nvg__modf = fmodf, 1842 nvg__sinf = sinf, 1843 nvg__cosf = cosf, 1844 nvg__tanf = tanf, 1845 nvg__atan2f = atan2f, 1846 nvg__acosf = acosf, 1847 nvg__ceilf = ceilf; 1848 1849 version(Windows) { 1850 public int nvg__lrintf (float f) nothrow @trusted @nogc { pragma(inline, true); return cast(int)(f+0.5); } 1851 } else { 1852 public import core.stdc.math : nvg__lrintf = lrintf; 1853 } 1854 1855 public auto nvg__min(T) (T a, T b) { pragma(inline, true); return (a < b ? a : b); } 1856 public auto nvg__max(T) (T a, T b) { pragma(inline, true); return (a > b ? a : b); } 1857 public auto nvg__clamp(T) (T a, T mn, T mx) { pragma(inline, true); return (a < mn ? mn : (a > mx ? mx : a)); } 1858 //float nvg__absf() (float a) { pragma(inline, true); return (a >= 0.0f ? a : -a); } 1859 public auto nvg__sign(T) (T a) { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); } 1860 public float nvg__cross() (float dx0, float dy0, float dx1, float dy1) { pragma(inline, true); return (dx1*dy0-dx0*dy1); } 1861 1862 //public import core.stdc.math : nvg__absf = fabsf; 1863 public import core.math : nvg__absf = fabs; 1864 1865 1866 float nvg__normalize (float* x, float* y) nothrow @safe @nogc { 1867 float d = nvg__sqrtf((*x)*(*x)+(*y)*(*y)); 1868 if (d > 1e-6f) { 1869 immutable float id = 1.0f/d; 1870 *x *= id; 1871 *y *= id; 1872 } 1873 return d; 1874 } 1875 1876 void nvg__deletePathCache (ref NVGpathCache* c) nothrow @trusted @nogc { 1877 if (c !is null) { 1878 c.clear(); 1879 free(c); 1880 } 1881 } 1882 1883 NVGpathCache* nvg__allocPathCache () nothrow @trusted @nogc { 1884 NVGpathCache* c = cast(NVGpathCache*)malloc(NVGpathCache.sizeof); 1885 if (c is null) goto error; 1886 memset(c, 0, NVGpathCache.sizeof); 1887 1888 c.points = cast(NVGpoint*)malloc(NVGpoint.sizeof*NVG_INIT_POINTS_SIZE); 1889 if (c.points is null) goto error; 1890 assert(c.npoints == 0); 1891 c.cpoints = NVG_INIT_POINTS_SIZE; 1892 1893 c.paths = cast(NVGpath*)malloc(NVGpath.sizeof*NVG_INIT_PATHS_SIZE); 1894 if (c.paths is null) goto error; 1895 assert(c.npaths == 0); 1896 c.cpaths = NVG_INIT_PATHS_SIZE; 1897 1898 c.verts = cast(NVGVertex*)malloc(NVGVertex.sizeof*NVG_INIT_VERTS_SIZE); 1899 if (c.verts is null) goto error; 1900 assert(c.nverts == 0); 1901 c.cverts = NVG_INIT_VERTS_SIZE; 1902 1903 return c; 1904 1905 error: 1906 nvg__deletePathCache(c); 1907 return null; 1908 } 1909 1910 void nvg__setDevicePixelRatio (NVGContext ctx, float ratio) pure nothrow @safe @nogc { 1911 ctx.tessTol = 0.25f/ratio; 1912 ctx.distTol = 0.01f/ratio; 1913 ctx.fringeWidth = 1.0f/ratio; 1914 ctx.devicePxRatio = ratio; 1915 } 1916 1917 NVGCompositeOperationState nvg__compositeOperationState (NVGCompositeOperation op) pure nothrow @safe @nogc { 1918 NVGCompositeOperationState state; 1919 NVGBlendFactor sfactor, dfactor; 1920 1921 if (op == NVGCompositeOperation.SourceOver) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha;} 1922 else if (op == NVGCompositeOperation.SourceIn) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.Zero; } 1923 else if (op == NVGCompositeOperation.SourceOut) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.Zero; } 1924 else if (op == NVGCompositeOperation.SourceAtop) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } 1925 else if (op == NVGCompositeOperation.DestinationOver) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.One; } 1926 else if (op == NVGCompositeOperation.DestinationIn) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.SrcAlpha; } 1927 else if (op == NVGCompositeOperation.DestinationOut) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } 1928 else if (op == NVGCompositeOperation.DestinationAtop) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.SrcAlpha; } 1929 else if (op == NVGCompositeOperation.Lighter) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.One; } 1930 else if (op == NVGCompositeOperation.Copy) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.Zero; } 1931 else if (op == NVGCompositeOperation.Xor) { 1932 state.simple = false; 1933 state.srcRGB = NVGBlendFactor.OneMinusDstColor; 1934 state.srcAlpha = NVGBlendFactor.OneMinusDstAlpha; 1935 state.dstRGB = NVGBlendFactor.OneMinusSrcColor; 1936 state.dstAlpha = NVGBlendFactor.OneMinusSrcAlpha; 1937 return state; 1938 } 1939 else { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } // default value for invalid op: SourceOver 1940 1941 state.simple = true; 1942 state.srcAlpha = sfactor; 1943 state.dstAlpha = dfactor; 1944 return state; 1945 } 1946 1947 NVGstate* nvg__getState (NVGContext ctx) pure nothrow @trusted @nogc { 1948 pragma(inline, true); 1949 if (ctx is null || !ctx.contextAlive || ctx.nstates == 0) assert(0, "NanoVega: cannot perform commands on inactive context"); 1950 return &ctx.states.ptr[ctx.nstates-1]; 1951 } 1952 1953 // Constructor called by the render back-end. 1954 NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc { 1955 FONSParams fontParams; 1956 NVGContext ctx = cast(NVGContext)malloc(NVGcontextinternal.sizeof); 1957 if (ctx is null) goto error; 1958 memset(ctx, 0, NVGcontextinternal.sizeof); 1959 1960 ctx.angleTol = 0; // angle tolerance for McSeem Bezier rasterizer 1961 ctx.cuspLimit = 0; // cusp limit for McSeem Bezier rasterizer (0: real cusps) 1962 1963 ctx.contextAlive = true; 1964 1965 ctx.params = *params; 1966 //ctx.fontImages[0..NVG_MAX_FONTIMAGES] = 0; 1967 1968 ctx.commands = cast(float*)malloc(float.sizeof*NVG_INIT_COMMANDS_SIZE); 1969 if (ctx.commands is null) goto error; 1970 ctx.ncommands = 0; 1971 ctx.ccommands = NVG_INIT_COMMANDS_SIZE; 1972 1973 ctx.cache = nvg__allocPathCache(); 1974 if (ctx.cache is null) goto error; 1975 1976 ctx.save(); 1977 ctx.reset(); 1978 1979 nvg__setDevicePixelRatio(ctx, 1.0f); 1980 ctx.mWidth = ctx.mHeight = 0; 1981 1982 if (!ctx.params.renderCreate(ctx.params.userPtr)) goto error; 1983 1984 // init font rendering 1985 memset(&fontParams, 0, fontParams.sizeof); 1986 fontParams.width = NVG_INIT_FONTIMAGE_SIZE; 1987 fontParams.height = NVG_INIT_FONTIMAGE_SIZE; 1988 fontParams.flags = FONSParams.Flag.ZeroTopLeft; 1989 fontParams.renderCreate = null; 1990 fontParams.renderUpdate = null; 1991 fontParams.renderDelete = null; 1992 fontParams.userPtr = null; 1993 ctx.fs = FONSContext.create(fontParams); 1994 if (ctx.fs is null) goto error; 1995 1996 // create font texture 1997 ctx.fontImages[0].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, fontParams.width, fontParams.height, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); 1998 if (ctx.fontImages[0].id == 0) goto error; 1999 ctx.fontImages[0].ctx = ctx; 2000 ctx.nvg__imageIncRef(ctx.fontImages[0].id, false); // don't increment driver refcount 2001 ctx.fontImageIdx = 0; 2002 2003 ctx.pathPickId = -1; 2004 ctx.tesselatortype = NVG_DEFAULT_TESSELATOR; 2005 2006 return ctx; 2007 2008 error: 2009 ctx.deleteInternal(); 2010 return null; 2011 } 2012 2013 // Called by render backend. 2014 NVGparams* internalParams (NVGContext ctx) nothrow @trusted @nogc { 2015 return &ctx.params; 2016 } 2017 2018 // Destructor called by the render back-end. 2019 void deleteInternal (ref NVGContext ctx) nothrow @trusted @nogc { 2020 if (ctx is null) return; 2021 if (ctx.contextAlive) { 2022 if (ctx.commands !is null) free(ctx.commands); 2023 nvg__deletePathCache(ctx.cache); 2024 2025 if (ctx.fs) ctx.fs.kill(); 2026 2027 foreach (uint i; 0..NVG_MAX_FONTIMAGES) ctx.fontImages[i].clear(); 2028 2029 if (ctx.params.renderDelete !is null) ctx.params.renderDelete(ctx.params.userPtr); 2030 2031 if (ctx.pickScene !is null) nvg__deletePickScene(ctx.pickScene); 2032 2033 ctx.contextAlive = false; 2034 2035 import core.atomic : atomicLoad; 2036 if (atomicLoad(ctx.imageCount) == 0) { 2037 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("destroyed context %p\n", ctx); } 2038 free(ctx); 2039 } else { 2040 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("context %p is zombie now (%d image refs)\n", ctx, ctx.imageCount); } 2041 } 2042 } 2043 } 2044 2045 /// Delete NanoVega context. 2046 /// Group: context_management 2047 public void kill (ref NVGContext ctx) nothrow @trusted @nogc { 2048 if (ctx !is null) { 2049 ctx.deleteInternal(); 2050 ctx = null; 2051 } 2052 } 2053 2054 /// Returns `true` if the given context is not `null` and can be used for painting. 2055 /// Group: context_management 2056 public bool valid (in NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive); } 2057 2058 2059 // ////////////////////////////////////////////////////////////////////////// // 2060 // Frame Management 2061 2062 /** Begin drawing a new frame. 2063 * 2064 * Calls to NanoVega drawing API should be wrapped in [beginFrame] and [endFrame] 2065 * 2066 * [beginFrame] defines the size of the window to render to in relation currently 2067 * set viewport (i.e. glViewport on GL backends). Device pixel ration allows to 2068 * control the rendering on Hi-DPI devices. 2069 * 2070 * For example, GLFW returns two dimension for an opened window: window size and 2071 * frame buffer size. In that case you would set windowWidth/windowHeight to the window size, 2072 * devicePixelRatio to: `windowWidth/windowHeight`. 2073 * 2074 * Default ratio is `1`. 2075 * 2076 * Note that fractional ratio can (and will) distort your fonts and images. 2077 * 2078 * This call also resets pick marks (see picking API for non-rasterized paths), 2079 * path recording, and GPU affine transformatin matrix. 2080 * 2081 * see also [glNVGClearFlags], which returns necessary flags for [glClear]. 2082 * 2083 * Group: frame_management 2084 */ 2085 public void beginFrame (NVGContext ctx, int windowWidth, int windowHeight, float devicePixelRatio=1.0f) nothrow @trusted @nogc { 2086 import std.math : isNaN; 2087 /* 2088 printf("Tris: draws:%d fill:%d stroke:%d text:%d TOT:%d\n", 2089 ctx.drawCallCount, ctx.fillTriCount, ctx.strokeTriCount, ctx.textTriCount, 2090 ctx.fillTriCount+ctx.strokeTriCount+ctx.textTriCount); 2091 */ 2092 if (ctx.nstates > 0) ctx.cancelFrame(); 2093 2094 if (windowWidth < 1) windowWidth = 1; 2095 if (windowHeight < 1) windowHeight = 1; 2096 2097 if (isNaN(devicePixelRatio)) devicePixelRatio = (windowHeight > 0 ? cast(float)windowWidth/cast(float)windowHeight : 1024.0/768.0); 2098 2099 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2100 ctx.nstates = 0; 2101 ctx.save(); 2102 ctx.reset(); 2103 2104 nvg__setDevicePixelRatio(ctx, devicePixelRatio); 2105 2106 ctx.params.renderViewport(ctx.params.userPtr, windowWidth, windowHeight); 2107 ctx.mWidth = windowWidth; 2108 ctx.mHeight = windowHeight; 2109 2110 ctx.recset = null; 2111 ctx.recstart = -1; 2112 2113 ctx.pathPickId = NVGNoPick; 2114 ctx.pathPickRegistered = 0; 2115 2116 ctx.drawCallCount = 0; 2117 ctx.fillTriCount = 0; 2118 ctx.strokeTriCount = 0; 2119 ctx.textTriCount = 0; 2120 2121 ctx.ncommands = 0; 2122 ctx.pathPickRegistered = 0; 2123 nvg__clearPathCache(ctx); 2124 2125 ctx.gpuAffine = NVGMatrix.Identity; 2126 2127 nvg__pickBeginFrame(ctx, windowWidth, windowHeight); 2128 } 2129 2130 /// Cancels drawing the current frame. Cancels path recording. 2131 /// Group: frame_management 2132 public void cancelFrame (NVGContext ctx) nothrow @trusted @nogc { 2133 ctx.cancelRecording(); 2134 //ctx.mWidth = 0; 2135 //ctx.mHeight = 0; 2136 // cancel render queue 2137 ctx.params.renderCancel(ctx.params.userPtr); 2138 // clear saved states (this may free some textures) 2139 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2140 ctx.nstates = 0; 2141 } 2142 2143 /// Ends drawing the current frame (flushing remaining render state). Commits recorded paths. 2144 /// Group: frame_management 2145 public void endFrame (NVGContext ctx) nothrow @trusted @nogc { 2146 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx); 2147 ctx.stopRecording(); 2148 //ctx.mWidth = 0; 2149 //ctx.mHeight = 0; 2150 // flush render queue 2151 NVGstate* state = nvg__getState(ctx); 2152 ctx.params.renderFlush(ctx.params.userPtr); 2153 if (ctx.fontImageIdx != 0) { 2154 auto fontImage = ctx.fontImages[ctx.fontImageIdx]; 2155 int j = 0, iw, ih; 2156 // delete images that smaller than current one 2157 if (!fontImage.valid) return; 2158 ctx.imageSize(fontImage, iw, ih); 2159 foreach (int i; 0..ctx.fontImageIdx) { 2160 if (ctx.fontImages[i].valid) { 2161 int nw, nh; 2162 ctx.imageSize(ctx.fontImages[i], nw, nh); 2163 if (nw < iw || nh < ih) { 2164 ctx.deleteImage(ctx.fontImages[i]); 2165 } else { 2166 ctx.fontImages[j++] = ctx.fontImages[i]; 2167 } 2168 } 2169 } 2170 // make current font image to first 2171 ctx.fontImages[j++] = ctx.fontImages[0]; 2172 ctx.fontImages[0] = fontImage; 2173 ctx.fontImageIdx = 0; 2174 // clear all images after j 2175 ctx.fontImages[j..NVG_MAX_FONTIMAGES] = NVGImage.init; 2176 } 2177 // clear saved states (this may free some textures) 2178 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2179 ctx.nstates = 0; 2180 } 2181 2182 2183 // ////////////////////////////////////////////////////////////////////////// // 2184 // Recording and Replaying Pathes 2185 2186 // Saved path set. 2187 // Group: path_recording 2188 public alias NVGPathSet = NVGPathSetS*; 2189 2190 2191 //TODO: save scissor info? 2192 struct NVGPathSetS { 2193 private: 2194 // either path cache, or text item 2195 static struct Node { 2196 NVGPaint paint; 2197 NVGpathCache* path; 2198 } 2199 2200 private: 2201 Node* nodes; 2202 int nnodes, cnodes; 2203 NVGpickScene* pickscene; 2204 //int npickscenes, cpickscenes; 2205 NVGContext svctx; // used to do some sanity checks, and to free resources 2206 2207 private: 2208 Node* allocNode () nothrow @trusted @nogc { 2209 import core.stdc.string : memset; 2210 // grow buffer if necessary 2211 if (nnodes+1 > cnodes) { 2212 import core.stdc.stdlib : realloc; 2213 int newsz = (cnodes == 0 ? 8 : cnodes <= 1024 ? cnodes*2 : cnodes+1024); 2214 nodes = cast(Node*)realloc(nodes, newsz*Node.sizeof); 2215 if (nodes is null) assert(0, "NanoVega: out of memory"); 2216 //memset(svp.caches+svp.ccaches, 0, (newsz-svp.ccaches)*NVGpathCache.sizeof); 2217 cnodes = newsz; 2218 } 2219 assert(nnodes < cnodes); 2220 memset(nodes+nnodes, 0, Node.sizeof); 2221 return &nodes[nnodes++]; 2222 } 2223 2224 Node* allocPathNode () nothrow @trusted @nogc { 2225 import core.stdc.stdlib : malloc; 2226 import core.stdc.string : memset; 2227 auto node = allocNode(); 2228 // allocate path cache 2229 auto pc = cast(NVGpathCache*)malloc(NVGpathCache.sizeof); 2230 if (pc is null) assert(0, "NanoVega: out of memory"); 2231 node.path = pc; 2232 return node; 2233 } 2234 2235 void clearNode (int idx) nothrow @trusted @nogc { 2236 if (idx < 0 || idx >= nnodes) return; 2237 Node* node = &nodes[idx]; 2238 if (svctx !is null && node.paint.image.valid) node.paint.image.clear(); 2239 if (node.path !is null) node.path.clear(); 2240 } 2241 2242 private: 2243 void takeCurrentPickScene (NVGContext ctx) nothrow @trusted @nogc { 2244 NVGpickScene* ps = ctx.pickScene; 2245 if (ps is null) return; // nothing to do 2246 if (ps.npaths == 0) return; // pick scene is empty 2247 ctx.pickScene = null; 2248 pickscene = ps; 2249 } 2250 2251 void replay (NVGContext ctx, in ref NVGColor fillTint, in ref NVGColor strokeTint) nothrow @trusted @nogc { 2252 NVGstate* state = nvg__getState(ctx); 2253 foreach (ref node; nodes[0..nnodes]) { 2254 if (auto cc = node.path) { 2255 if (cc.npaths <= 0) continue; 2256 2257 if (cc.fillReady) { 2258 NVGPaint fillPaint = node.paint; 2259 2260 // apply global alpha 2261 fillPaint.innerColor.a *= state.alpha; 2262 fillPaint.middleColor.a *= state.alpha; 2263 fillPaint.outerColor.a *= state.alpha; 2264 2265 fillPaint.innerColor.applyTint(fillTint); 2266 fillPaint.middleColor.applyTint(fillTint); 2267 fillPaint.outerColor.applyTint(fillTint); 2268 2269 ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &fillPaint, &state.scissor, cc.fringeWidth, cc.bounds.ptr, cc.paths, cc.npaths, cc.evenOddMode); 2270 2271 // count triangles 2272 foreach (int i; 0..cc.npaths) { 2273 NVGpath* path = &cc.paths[i]; 2274 ctx.fillTriCount += path.nfill-2; 2275 ctx.fillTriCount += path.nstroke-2; 2276 ctx.drawCallCount += 2; 2277 } 2278 } 2279 2280 if (cc.strokeReady) { 2281 NVGPaint strokePaint = node.paint; 2282 2283 strokePaint.innerColor.a *= cc.strokeAlphaMul; 2284 strokePaint.middleColor.a *= cc.strokeAlphaMul; 2285 strokePaint.outerColor.a *= cc.strokeAlphaMul; 2286 2287 // apply global alpha 2288 strokePaint.innerColor.a *= state.alpha; 2289 strokePaint.middleColor.a *= state.alpha; 2290 strokePaint.outerColor.a *= state.alpha; 2291 2292 strokePaint.innerColor.applyTint(strokeTint); 2293 strokePaint.middleColor.applyTint(strokeTint); 2294 strokePaint.outerColor.applyTint(strokeTint); 2295 2296 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &strokePaint, &state.scissor, cc.fringeWidth, cc.strokeWidth, cc.paths, cc.npaths); 2297 2298 // count triangles 2299 foreach (int i; 0..cc.npaths) { 2300 NVGpath* path = &cc.paths[i]; 2301 ctx.strokeTriCount += path.nstroke-2; 2302 ++ctx.drawCallCount; 2303 } 2304 } 2305 } 2306 } 2307 } 2308 2309 public: 2310 @disable this (this); // no copies 2311 void opAssign() (in auto ref NVGPathSetS a) { static assert(0, "no copies!"); } 2312 2313 // pick test 2314 // Call delegate [dg] for each path under the specified position (in no particular order). 2315 // Returns the id of the path for which delegate [dg] returned true or -1. 2316 // dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending). 2317 int hitTestDG(bool bestOrder=false, DG) (in float x, in float y, NVGPickKind kind, scope DG dg) if (IsGoodHitTestDG!DG || IsGoodHitTestInternalDG!DG) { 2318 if (pickscene is null) return -1; 2319 2320 NVGpickScene* ps = pickscene; 2321 int levelwidth = 1<<(ps.nlevels-1); 2322 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth); 2323 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth); 2324 int npicked = 0; 2325 2326 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) { 2327 NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; 2328 while (pp !is null) { 2329 if (nvg__pickPathTestBounds(svctx, ps, pp, x, y)) { 2330 int hit = 0; 2331 if ((kind&NVGPickKind.Stroke) && (pp.flags&NVGPathFlags.Stroke)) hit = nvg__pickPathStroke(ps, pp, x, y); 2332 if (!hit && (kind&NVGPickKind.Fill) && (pp.flags&NVGPathFlags.Fill)) hit = nvg__pickPath(ps, pp, x, y); 2333 if (hit) { 2334 static if (IsGoodHitTestDG!DG) { 2335 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) { 2336 if (dg(pp.id, cast(int)pp.order)) return pp.id; 2337 } else { 2338 dg(pp.id, cast(int)pp.order); 2339 } 2340 } else { 2341 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) { 2342 if (dg(pp)) return pp.id; 2343 } else { 2344 dg(pp); 2345 } 2346 } 2347 } 2348 } 2349 pp = pp.next; 2350 } 2351 cellx >>= 1; 2352 celly >>= 1; 2353 levelwidth >>= 1; 2354 } 2355 2356 return -1; 2357 } 2358 2359 // Fills ids with a list of the top most hit ids under the specified position. 2360 // Returns the slice of [ids]. 2361 int[] hitTestAll (in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc { 2362 if (pickscene is null || ids.length == 0) return ids[0..0]; 2363 2364 int npicked = 0; 2365 NVGpickScene* ps = pickscene; 2366 2367 hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 2368 if (npicked == ps.cpicked) { 2369 int cpicked = ps.cpicked+ps.cpicked; 2370 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked); 2371 if (picked is null) return true; // abort 2372 ps.cpicked = cpicked; 2373 ps.picked = picked; 2374 } 2375 ps.picked[npicked] = pp; 2376 ++npicked; 2377 return false; // go on 2378 }); 2379 2380 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths); 2381 2382 assert(npicked >= 0); 2383 if (npicked > ids.length) npicked = cast(int)ids.length; 2384 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id; 2385 2386 return ids[0..npicked]; 2387 } 2388 2389 // Returns the id of the pickable shape containing x,y or -1 if no shape was found. 2390 int hitTest (in float x, in float y, NVGPickKind kind) nothrow @trusted @nogc { 2391 if (pickscene is null) return -1; 2392 2393 int bestOrder = -1; 2394 int bestID = -1; 2395 2396 hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 2397 if (pp.order > bestOrder) { 2398 bestOrder = pp.order; 2399 bestID = pp.id; 2400 } 2401 }); 2402 2403 return bestID; 2404 } 2405 } 2406 2407 // Append current path to existing path set. Is is safe to call this with `null` [svp]. 2408 void appendCurrentPathToCache (NVGContext ctx, NVGPathSet svp, in ref NVGPaint paint) nothrow @trusted @nogc { 2409 if (ctx is null || svp is null) return; 2410 if (ctx !is svp.svctx) assert(0, "NanoVega: cannot save paths from different contexts"); 2411 if (ctx.ncommands == 0) { 2412 assert(ctx.cache.npaths == 0); 2413 return; 2414 } 2415 if (!ctx.cache.fillReady && !ctx.cache.strokeReady) return; 2416 2417 // tesselate current path 2418 //if (!ctx.cache.fillReady) nvg__prepareFill(ctx); 2419 //if (!ctx.cache.strokeReady) nvg__prepareStroke(ctx); 2420 2421 auto node = svp.allocPathNode(); 2422 NVGpathCache* cc = node.path; 2423 cc.copyFrom(ctx.cache); 2424 node.paint = paint; 2425 // copy path commands (we may need 'em for picking) 2426 version(all) { 2427 cc.ncommands = ctx.ncommands; 2428 if (cc.ncommands) { 2429 import core.stdc.stdlib : malloc; 2430 import core.stdc.string : memcpy; 2431 cc.commands = cast(float*)malloc(ctx.ncommands*float.sizeof); 2432 if (cc.commands is null) assert(0, "NanoVega: out of memory"); 2433 memcpy(cc.commands, ctx.commands, ctx.ncommands*float.sizeof); 2434 } else { 2435 cc.commands = null; 2436 } 2437 } 2438 } 2439 2440 // Create new empty path set. 2441 // Group: path_recording 2442 public NVGPathSet newPathSet (NVGContext ctx) nothrow @trusted @nogc { 2443 import core.stdc.stdlib : malloc; 2444 import core.stdc.string : memset; 2445 if (ctx is null) return null; 2446 NVGPathSet res = cast(NVGPathSet)malloc(NVGPathSetS.sizeof); 2447 if (res is null) assert(0, "NanoVega: out of memory"); 2448 memset(res, 0, NVGPathSetS.sizeof); 2449 res.svctx = ctx; 2450 return res; 2451 } 2452 2453 // Is the given path set empty? Empty path set can be `null`. 2454 // Group: path_recording 2455 public bool empty (NVGPathSet svp) pure nothrow @safe @nogc { pragma(inline, true); return (svp is null || svp.nnodes == 0); } 2456 2457 // Clear path set contents. Will release $(B some) allocated memory (this function is meant to clear something that will be reused). 2458 // Group: path_recording 2459 public void clear (NVGPathSet svp) nothrow @trusted @nogc { 2460 if (svp !is null) { 2461 import core.stdc.stdlib : free; 2462 foreach (immutable idx; 0.. svp.nnodes) svp.clearNode(idx); 2463 svp.nnodes = 0; 2464 } 2465 } 2466 2467 // Destroy path set (frees all allocated memory). 2468 // Group: path_recording 2469 public void kill (ref NVGPathSet svp) nothrow @trusted @nogc { 2470 if (svp !is null) { 2471 import core.stdc.stdlib : free; 2472 svp.clear(); 2473 if (svp.nodes !is null) free(svp.nodes); 2474 free(svp); 2475 if (svp.pickscene !is null) nvg__deletePickScene(svp.pickscene); 2476 svp = null; 2477 } 2478 } 2479 2480 // Start path recording. [svp] should be alive until recording is cancelled or stopped. 2481 // Group: path_recording 2482 public void startRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { 2483 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2484 ctx.stopRecording(); 2485 ctx.recset = svp; 2486 ctx.recstart = (svp !is null ? svp.nnodes : -1); 2487 ctx.recblockdraw = false; 2488 } 2489 2490 /* Start path recording. [svp] should be alive until recording is cancelled or stopped. 2491 * 2492 * This will block all rendering, so you can call your rendering functions to record paths without actual drawing. 2493 * Commiting or cancelling will re-enable rendering. 2494 * You can call this with `null` svp to block rendering without recording any paths. 2495 * 2496 * Group: path_recording 2497 */ 2498 public void startBlockingRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { 2499 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2500 ctx.stopRecording(); 2501 ctx.recset = svp; 2502 ctx.recstart = (svp !is null ? svp.nnodes : -1); 2503 ctx.recblockdraw = true; 2504 } 2505 2506 // Commit recorded paths. It is safe to call this when recording is not started. 2507 // Group: path_recording 2508 public void stopRecording (NVGContext ctx) nothrow @trusted @nogc { 2509 if (ctx.recset !is null && ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2510 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx); 2511 ctx.recset = null; 2512 ctx.recstart = -1; 2513 ctx.recblockdraw = false; 2514 } 2515 2516 // Cancel path recording. 2517 // Group: path_recording 2518 public void cancelRecording (NVGContext ctx) nothrow @trusted @nogc { 2519 if (ctx.recset !is null) { 2520 if (ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2521 assert(ctx.recstart >= 0 && ctx.recstart <= ctx.recset.nnodes); 2522 foreach (immutable idx; ctx.recstart..ctx.recset.nnodes) ctx.recset.clearNode(idx); 2523 ctx.recset.nnodes = ctx.recstart; 2524 ctx.recset = null; 2525 ctx.recstart = -1; 2526 } 2527 ctx.recblockdraw = false; 2528 } 2529 2530 /* Replay saved path set. 2531 * 2532 * Replaying record while you're recording another one is undefined behavior. 2533 * 2534 * Group: path_recording 2535 */ 2536 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint, in auto ref NVGColor strokeTint) nothrow @trusted @nogc { 2537 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2538 svp.replay(ctx, fillTint, strokeTint); 2539 } 2540 2541 /// Ditto. 2542 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint) nothrow @trusted @nogc { ctx.replayRecording(svp, fillTint, NVGColor.transparent); } 2543 2544 /// Ditto. 2545 public void replayRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { ctx.replayRecording(svp, NVGColor.transparent, NVGColor.transparent); } 2546 2547 2548 // ////////////////////////////////////////////////////////////////////////// // 2549 // Composite operation 2550 2551 /// Sets the composite operation. 2552 /// Group: composite_operation 2553 public void globalCompositeOperation (NVGContext ctx, NVGCompositeOperation op) nothrow @trusted @nogc { 2554 NVGstate* state = nvg__getState(ctx); 2555 state.compositeOperation = nvg__compositeOperationState(op); 2556 } 2557 2558 /// Sets the composite operation with custom pixel arithmetic. 2559 /// Group: composite_operation 2560 public void globalCompositeBlendFunc (NVGContext ctx, NVGBlendFactor sfactor, NVGBlendFactor dfactor) nothrow @trusted @nogc { 2561 ctx.globalCompositeBlendFuncSeparate(sfactor, dfactor, sfactor, dfactor); 2562 } 2563 2564 /// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. 2565 /// Group: composite_operation 2566 public void globalCompositeBlendFuncSeparate (NVGContext ctx, NVGBlendFactor srcRGB, NVGBlendFactor dstRGB, NVGBlendFactor srcAlpha, NVGBlendFactor dstAlpha) nothrow @trusted @nogc { 2567 NVGCompositeOperationState op; 2568 op.simple = false; 2569 op.srcRGB = srcRGB; 2570 op.dstRGB = dstRGB; 2571 op.srcAlpha = srcAlpha; 2572 op.dstAlpha = dstAlpha; 2573 NVGstate* state = nvg__getState(ctx); 2574 state.compositeOperation = op; 2575 } 2576 2577 2578 // ////////////////////////////////////////////////////////////////////////// // 2579 // Color utils 2580 2581 /// Returns a color value from string form. 2582 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb" 2583 /// Group: color_utils 2584 public NVGColor nvgRGB (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); } 2585 2586 /// Ditto. 2587 public NVGColor nvgRGBA (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); } 2588 2589 /// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f). 2590 /// Group: color_utils 2591 public NVGColor nvgRGB (int r, int g, int b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(nvgClampToByte(r), nvgClampToByte(g), nvgClampToByte(b), 255); } 2592 2593 /// Returns a color value from red, green, blue values. Alpha will be set to 1.0f. 2594 /// Group: color_utils 2595 public NVGColor nvgRGBf (float r, float g, float b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(r, g, b, 1.0f); } 2596 2597 /// Returns a color value from red, green, blue and alpha values. 2598 /// Group: color_utils 2599 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)); } 2600 2601 /// Returns a color value from red, green, blue and alpha values. 2602 /// Group: color_utils 2603 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); } 2604 2605 /// Returns new color with transparency (alpha) set to [a]. 2606 /// Group: color_utils 2607 public NVGColor nvgTransRGBA (NVGColor c, ubyte a) nothrow @trusted @nogc { 2608 pragma(inline, true); 2609 c.a = a/255.0f; 2610 return c; 2611 } 2612 2613 /// Ditto. 2614 public NVGColor nvgTransRGBAf (NVGColor c, float a) nothrow @trusted @nogc { 2615 pragma(inline, true); 2616 c.a = a; 2617 return c; 2618 } 2619 2620 /// Linearly interpolates from color c0 to c1, and returns resulting color value. 2621 /// Group: color_utils 2622 public NVGColor nvgLerpRGBA() (in auto ref NVGColor c0, in auto ref NVGColor c1, float u) nothrow @trusted @nogc { 2623 NVGColor cint = void; 2624 u = nvg__clamp(u, 0.0f, 1.0f); 2625 float oneminu = 1.0f-u; 2626 foreach (uint i; 0..4) cint.rgba.ptr[i] = c0.rgba.ptr[i]*oneminu+c1.rgba.ptr[i]*u; 2627 return cint; 2628 } 2629 2630 /* see below 2631 public NVGColor nvgHSL() (float h, float s, float l) { 2632 //pragma(inline, true); // alas 2633 return nvgHSLA(h, s, l, 255); 2634 } 2635 */ 2636 2637 float nvg__hue (float h, float m1, float m2) pure nothrow @safe @nogc { 2638 if (h < 0) h += 1; 2639 if (h > 1) h -= 1; 2640 if (h < 1.0f/6.0f) return m1+(m2-m1)*h*6.0f; 2641 if (h < 3.0f/6.0f) return m2; 2642 if (h < 4.0f/6.0f) return m1+(m2-m1)*(2.0f/3.0f-h)*6.0f; 2643 return m1; 2644 } 2645 2646 /// Returns color value specified by hue, saturation and lightness. 2647 /// HSL values are all in range [0..1], alpha will be set to 255. 2648 /// Group: color_utils 2649 public alias nvgHSL = nvgHSLA; // trick to allow inlining 2650 2651 /// Returns color value specified by hue, saturation and lightness and alpha. 2652 /// HSL values are all in range [0..1], alpha in range [0..255]. 2653 /// Group: color_utils 2654 public NVGColor nvgHSLA (float h, float s, float l, ubyte a=255) nothrow @trusted @nogc { 2655 pragma(inline, true); 2656 NVGColor col = void; 2657 h = nvg__modf(h, 1.0f); 2658 if (h < 0.0f) h += 1.0f; 2659 s = nvg__clamp(s, 0.0f, 1.0f); 2660 l = nvg__clamp(l, 0.0f, 1.0f); 2661 immutable float m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s); 2662 immutable float m1 = 2*l-m2; 2663 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2664 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f); 2665 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2666 col.a = a/255.0f; 2667 return col; 2668 } 2669 2670 /// Returns color value specified by hue, saturation and lightness and alpha. 2671 /// HSL values and alpha are all in range [0..1]. 2672 /// Group: color_utils 2673 public NVGColor nvgHSLA (float h, float s, float l, float a) nothrow @trusted @nogc { 2674 // sorry for copypasta, it is for inliner 2675 static if (__VERSION__ >= 2072) pragma(inline, true); 2676 NVGColor col = void; 2677 h = nvg__modf(h, 1.0f); 2678 if (h < 0.0f) h += 1.0f; 2679 s = nvg__clamp(s, 0.0f, 1.0f); 2680 l = nvg__clamp(l, 0.0f, 1.0f); 2681 immutable m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s); 2682 immutable m1 = 2*l-m2; 2683 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2684 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f); 2685 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2686 col.a = a; 2687 return col; 2688 } 2689 2690 2691 // ////////////////////////////////////////////////////////////////////////// // 2692 // Matrices and Transformations 2693 2694 /** Matrix class. 2695 * 2696 * Group: matrices 2697 */ 2698 public align(1) struct NVGMatrix { 2699 align(1): 2700 private: 2701 static immutable float[6] IdentityMat = [ 2702 1.0f, 0.0f, 2703 0.0f, 1.0f, 2704 0.0f, 0.0f, 2705 ]; 2706 2707 public: 2708 /// Matrix values. Initial value is identity matrix. 2709 float[6] mat = [ 2710 1.0f, 0.0f, 2711 0.0f, 1.0f, 2712 0.0f, 0.0f, 2713 ]; 2714 2715 public nothrow @trusted @nogc: 2716 /// Create Matrix with the given values. 2717 this (const(float)[] amat...) { 2718 pragma(inline, true); 2719 if (amat.length >= 6) { 2720 mat.ptr[0..6] = amat.ptr[0..6]; 2721 } else { 2722 mat.ptr[0..6] = 0; 2723 mat.ptr[0..amat.length] = amat[]; 2724 } 2725 } 2726 2727 /// Can be used to check validity of [inverted] result 2728 @property bool valid () const { import core.stdc.math : isfinite; return (isfinite(mat.ptr[0]) != 0); } 2729 2730 /// Returns `true` if this matrix is identity matrix. 2731 @property bool isIdentity () const { version(aliced) pragma(inline, true); return (mat[] == IdentityMat[]); } 2732 2733 /// Returns new inverse matrix. 2734 /// If inverted matrix cannot be calculated, `res.valid` fill be `false`. 2735 NVGMatrix inverted () const { 2736 NVGMatrix res = this; 2737 res.invert; 2738 return res; 2739 } 2740 2741 /// Inverts this matrix. 2742 /// If inverted matrix cannot be calculated, `this.valid` fill be `false`. 2743 ref NVGMatrix invert () return { 2744 float[6] inv = void; 2745 immutable double det = cast(double)mat.ptr[0]*mat.ptr[3]-cast(double)mat.ptr[2]*mat.ptr[1]; 2746 if (det > -1e-6 && det < 1e-6) { 2747 inv[] = float.nan; 2748 } else { 2749 immutable double invdet = 1.0/det; 2750 inv.ptr[0] = cast(float)(mat.ptr[3]*invdet); 2751 inv.ptr[2] = cast(float)(-mat.ptr[2]*invdet); 2752 inv.ptr[4] = cast(float)((cast(double)mat.ptr[2]*mat.ptr[5]-cast(double)mat.ptr[3]*mat.ptr[4])*invdet); 2753 inv.ptr[1] = cast(float)(-mat.ptr[1]*invdet); 2754 inv.ptr[3] = cast(float)(mat.ptr[0]*invdet); 2755 inv.ptr[5] = cast(float)((cast(double)mat.ptr[1]*mat.ptr[4]-cast(double)mat.ptr[0]*mat.ptr[5])*invdet); 2756 } 2757 mat.ptr[0..6] = inv.ptr[0..6]; 2758 return this; 2759 } 2760 2761 /// Sets this matrix to identity matrix. 2762 ref NVGMatrix identity () return { version(aliced) pragma(inline, true); mat[] = IdentityMat[]; return this; } 2763 2764 /// Translate this matrix. 2765 ref NVGMatrix translate (in float tx, in float ty) return { 2766 version(aliced) pragma(inline, true); 2767 return this.mul(Translated(tx, ty)); 2768 } 2769 2770 /// Scale this matrix. 2771 ref NVGMatrix scale (in float sx, in float sy) return { 2772 version(aliced) pragma(inline, true); 2773 return this.mul(Scaled(sx, sy)); 2774 } 2775 2776 /// Rotate this matrix. 2777 ref NVGMatrix rotate (in float a) return { 2778 version(aliced) pragma(inline, true); 2779 return this.mul(Rotated(a)); 2780 } 2781 2782 /// Skew this matrix by X axis. 2783 ref NVGMatrix skewX (in float a) return { 2784 version(aliced) pragma(inline, true); 2785 return this.mul(SkewedX(a)); 2786 } 2787 2788 /// Skew this matrix by Y axis. 2789 ref NVGMatrix skewY (in float a) return { 2790 version(aliced) pragma(inline, true); 2791 return this.mul(SkewedY(a)); 2792 } 2793 2794 /// Skew this matrix by both axes. 2795 ref NVGMatrix skewY (in float ax, in float ay) return { 2796 version(aliced) pragma(inline, true); 2797 return this.mul(SkewedXY(ax, ay)); 2798 } 2799 2800 /// Transform point with this matrix. `null` destinations are allowed. 2801 /// [sx] and [sy] is the source point. [dx] and [dy] may point to the same variables. 2802 void point (float* dx, float* dy, float sx, float sy) nothrow @trusted @nogc { 2803 version(aliced) pragma(inline, true); 2804 if (dx !is null) *dx = sx*mat.ptr[0]+sy*mat.ptr[2]+mat.ptr[4]; 2805 if (dy !is null) *dy = sx*mat.ptr[1]+sy*mat.ptr[3]+mat.ptr[5]; 2806 } 2807 2808 /// Transform point with this matrix. 2809 void point (ref float x, ref float y) nothrow @trusted @nogc { 2810 version(aliced) pragma(inline, true); 2811 immutable float nx = x*mat.ptr[0]+y*mat.ptr[2]+mat.ptr[4]; 2812 immutable float ny = x*mat.ptr[1]+y*mat.ptr[3]+mat.ptr[5]; 2813 x = nx; 2814 y = ny; 2815 } 2816 2817 /// Sets this matrix to the result of multiplication of `this` and [s] (this * S). 2818 ref NVGMatrix mul() (in auto ref NVGMatrix s) { 2819 immutable float t0 = mat.ptr[0]*s.mat.ptr[0]+mat.ptr[1]*s.mat.ptr[2]; 2820 immutable float t2 = mat.ptr[2]*s.mat.ptr[0]+mat.ptr[3]*s.mat.ptr[2]; 2821 immutable float t4 = mat.ptr[4]*s.mat.ptr[0]+mat.ptr[5]*s.mat.ptr[2]+s.mat.ptr[4]; 2822 mat.ptr[1] = mat.ptr[0]*s.mat.ptr[1]+mat.ptr[1]*s.mat.ptr[3]; 2823 mat.ptr[3] = mat.ptr[2]*s.mat.ptr[1]+mat.ptr[3]*s.mat.ptr[3]; 2824 mat.ptr[5] = mat.ptr[4]*s.mat.ptr[1]+mat.ptr[5]*s.mat.ptr[3]+s.mat.ptr[5]; 2825 mat.ptr[0] = t0; 2826 mat.ptr[2] = t2; 2827 mat.ptr[4] = t4; 2828 return this; 2829 } 2830 2831 /// Sets this matrix to the result of multiplication of [s] and `this` (S * this). 2832 /// Sets the transform to the result of multiplication of two transforms, of A = B*A. 2833 /// Group: matrices 2834 ref NVGMatrix premul() (in auto ref NVGMatrix s) { 2835 NVGMatrix s2 = s; 2836 s2.mul(this); 2837 mat[] = s2.mat[]; 2838 return this; 2839 } 2840 2841 /// Multiply this matrix by [s], return result as new matrix. 2842 /// Performs operations in this left-to-right order. 2843 NVGMatrix opBinary(string op="*") (in auto ref NVGMatrix s) const { 2844 version(aliced) pragma(inline, true); 2845 NVGMatrix res = this; 2846 res.mul(s); 2847 return res; 2848 } 2849 2850 /// Multiply this matrix by [s]. 2851 /// Performs operations in this left-to-right order. 2852 ref NVGMatrix opOpAssign(string op="*") (in auto ref NVGMatrix s) { 2853 version(aliced) pragma(inline, true); 2854 return this.mul(s); 2855 } 2856 2857 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. 2858 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. 2859 float rotation () const { pragma(inline, true); return nvg__atan2f(mat.ptr[1], mat.ptr[0]); } /// Returns rotation of this matrix. 2860 float tx () const { pragma(inline, true); return mat.ptr[4]; } /// Returns x translation of this matrix. 2861 float ty () const { pragma(inline, true); return mat.ptr[5]; } /// Returns y translation of this matrix. 2862 2863 ref NVGMatrix scaleX (in float v) return { pragma(inline, true); return scaleRotateTransform(v, scaleY, rotation, tx, ty); } /// Sets x scaling of this matrix. 2864 ref NVGMatrix scaleY (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, v, rotation, tx, ty); } /// Sets y scaling of this matrix. 2865 ref NVGMatrix rotation (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, scaleY, v, tx, ty); } /// Sets rotation of this matrix. 2866 ref NVGMatrix tx (in float v) return { pragma(inline, true); mat.ptr[4] = v; return this; } /// Sets x translation of this matrix. 2867 ref NVGMatrix ty (in float v) return { pragma(inline, true); mat.ptr[5] = v; return this; } /// Sets y translation of this matrix. 2868 2869 /// Utility function to be used in `setXXX()`. 2870 /// This is the same as doing: `mat.identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster 2871 ref NVGMatrix scaleRotateTransform (in float xscale, in float yscale, in float a, in float tx, in float ty) return { 2872 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2873 mat.ptr[0] = xscale*cs; mat.ptr[1] = yscale*sn; 2874 mat.ptr[2] = xscale*-sn; mat.ptr[3] = yscale*cs; 2875 mat.ptr[4] = tx; mat.ptr[5] = ty; 2876 return this; 2877 } 2878 2879 /// This is the same as doing: `mat.identity.rotate(a).translate(tx, ty)`, only faster 2880 ref NVGMatrix rotateTransform (in float a, in float tx, in float ty) return { 2881 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2882 mat.ptr[0] = cs; mat.ptr[1] = sn; 2883 mat.ptr[2] = -sn; mat.ptr[3] = cs; 2884 mat.ptr[4] = tx; mat.ptr[5] = ty; 2885 return this; 2886 } 2887 2888 /// Returns new identity matrix. 2889 static NVGMatrix Identity () { pragma(inline, true); return NVGMatrix.init; } 2890 2891 /// Returns new translation matrix. 2892 static NVGMatrix Translated (in float tx, in float ty) { 2893 version(aliced) pragma(inline, true); 2894 NVGMatrix res = void; 2895 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f; 2896 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f; 2897 res.mat.ptr[4] = tx; res.mat.ptr[5] = ty; 2898 return res; 2899 } 2900 2901 /// Returns new scaling matrix. 2902 static NVGMatrix Scaled (in float sx, in float sy) { 2903 version(aliced) pragma(inline, true); 2904 NVGMatrix res = void; 2905 res.mat.ptr[0] = sx; res.mat.ptr[1] = 0.0f; 2906 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = sy; 2907 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2908 return res; 2909 } 2910 2911 /// Returns new rotation matrix. Angle is specified in radians. 2912 static NVGMatrix Rotated (in float a) { 2913 version(aliced) pragma(inline, true); 2914 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2915 NVGMatrix res = void; 2916 res.mat.ptr[0] = cs; res.mat.ptr[1] = sn; 2917 res.mat.ptr[2] = -sn; res.mat.ptr[3] = cs; 2918 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2919 return res; 2920 } 2921 2922 /// Returns new x-skewing matrix. Angle is specified in radians. 2923 static NVGMatrix SkewedX (in float a) { 2924 version(aliced) pragma(inline, true); 2925 NVGMatrix res = void; 2926 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f; 2927 res.mat.ptr[2] = nvg__tanf(a); res.mat.ptr[3] = 1.0f; 2928 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2929 return res; 2930 } 2931 2932 /// Returns new y-skewing matrix. Angle is specified in radians. 2933 static NVGMatrix SkewedY (in float a) { 2934 version(aliced) pragma(inline, true); 2935 NVGMatrix res = void; 2936 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(a); 2937 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f; 2938 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2939 return res; 2940 } 2941 2942 /// Returns new xy-skewing matrix. Angles are specified in radians. 2943 static NVGMatrix SkewedXY (in float ax, in float ay) { 2944 version(aliced) pragma(inline, true); 2945 NVGMatrix res = void; 2946 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(ay); 2947 res.mat.ptr[2] = nvg__tanf(ax); res.mat.ptr[3] = 1.0f; 2948 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2949 return res; 2950 } 2951 2952 /// Utility function to be used in `setXXX()`. 2953 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster 2954 static NVGMatrix ScaledRotatedTransformed (in float xscale, in float yscale, in float a, in float tx, in float ty) { 2955 NVGMatrix res = void; 2956 res.scaleRotateTransform(xscale, yscale, a, tx, ty); 2957 return res; 2958 } 2959 2960 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).translate(tx, ty)`, only faster 2961 static NVGMatrix RotatedTransformed (in float a, in float tx, in float ty) { 2962 NVGMatrix res = void; 2963 res.rotateTransform(a, tx, ty); 2964 return res; 2965 } 2966 } 2967 2968 2969 /// Converts degrees to radians. 2970 /// Group: matrices 2971 public float nvgDegToRad() (in float deg) pure nothrow @safe @nogc { pragma(inline, true); return deg/180.0f*NVG_PI; } 2972 2973 /// Converts radians to degrees. 2974 /// Group: matrices 2975 public float nvgRadToDeg() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad/NVG_PI*180.0f; } 2976 2977 public alias nvgDegrees = nvgDegToRad; /// Use this like `42.nvgDegrees` 2978 public float nvgRadians() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad; } /// Use this like `0.1.nvgRadians` 2979 2980 2981 // ////////////////////////////////////////////////////////////////////////// // 2982 void nvg__setPaintColor() (ref NVGPaint p, in auto ref NVGColor color) nothrow @trusted @nogc { 2983 p.clear(); 2984 p.xform.identity; 2985 p.radius = 0.0f; 2986 p.feather = 1.0f; 2987 p.innerColor = p.middleColor = p.outerColor = color; 2988 p.midp = -1; 2989 p.simpleColor = true; 2990 } 2991 2992 2993 // ////////////////////////////////////////////////////////////////////////// // 2994 // State handling 2995 2996 version(nanovega_debug_clipping) { 2997 public void nvgClipDumpOn (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, true); } 2998 public void nvgClipDumpOff (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, false); } 2999 } 3000 3001 /** Pushes and saves the current render state into a state stack. 3002 * A matching [restore] must be used to restore the state. 3003 * Returns `false` if state stack overflowed. 3004 * 3005 * Group: state_handling 3006 */ 3007 public bool save (NVGContext ctx) nothrow @trusted @nogc { 3008 if (ctx.nstates >= NVG_MAX_STATES) return false; 3009 if (ctx.nstates > 0) { 3010 //memcpy(&ctx.states[ctx.nstates], &ctx.states[ctx.nstates-1], NVGstate.sizeof); 3011 ctx.states[ctx.nstates] = ctx.states[ctx.nstates-1]; 3012 ctx.params.renderPushClip(ctx.params.userPtr); 3013 } 3014 ++ctx.nstates; 3015 return true; 3016 } 3017 3018 /// Pops and restores current render state. 3019 /// Group: state_handling 3020 public bool restore (NVGContext ctx) nothrow @trusted @nogc { 3021 if (ctx.nstates <= 1) return false; 3022 ctx.states[ctx.nstates-1].clearPaint(); 3023 ctx.params.renderPopClip(ctx.params.userPtr); 3024 --ctx.nstates; 3025 return true; 3026 } 3027 3028 /// Resets current render state to default values. Does not affect the render state stack. 3029 /// Group: state_handling 3030 public void reset (NVGContext ctx) nothrow @trusted @nogc { 3031 NVGstate* state = nvg__getState(ctx); 3032 state.clearPaint(); 3033 3034 nvg__setPaintColor(state.fill, nvgRGBA(255, 255, 255, 255)); 3035 nvg__setPaintColor(state.stroke, nvgRGBA(0, 0, 0, 255)); 3036 state.compositeOperation = nvg__compositeOperationState(NVGCompositeOperation.SourceOver); 3037 state.shapeAntiAlias = true; 3038 state.strokeWidth = 1.0f; 3039 state.miterLimit = 10.0f; 3040 state.lineCap = NVGLineCap.Butt; 3041 state.lineJoin = NVGLineCap.Miter; 3042 state.alpha = 1.0f; 3043 state.xform.identity; 3044 3045 state.scissor.extent[] = -1.0f; 3046 3047 state.fontSize = 16.0f; 3048 state.letterSpacing = 0.0f; 3049 state.lineHeight = 1.0f; 3050 state.fontBlur = 0.0f; 3051 state.textAlign.reset; 3052 state.fontId = 0; 3053 state.evenOddMode = false; 3054 state.dashCount = 0; 3055 state.lastFlattenDashCount = 0; 3056 state.dashStart = 0; 3057 state.firstDashIsGap = false; 3058 state.dasherActive = false; 3059 3060 ctx.params.renderResetClip(ctx.params.userPtr); 3061 } 3062 3063 /** Returns `true` if we have any room in state stack. 3064 * It is guaranteed to have at least 32 stack slots. 3065 * 3066 * Group: state_handling 3067 */ 3068 public bool canSave (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates < NVG_MAX_STATES); } 3069 3070 /** Returns `true` if we have any saved state. 3071 * 3072 * Group: state_handling 3073 */ 3074 public bool canRestore (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates > 1); } 3075 3076 /// Returns `true` if rendering is currently blocked. 3077 /// Group: state_handling 3078 public bool renderBlocked (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.recblockdraw : false); } 3079 3080 /// Blocks/unblocks rendering 3081 /// Group: state_handling 3082 public void renderBlocked (NVGContext ctx, bool v) pure nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null && ctx.contextAlive) ctx.recblockdraw = v; } 3083 3084 /// Blocks/unblocks rendering; returns previous state. 3085 /// Group: state_handling 3086 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; } 3087 3088 3089 // ////////////////////////////////////////////////////////////////////////// // 3090 // Render styles 3091 3092 /// Sets filling mode to "even-odd". 3093 /// Group: render_styles 3094 public void evenOddFill (NVGContext ctx) nothrow @trusted @nogc { 3095 NVGstate* state = nvg__getState(ctx); 3096 state.evenOddMode = true; 3097 } 3098 3099 /// Sets filling mode to "non-zero" (this is default mode). 3100 /// Group: render_styles 3101 public void nonZeroFill (NVGContext ctx) nothrow @trusted @nogc { 3102 NVGstate* state = nvg__getState(ctx); 3103 state.evenOddMode = false; 3104 } 3105 3106 /// Sets whether to draw antialias for [stroke] and [fill]. It's enabled by default. 3107 /// Group: render_styles 3108 public void shapeAntiAlias (NVGContext ctx, bool enabled) { 3109 NVGstate* state = nvg__getState(ctx); 3110 state.shapeAntiAlias = enabled; 3111 } 3112 3113 /// Sets the stroke width of the stroke style. 3114 /// Group: render_styles 3115 @scriptable 3116 public void strokeWidth (NVGContext ctx, float width) nothrow @trusted @nogc { 3117 NVGstate* state = nvg__getState(ctx); 3118 state.strokeWidth = width; 3119 } 3120 3121 /// Sets the miter limit of the stroke style. Miter limit controls when a sharp corner is beveled. 3122 /// Group: render_styles 3123 public void miterLimit (NVGContext ctx, float limit) nothrow @trusted @nogc { 3124 NVGstate* state = nvg__getState(ctx); 3125 state.miterLimit = limit; 3126 } 3127 3128 /// Sets how the end of the line (cap) is drawn, 3129 /// Can be one of: NVGLineCap.Butt (default), NVGLineCap.Round, NVGLineCap.Square. 3130 /// Group: render_styles 3131 public void lineCap (NVGContext ctx, NVGLineCap cap) nothrow @trusted @nogc { 3132 NVGstate* state = nvg__getState(ctx); 3133 state.lineCap = cap; 3134 } 3135 3136 /// Sets how sharp path corners are drawn. 3137 /// Can be one of NVGLineCap.Miter (default), NVGLineCap.Round, NVGLineCap.Bevel. 3138 /// Group: render_styles 3139 public void lineJoin (NVGContext ctx, NVGLineCap join) nothrow @trusted @nogc { 3140 NVGstate* state = nvg__getState(ctx); 3141 state.lineJoin = join; 3142 } 3143 3144 /// Sets stroke dashing, using (dash_length, gap_length) pairs. 3145 /// Current limit is 16 pairs. 3146 /// Resets dash start to zero. 3147 /// Group: render_styles 3148 public void setLineDash (NVGContext ctx, const(float)[] dashdata) nothrow @trusted @nogc { 3149 NVGstate* state = nvg__getState(ctx); 3150 state.dashCount = 0; 3151 state.dashStart = 0; 3152 state.firstDashIsGap = false; 3153 if (dashdata.length >= 2) { 3154 bool curFIsGap = true; // trick 3155 foreach (immutable idx, float f; dashdata) { 3156 curFIsGap = !curFIsGap; 3157 if (f < 0.01f) continue; // skip it 3158 if (idx == 0) { 3159 // register first dash 3160 state.firstDashIsGap = curFIsGap; 3161 state.dashes.ptr[state.dashCount++] = f; 3162 } else { 3163 if ((idx&1) != ((state.dashCount&1)^cast(uint)state.firstDashIsGap)) { 3164 // oops, continuation 3165 state.dashes[state.dashCount-1] += f; 3166 } else { 3167 if (state.dashCount == state.dashes.length) break; 3168 state.dashes[state.dashCount++] = f; 3169 } 3170 } 3171 } 3172 if (state.dashCount&1) { 3173 if (state.dashCount == 1) { 3174 state.dashCount = 0; 3175 } else { 3176 assert(state.dashCount < state.dashes.length); 3177 state.dashes[state.dashCount++] = 0; 3178 } 3179 } 3180 // calculate total dash path length 3181 state.totalDashLen = 0; 3182 foreach (float f; state.dashes.ptr[0..state.dashCount]) state.totalDashLen += f; 3183 if (state.totalDashLen < 0.01f) { 3184 state.dashCount = 0; // nothing to do 3185 } else { 3186 if (state.lastFlattenDashCount != 0) state.lastFlattenDashCount = uint.max; // force re-flattening 3187 } 3188 } 3189 } 3190 3191 public alias lineDash = setLineDash; /// Ditto. 3192 3193 /// Sets stroke dashing, using (dash_length, gap_length) pairs. 3194 /// Current limit is 16 pairs. 3195 /// Group: render_styles 3196 public void setLineDashStart (NVGContext ctx, in float dashStart) nothrow @trusted @nogc { 3197 NVGstate* state = nvg__getState(ctx); 3198 if (state.lastFlattenDashCount != 0 && state.dashStart != dashStart) { 3199 state.lastFlattenDashCount = uint.max; // force re-flattening 3200 } 3201 state.dashStart = dashStart; 3202 } 3203 3204 public alias lineDashStart = setLineDashStart; /// Ditto. 3205 3206 /// Sets the transparency applied to all rendered shapes. 3207 /// Already transparent paths will get proportionally more transparent as well. 3208 /// Group: render_styles 3209 public void globalAlpha (NVGContext ctx, float alpha) nothrow @trusted @nogc { 3210 NVGstate* state = nvg__getState(ctx); 3211 state.alpha = alpha; 3212 } 3213 3214 private void strokeColor() {} 3215 3216 static if (NanoVegaHasArsdColor) { 3217 /// Sets current stroke style to a solid color. 3218 /// Group: render_styles 3219 @scriptable 3220 public void strokeColor (NVGContext ctx, Color color) nothrow @trusted @nogc { 3221 NVGstate* state = nvg__getState(ctx); 3222 nvg__setPaintColor(state.stroke, NVGColor(color)); 3223 } 3224 } 3225 3226 /// Sets current stroke style to a solid color. 3227 /// Group: render_styles 3228 public void strokeColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc { 3229 NVGstate* state = nvg__getState(ctx); 3230 nvg__setPaintColor(state.stroke, color); 3231 } 3232 3233 @scriptable 3234 public void strokePaint(NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc { 3235 strokePaint(ctx, *paint); 3236 } 3237 3238 /// Sets current stroke style to a paint, which can be a one of the gradients or a pattern. 3239 /// Group: render_styles 3240 @scriptable 3241 public void strokePaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc { 3242 NVGstate* state = nvg__getState(ctx); 3243 state.stroke = paint; 3244 //nvgTransformMultiply(state.stroke.xform[], state.xform[]); 3245 state.stroke.xform.mul(state.xform); 3246 } 3247 3248 // this is a hack to work around https://issues.dlang.org/show_bug.cgi?id=16206 3249 // for scriptable reflection. it just needs to be declared first among the overloads 3250 private void fillColor (NVGContext ctx) nothrow @trusted @nogc { } 3251 3252 static if (NanoVegaHasArsdColor) { 3253 /// Sets current fill style to a solid color. 3254 /// Group: render_styles 3255 @scriptable 3256 public void fillColor (NVGContext ctx, Color color) nothrow @trusted @nogc { 3257 NVGstate* state = nvg__getState(ctx); 3258 nvg__setPaintColor(state.fill, NVGColor(color)); 3259 } 3260 } 3261 3262 /// Sets current fill style to a solid color. 3263 /// Group: render_styles 3264 public void fillColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc { 3265 NVGstate* state = nvg__getState(ctx); 3266 nvg__setPaintColor(state.fill, color); 3267 3268 } 3269 3270 @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) 3271 public void fillPaint (NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc { 3272 fillPaint(ctx, *paint); 3273 } 3274 3275 /// Sets current fill style to a paint, which can be a one of the gradients or a pattern. 3276 /// Group: render_styles 3277 @scriptable 3278 public void fillPaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc { 3279 NVGstate* state = nvg__getState(ctx); 3280 state.fill = paint; 3281 //nvgTransformMultiply(state.fill.xform[], state.xform[]); 3282 state.fill.xform.mul(state.xform); 3283 } 3284 3285 /// Sets current fill style to a multistop linear gradient. 3286 /// Group: render_styles 3287 public void fillPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc { 3288 if (!lgs.valid) { 3289 NVGPaint p = void; 3290 memset(&p, 0, p.sizeof); 3291 nvg__setPaintColor(p, NVGColor.red); 3292 ctx.fillPaint = p; 3293 } else if (lgs.midp >= -1) { 3294 //{ import core.stdc.stdio; printf("SIMPLE! midp=%f\n", cast(double)lgs.midp); } 3295 ctx.fillPaint = ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc); 3296 } else { 3297 ctx.fillPaint = ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid); 3298 } 3299 } 3300 3301 /// Returns current transformation matrix. 3302 /// Group: render_transformations 3303 public NVGMatrix currTransform (NVGContext ctx) pure nothrow @trusted @nogc { 3304 NVGstate* state = nvg__getState(ctx); 3305 return state.xform; 3306 } 3307 3308 /// Sets current transformation matrix. 3309 /// Group: render_transformations 3310 public void currTransform() (NVGContext ctx, in auto ref NVGMatrix m) nothrow @trusted @nogc { 3311 NVGstate* state = nvg__getState(ctx); 3312 state.xform = m; 3313 } 3314 3315 /// Resets current transform to an identity matrix. 3316 /// Group: render_transformations 3317 @scriptable 3318 public void resetTransform (NVGContext ctx) nothrow @trusted @nogc { 3319 NVGstate* state = nvg__getState(ctx); 3320 state.xform.identity; 3321 } 3322 3323 /// Premultiplies current coordinate system by specified matrix. 3324 /// Group: render_transformations 3325 public void transform() (NVGContext ctx, in auto ref NVGMatrix mt) nothrow @trusted @nogc { 3326 NVGstate* state = nvg__getState(ctx); 3327 //nvgTransformPremultiply(state.xform[], t[]); 3328 state.xform *= mt; 3329 } 3330 3331 /// Translates current coordinate system. 3332 /// Group: render_transformations 3333 @scriptable 3334 public void translate (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 3335 NVGstate* state = nvg__getState(ctx); 3336 //NVGMatrix t = void; 3337 //nvgTransformTranslate(t[], x, y); 3338 //nvgTransformPremultiply(state.xform[], t[]); 3339 state.xform.premul(NVGMatrix.Translated(x, y)); 3340 } 3341 3342 /// Rotates current coordinate system. Angle is specified in radians. 3343 /// Group: render_transformations 3344 @scriptable 3345 public void rotate (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3346 NVGstate* state = nvg__getState(ctx); 3347 //NVGMatrix t = void; 3348 //nvgTransformRotate(t[], angle); 3349 //nvgTransformPremultiply(state.xform[], t[]); 3350 state.xform.premul(NVGMatrix.Rotated(angle)); 3351 } 3352 3353 /// Skews the current coordinate system along X axis. Angle is specified in radians. 3354 /// Group: render_transformations 3355 @scriptable 3356 public void skewX (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3357 NVGstate* state = nvg__getState(ctx); 3358 //NVGMatrix t = void; 3359 //nvgTransformSkewX(t[], angle); 3360 //nvgTransformPremultiply(state.xform[], t[]); 3361 state.xform.premul(NVGMatrix.SkewedX(angle)); 3362 } 3363 3364 /// Skews the current coordinate system along Y axis. Angle is specified in radians. 3365 /// Group: render_transformations 3366 @scriptable 3367 public void skewY (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3368 NVGstate* state = nvg__getState(ctx); 3369 //NVGMatrix t = void; 3370 //nvgTransformSkewY(t[], angle); 3371 //nvgTransformPremultiply(state.xform[], t[]); 3372 state.xform.premul(NVGMatrix.SkewedY(angle)); 3373 } 3374 3375 /// Scales the current coordinate system. 3376 /// Group: render_transformations 3377 @scriptable 3378 public void scale (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 3379 NVGstate* state = nvg__getState(ctx); 3380 //NVGMatrix t = void; 3381 //nvgTransformScale(t[], x, y); 3382 //nvgTransformPremultiply(state.xform[], t[]); 3383 state.xform.premul(NVGMatrix.Scaled(x, y)); 3384 } 3385 3386 3387 // ////////////////////////////////////////////////////////////////////////// // 3388 // Images 3389 3390 /// Creates image by loading it from the disk from specified file name. 3391 /// Returns handle to the image or 0 on error. 3392 /// Group: images 3393 public NVGImage createImage() (NVGContext ctx, const(char)[] filename, const(NVGImageFlag)[] imageFlagsList...) { 3394 static if (NanoVegaHasArsdImage) { 3395 import arsd.image; 3396 // do we have new arsd API to load images? 3397 static if (!is(typeof(MemoryImage.fromImageFile)) || !is(typeof(MemoryImage.clearInternal))) { 3398 static assert(0, "Sorry, your ARSD is too old. Please, update it."); 3399 } 3400 try { 3401 auto oimg = MemoryImage.fromImageFile(filename); 3402 if (auto img = cast(TrueColorImage)oimg) { 3403 scope(exit) oimg.clearInternal(); 3404 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList); 3405 } else { 3406 TrueColorImage img = oimg.getAsTrueColorImage; 3407 scope(exit) img.clearInternal(); 3408 oimg.clearInternal(); // drop original image, as `getAsTrueColorImage()` MUST create a new one here 3409 oimg = null; 3410 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList); 3411 } 3412 } catch (Exception) {} 3413 return NVGImage.init; 3414 } else { 3415 import std.internal.cstring; 3416 ubyte* img; 3417 int w, h, n; 3418 stbi_set_unpremultiply_on_load(1); 3419 stbi_convert_iphone_png_to_rgb(1); 3420 img = stbi_load(filename.tempCString, &w, &h, &n, 4); 3421 if (img is null) { 3422 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); 3423 return NVGImage.init; 3424 } 3425 auto image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList); 3426 stbi_image_free(img); 3427 return image; 3428 } 3429 } 3430 3431 static if (NanoVegaHasArsdImage) { 3432 /// Creates image by loading it from the specified memory image. 3433 /// Returns handle to the image or 0 on error. 3434 /// Group: images 3435 public NVGImage createImageFromMemoryImage() (NVGContext ctx, MemoryImage img, const(NVGImageFlag)[] imageFlagsList...) { 3436 if (img is null) return NVGImage.init; 3437 if (auto tc = cast(TrueColorImage)img) { 3438 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList); 3439 } else { 3440 auto tc = img.getAsTrueColorImage; 3441 scope(exit) tc.clearInternal(); // here, it is guaranteed that `tc` is newly allocated image, so it is safe to kill it 3442 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList); 3443 } 3444 } 3445 } else { 3446 /// Creates image by loading it from the specified chunk of memory. 3447 /// Returns handle to the image or 0 on error. 3448 /// Group: images 3449 public NVGImage createImageMem() (NVGContext ctx, const(ubyte)* data, int ndata, const(NVGImageFlag)[] imageFlagsList...) { 3450 int w, h, n, image; 3451 ubyte* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4); 3452 if (img is null) { 3453 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); 3454 return NVGImage.init; 3455 } 3456 image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList); 3457 stbi_image_free(img); 3458 return image; 3459 } 3460 } 3461 3462 /// Creates image from specified image data. 3463 /// Returns handle to the image or 0 on error. 3464 /// Group: images 3465 public NVGImage createImageRGBA (NVGContext ctx, int w, int h, const(void)[] data, const(NVGImageFlag)[] imageFlagsList...) nothrow @trusted @nogc { 3466 if (w < 1 || h < 1 || data.length < w*h*4) return NVGImage.init; 3467 uint imageFlags = 0; 3468 foreach (immutable uint flag; imageFlagsList) imageFlags |= flag; 3469 NVGImage res; 3470 res.id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.RGBA, w, h, imageFlags, cast(const(ubyte)*)data.ptr); 3471 if (res.id > 0) { 3472 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); } 3473 res.ctx = ctx; 3474 ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount 3475 } 3476 return res; 3477 } 3478 3479 /// Updates image data specified by image handle. 3480 /// Group: images 3481 public void updateImage() (NVGContext ctx, auto ref NVGImage image, const(void)[] data) nothrow @trusted @nogc { 3482 if (image.valid) { 3483 int w, h; 3484 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3485 ctx.params.renderGetTextureSize(ctx.params.userPtr, image.id, &w, &h); 3486 ctx.params.renderUpdateTexture(ctx.params.userPtr, image.id, 0, 0, w, h, cast(const(ubyte)*)data.ptr); 3487 } 3488 } 3489 3490 /// Returns the dimensions of a created image. 3491 /// Group: images 3492 public void imageSize() (NVGContext ctx, in auto ref NVGImage image, out int w, out int h) nothrow @trusted @nogc { 3493 if (image.valid) { 3494 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3495 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, image.id, &w, &h); 3496 } 3497 } 3498 3499 /// Deletes created image. 3500 /// Group: images 3501 public void deleteImage() (NVGContext ctx, ref NVGImage image) nothrow @trusted @nogc { 3502 if (ctx is null || !image.valid) return; 3503 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3504 image.clear(); 3505 } 3506 3507 3508 // ////////////////////////////////////////////////////////////////////////// // 3509 // Paints 3510 3511 private void linearGradient() {} // hack for dmd bug 3512 3513 static if (NanoVegaHasArsdColor) { 3514 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates 3515 * of the linear gradient, icol specifies the start color and ocol the end color. 3516 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3517 * 3518 * Group: paints 3519 */ 3520 @scriptable 3521 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 { 3522 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), NVGColor(ocol)); 3523 } 3524 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start 3525 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in 3526 * range `(0..1)`, and ocol the end color. 3527 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3528 * 3529 * Group: paints 3530 */ 3531 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 { 3532 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), midp, NVGColor(mcol), NVGColor(ocol)); 3533 } 3534 } 3535 3536 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates 3537 * of the linear gradient, icol specifies the start color and ocol the end color. 3538 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3539 * 3540 * Group: paints 3541 */ 3542 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 { 3543 enum large = 1e5f; 3544 3545 NVGPaint p = void; 3546 memset(&p, 0, p.sizeof); 3547 p.simpleColor = false; 3548 3549 // Calculate transform aligned to the line 3550 float dx = ex-sx; 3551 float dy = ey-sy; 3552 immutable float d = nvg__sqrtf(dx*dx+dy*dy); 3553 if (d > 0.0001f) { 3554 dx /= d; 3555 dy /= d; 3556 } else { 3557 dx = 0; 3558 dy = 1; 3559 } 3560 3561 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx; 3562 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy; 3563 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large; 3564 3565 p.extent.ptr[0] = large; 3566 p.extent.ptr[1] = large+d*0.5f; 3567 3568 p.radius = 0.0f; 3569 3570 p.feather = nvg__max(NVG_MIN_FEATHER, d); 3571 3572 p.innerColor = p.middleColor = icol; 3573 p.outerColor = ocol; 3574 p.midp = -1; 3575 3576 return p; 3577 } 3578 3579 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start 3580 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in 3581 * range `(0..1)`, and ocol the end color. 3582 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3583 * 3584 * Group: paints 3585 */ 3586 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 { 3587 enum large = 1e5f; 3588 3589 NVGPaint p = void; 3590 memset(&p, 0, p.sizeof); 3591 p.simpleColor = false; 3592 3593 // Calculate transform aligned to the line 3594 float dx = ex-sx; 3595 float dy = ey-sy; 3596 immutable float d = nvg__sqrtf(dx*dx+dy*dy); 3597 if (d > 0.0001f) { 3598 dx /= d; 3599 dy /= d; 3600 } else { 3601 dx = 0; 3602 dy = 1; 3603 } 3604 3605 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx; 3606 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy; 3607 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large; 3608 3609 p.extent.ptr[0] = large; 3610 p.extent.ptr[1] = large+d*0.5f; 3611 3612 p.radius = 0.0f; 3613 3614 p.feather = nvg__max(NVG_MIN_FEATHER, d); 3615 3616 if (midp <= 0) { 3617 p.innerColor = p.middleColor = mcol; 3618 p.midp = -1; 3619 } else if (midp > 1) { 3620 p.innerColor = p.middleColor = icol; 3621 p.midp = -1; 3622 } else { 3623 p.innerColor = icol; 3624 p.middleColor = mcol; 3625 p.midp = midp; 3626 } 3627 p.outerColor = ocol; 3628 3629 return p; 3630 } 3631 3632 static if (NanoVegaHasArsdColor) { 3633 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify 3634 * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. 3635 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3636 * 3637 * Group: paints 3638 */ 3639 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 { 3640 return ctx.radialGradient(cx, cy, inr, outr, NVGColor(icol), NVGColor(ocol)); 3641 } 3642 } 3643 3644 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify 3645 * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. 3646 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3647 * 3648 * Group: paints 3649 */ 3650 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 { 3651 immutable float r = (inr+outr)*0.5f; 3652 immutable float f = (outr-inr); 3653 3654 NVGPaint p = void; 3655 memset(&p, 0, p.sizeof); 3656 p.simpleColor = false; 3657 3658 p.xform.identity; 3659 p.xform.mat.ptr[4] = cx; 3660 p.xform.mat.ptr[5] = cy; 3661 3662 p.extent.ptr[0] = r; 3663 p.extent.ptr[1] = r; 3664 3665 p.radius = r; 3666 3667 p.feather = nvg__max(NVG_MIN_FEATHER, f); 3668 3669 p.innerColor = p.middleColor = icol; 3670 p.outerColor = ocol; 3671 p.midp = -1; 3672 3673 return p; 3674 } 3675 3676 static if (NanoVegaHasArsdColor) { 3677 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering 3678 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle, 3679 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry 3680 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. 3681 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3682 * 3683 * Group: paints 3684 */ 3685 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 { 3686 return ctx.boxGradient(x, y, w, h, r, f, NVGColor(icol), NVGColor(ocol)); 3687 } 3688 } 3689 3690 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering 3691 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle, 3692 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry 3693 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. 3694 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3695 * 3696 * Group: paints 3697 */ 3698 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 { 3699 NVGPaint p = void; 3700 memset(&p, 0, p.sizeof); 3701 p.simpleColor = false; 3702 3703 p.xform.identity; 3704 p.xform.mat.ptr[4] = x+w*0.5f; 3705 p.xform.mat.ptr[5] = y+h*0.5f; 3706 3707 p.extent.ptr[0] = w*0.5f; 3708 p.extent.ptr[1] = h*0.5f; 3709 3710 p.radius = r; 3711 3712 p.feather = nvg__max(NVG_MIN_FEATHER, f); 3713 3714 p.innerColor = p.middleColor = icol; 3715 p.outerColor = ocol; 3716 p.midp = -1; 3717 3718 return p; 3719 } 3720 3721 /** Creates and returns an image pattern. Parameters `(cx, cy)` specify the left-top location of the image pattern, 3722 * `(w, h)` the size of one image, [angle] rotation around the top-left corner, [image] is handle to the image to render. 3723 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3724 * 3725 * Group: paints 3726 */ 3727 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 { 3728 NVGPaint p = void; 3729 memset(&p, 0, p.sizeof); 3730 p.simpleColor = false; 3731 3732 p.xform.identity.rotate(angle); 3733 p.xform.mat.ptr[4] = cx; 3734 p.xform.mat.ptr[5] = cy; 3735 3736 p.extent.ptr[0] = w; 3737 p.extent.ptr[1] = h; 3738 3739 p.image = image; 3740 3741 p.innerColor = p.middleColor = p.outerColor = nvgRGBAf(1, 1, 1, alpha); 3742 p.midp = -1; 3743 3744 return p; 3745 } 3746 3747 /// Linear gradient with multiple stops. 3748 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3749 /// Group: paints 3750 public struct NVGLGS { 3751 private: 3752 NVGColor ic, mc, oc; // inner, middle, out 3753 float midp; 3754 NVGImage imgid; 3755 // [imagePattern] arguments 3756 float cx, cy, dimx, dimy; // dimx and dimy are ex and ey for simple gradients 3757 public float angle; /// 3758 3759 public: 3760 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (imgid.valid || midp >= -1); } /// 3761 void clear () nothrow @safe @nogc { pragma(inline, true); imgid.clear(); midp = float.nan; } /// 3762 } 3763 3764 /** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops]. 3765 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3766 * 3767 * $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3768 * Group: paints 3769 */ 3770 public NVGPaint asPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc { 3771 if (!lgs.valid) { 3772 NVGPaint p = void; 3773 memset(&p, 0, p.sizeof); 3774 nvg__setPaintColor(p, NVGColor.red); 3775 return p; 3776 } else if (lgs.midp >= -1) { 3777 return ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc); 3778 } else { 3779 return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid); 3780 } 3781 } 3782 3783 /// Gradient Stop Point. 3784 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3785 /// Group: paints 3786 public struct NVGGradientStop { 3787 float offset = 0; /// [0..1] 3788 NVGColor color; /// 3789 3790 this() (in float aofs, in auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } /// 3791 static if (NanoVegaHasArsdColor) { 3792 this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } /// 3793 } 3794 } 3795 3796 /// Create linear gradient data suitable to use with `linearGradient(res)`. 3797 /// Don't forget to destroy the result when you don't need it anymore with `ctx.kill(res);`. 3798 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3799 /// Group: paints 3800 public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, const(NVGGradientStop)[] stops...) nothrow @trusted @nogc { 3801 // based on the code by Jorge Acereda <jacereda@gmail.com> 3802 enum NVG_GRADIENT_SAMPLES = 1024; 3803 static void gradientSpan (uint* dst, const(NVGGradientStop)* s0, const(NVGGradientStop)* s1) nothrow @trusted @nogc { 3804 immutable float s0o = nvg__clamp(s0.offset, 0.0f, 1.0f); 3805 immutable float s1o = nvg__clamp(s1.offset, 0.0f, 1.0f); 3806 uint s = cast(uint)(s0o*NVG_GRADIENT_SAMPLES); 3807 uint e = cast(uint)(s1o*NVG_GRADIENT_SAMPLES); 3808 uint sc = 0xffffffffU; 3809 uint sh = 24; 3810 uint r = cast(uint)(s0.color.rgba[0]*sc); 3811 uint g = cast(uint)(s0.color.rgba[1]*sc); 3812 uint b = cast(uint)(s0.color.rgba[2]*sc); 3813 uint a = cast(uint)(s0.color.rgba[3]*sc); 3814 uint dr = cast(uint)((s1.color.rgba[0]*sc-r)/(e-s)); 3815 uint dg = cast(uint)((s1.color.rgba[1]*sc-g)/(e-s)); 3816 uint db = cast(uint)((s1.color.rgba[2]*sc-b)/(e-s)); 3817 uint da = cast(uint)((s1.color.rgba[3]*sc-a)/(e-s)); 3818 dst += s; 3819 foreach (immutable _; s..e) { 3820 version(BigEndian) { 3821 *dst++ = ((r>>sh)<<24)+((g>>sh)<<16)+((b>>sh)<<8)+((a>>sh)<<0); 3822 } else { 3823 *dst++ = ((a>>sh)<<24)+((b>>sh)<<16)+((g>>sh)<<8)+((r>>sh)<<0); 3824 } 3825 r += dr; 3826 g += dg; 3827 b += db; 3828 a += da; 3829 } 3830 } 3831 3832 NVGLGS res; 3833 res.cx = sx; 3834 res.cy = sy; 3835 3836 if (stops.length == 2 && stops.ptr[0].offset <= 0 && stops.ptr[1].offset >= 1) { 3837 // create simple linear gradient 3838 res.ic = res.mc = stops.ptr[0].color; 3839 res.oc = stops.ptr[1].color; 3840 res.midp = -1; 3841 res.dimx = ex; 3842 res.dimy = ey; 3843 } else if (stops.length == 3 && stops.ptr[0].offset <= 0 && stops.ptr[2].offset >= 1) { 3844 // create simple linear gradient with middle stop 3845 res.ic = stops.ptr[0].color; 3846 res.mc = stops.ptr[1].color; 3847 res.oc = stops.ptr[2].color; 3848 res.midp = stops.ptr[1].offset; 3849 res.dimx = ex; 3850 res.dimy = ey; 3851 } else { 3852 // create image gradient 3853 uint[NVG_GRADIENT_SAMPLES] data = void; 3854 immutable float w = ex-sx; 3855 immutable float h = ey-sy; 3856 res.dimx = nvg__sqrtf(w*w+h*h); 3857 res.dimy = 1; //??? 3858 3859 res.angle = 3860 (/*nvg__absf(h) < 0.0001 ? 0 : 3861 nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/ 3862 nvg__atan2f(h/*ey-sy*/, w/*ex-sx*/)); 3863 3864 if (stops.length > 0) { 3865 auto s0 = NVGGradientStop(0, nvgRGBAf(0, 0, 0, 1)); 3866 auto s1 = NVGGradientStop(1, nvgRGBAf(1, 1, 1, 1)); 3867 if (stops.length > 64) stops = stops[0..64]; 3868 if (stops.length) { 3869 s0.color = stops[0].color; 3870 s1.color = stops[$-1].color; 3871 } 3872 gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1)); 3873 foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1); 3874 gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1); 3875 res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[]/*, NVGImageFlag.RepeatX, NVGImageFlag.RepeatY*/); 3876 } 3877 } 3878 return res; 3879 } 3880 3881 3882 // ////////////////////////////////////////////////////////////////////////// // 3883 // Scissoring 3884 3885 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform. 3886 /// Group: scissoring 3887 public void scissor (NVGContext ctx, in float x, in float y, float w, float h) nothrow @trusted @nogc { 3888 NVGstate* state = nvg__getState(ctx); 3889 3890 w = nvg__max(0.0f, w); 3891 h = nvg__max(0.0f, h); 3892 3893 state.scissor.xform.identity; 3894 state.scissor.xform.mat.ptr[4] = x+w*0.5f; 3895 state.scissor.xform.mat.ptr[5] = y+h*0.5f; 3896 //nvgTransformMultiply(state.scissor.xform[], state.xform[]); 3897 state.scissor.xform.mul(state.xform); 3898 3899 state.scissor.extent.ptr[0] = w*0.5f; 3900 state.scissor.extent.ptr[1] = h*0.5f; 3901 } 3902 3903 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform. 3904 /// Arguments: [x, y, w, h]* 3905 /// Group: scissoring 3906 public void scissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 3907 enum ArgC = 4; 3908 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [scissor] call"); 3909 if (args.length < ArgC) return; 3910 NVGstate* state = nvg__getState(ctx); 3911 const(float)* aptr = args.ptr; 3912 foreach (immutable idx; 0..args.length/ArgC) { 3913 immutable x = *aptr++; 3914 immutable y = *aptr++; 3915 immutable w = nvg__max(0.0f, *aptr++); 3916 immutable h = nvg__max(0.0f, *aptr++); 3917 3918 state.scissor.xform.identity; 3919 state.scissor.xform.mat.ptr[4] = x+w*0.5f; 3920 state.scissor.xform.mat.ptr[5] = y+h*0.5f; 3921 //nvgTransformMultiply(state.scissor.xform[], state.xform[]); 3922 state.scissor.xform.mul(state.xform); 3923 3924 state.scissor.extent.ptr[0] = w*0.5f; 3925 state.scissor.extent.ptr[1] = h*0.5f; 3926 } 3927 } 3928 3929 void nvg__isectRects (float* dst, float ax, float ay, float aw, float ah, float bx, float by, float bw, float bh) nothrow @trusted @nogc { 3930 immutable float minx = nvg__max(ax, bx); 3931 immutable float miny = nvg__max(ay, by); 3932 immutable float maxx = nvg__min(ax+aw, bx+bw); 3933 immutable float maxy = nvg__min(ay+ah, by+bh); 3934 dst[0] = minx; 3935 dst[1] = miny; 3936 dst[2] = nvg__max(0.0f, maxx-minx); 3937 dst[3] = nvg__max(0.0f, maxy-miny); 3938 } 3939 3940 /** Intersects current scissor rectangle with the specified rectangle. 3941 * The scissor rectangle is transformed by the current transform. 3942 * Note: in case the rotation of previous scissor rect differs from 3943 * the current one, the intersection will be done between the specified 3944 * rectangle and the previous scissor rectangle transformed in the current 3945 * transform space. The resulting shape is always rectangle. 3946 * 3947 * Group: scissoring 3948 */ 3949 public void intersectScissor (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc { 3950 NVGstate* state = nvg__getState(ctx); 3951 3952 // If no previous scissor has been set, set the scissor as current scissor. 3953 if (state.scissor.extent.ptr[0] < 0) { 3954 ctx.scissor(x, y, w, h); 3955 return; 3956 } 3957 3958 NVGMatrix pxform = void; 3959 NVGMatrix invxorm = void; 3960 float[4] rect = void; 3961 3962 // Transform the current scissor rect into current transform space. 3963 // If there is difference in rotation, this will be approximation. 3964 //memcpy(pxform.mat.ptr, state.scissor.xform.ptr, float.sizeof*6); 3965 pxform = state.scissor.xform; 3966 immutable float ex = state.scissor.extent.ptr[0]; 3967 immutable float ey = state.scissor.extent.ptr[1]; 3968 //nvgTransformInverse(invxorm[], state.xform[]); 3969 invxorm = state.xform.inverted; 3970 //nvgTransformMultiply(pxform[], invxorm[]); 3971 pxform.mul(invxorm); 3972 immutable float tex = ex*nvg__absf(pxform.mat.ptr[0])+ey*nvg__absf(pxform.mat.ptr[2]); 3973 immutable float tey = ex*nvg__absf(pxform.mat.ptr[1])+ey*nvg__absf(pxform.mat.ptr[3]); 3974 3975 // Intersect rects. 3976 nvg__isectRects(rect.ptr, pxform.mat.ptr[4]-tex, pxform.mat.ptr[5]-tey, tex*2, tey*2, x, y, w, h); 3977 3978 //ctx.scissor(rect.ptr[0], rect.ptr[1], rect.ptr[2], rect.ptr[3]); 3979 ctx.scissor(rect.ptr[0..4]); 3980 } 3981 3982 /** Intersects current scissor rectangle with the specified rectangle. 3983 * The scissor rectangle is transformed by the current transform. 3984 * Note: in case the rotation of previous scissor rect differs from 3985 * the current one, the intersection will be done between the specified 3986 * rectangle and the previous scissor rectangle transformed in the current 3987 * transform space. The resulting shape is always rectangle. 3988 * 3989 * Arguments: [x, y, w, h]* 3990 * 3991 * Group: scissoring 3992 */ 3993 public void intersectScissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 3994 enum ArgC = 4; 3995 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [intersectScissor] call"); 3996 if (args.length < ArgC) return; 3997 const(float)* aptr = args.ptr; 3998 foreach (immutable idx; 0..args.length/ArgC) { 3999 immutable x = *aptr++; 4000 immutable y = *aptr++; 4001 immutable w = *aptr++; 4002 immutable h = *aptr++; 4003 ctx.intersectScissor(x, y, w, h); 4004 } 4005 } 4006 4007 /// Reset and disables scissoring. 4008 /// Group: scissoring 4009 public void resetScissor (NVGContext ctx) nothrow @trusted @nogc { 4010 NVGstate* state = nvg__getState(ctx); 4011 state.scissor.xform.mat[] = 0.0f; 4012 state.scissor.extent[] = -1.0f; 4013 } 4014 4015 4016 // ////////////////////////////////////////////////////////////////////////// // 4017 // Render-Time Affine Transformations 4018 4019 /// Sets GPU affine transformatin matrix. Don't do scaling or skewing here. 4020 /// This matrix won't be saved/restored with context state save/restore operations, as it is not a part of that state. 4021 /// Group: gpu_affine 4022 public void affineGPU() (NVGContext ctx, in auto ref NVGMatrix mat) nothrow @trusted @nogc { 4023 ctx.gpuAffine = mat; 4024 ctx.params.renderSetAffine(ctx.params.userPtr, ctx.gpuAffine); 4025 } 4026 4027 /// Get current GPU affine transformatin matrix. 4028 /// Group: gpu_affine 4029 public NVGMatrix affineGPU (NVGContext ctx) nothrow @safe @nogc { 4030 pragma(inline, true); 4031 return ctx.gpuAffine; 4032 } 4033 4034 /// "Untransform" point using current GPU affine matrix. 4035 /// Group: gpu_affine 4036 public void gpuUntransformPoint (NVGContext ctx, float *dx, float *dy, in float x, in float y) nothrow @safe @nogc { 4037 if (ctx.gpuAffine.isIdentity) { 4038 if (dx !is null) *dx = x; 4039 if (dy !is null) *dy = y; 4040 } else { 4041 // inverse GPU transformation 4042 NVGMatrix igpu = ctx.gpuAffine.inverted; 4043 igpu.point(dx, dy, x, y); 4044 } 4045 } 4046 4047 4048 // ////////////////////////////////////////////////////////////////////////// // 4049 // rasterization (tesselation) code 4050 4051 int nvg__ptEquals (float x1, float y1, float x2, float y2, float tol) pure nothrow @safe @nogc { 4052 //pragma(inline, true); 4053 immutable float dx = x2-x1; 4054 immutable float dy = y2-y1; 4055 return dx*dx+dy*dy < tol*tol; 4056 } 4057 4058 float nvg__distPtSeg (float x, float y, float px, float py, float qx, float qy) pure nothrow @safe @nogc { 4059 immutable float pqx = qx-px; 4060 immutable float pqy = qy-py; 4061 float dx = x-px; 4062 float dy = y-py; 4063 immutable float d = pqx*pqx+pqy*pqy; 4064 float t = pqx*dx+pqy*dy; 4065 if (d > 0) t /= d; 4066 if (t < 0) t = 0; else if (t > 1) t = 1; 4067 dx = px+t*pqx-x; 4068 dy = py+t*pqy-y; 4069 return dx*dx+dy*dy; 4070 } 4071 4072 void nvg__appendCommands(bool useCommand=true) (NVGContext ctx, Command acmd, const(float)[] vals...) nothrow @trusted @nogc { 4073 int nvals = cast(int)vals.length; 4074 static if (useCommand) { 4075 enum addon = 1; 4076 } else { 4077 enum addon = 0; 4078 if (nvals == 0) return; // nothing to do 4079 } 4080 4081 NVGstate* state = nvg__getState(ctx); 4082 4083 if (ctx.ncommands+nvals+addon > ctx.ccommands) { 4084 //int ccommands = ctx.ncommands+nvals+ctx.ccommands/2; 4085 int ccommands = ((ctx.ncommands+(nvals+addon))|0xfff)+1; 4086 float* commands = cast(float*)realloc(ctx.commands, float.sizeof*ccommands); 4087 if (commands is null) assert(0, "NanoVega: out of memory"); 4088 ctx.commands = commands; 4089 ctx.ccommands = ccommands; 4090 assert(ctx.ncommands+(nvals+addon) <= ctx.ccommands); 4091 } 4092 4093 static if (!useCommand) acmd = cast(Command)vals.ptr[0]; 4094 4095 if (acmd != Command.Close && acmd != Command.Winding) { 4096 //assert(nvals+addon >= 3); 4097 ctx.commandx = vals.ptr[nvals-2]; 4098 ctx.commandy = vals.ptr[nvals-1]; 4099 } 4100 4101 // copy commands 4102 float* vp = ctx.commands+ctx.ncommands; 4103 static if (useCommand) { 4104 vp[0] = cast(float)acmd; 4105 if (nvals > 0) memcpy(vp+1, vals.ptr, nvals*float.sizeof); 4106 } else { 4107 memcpy(vp, vals.ptr, nvals*float.sizeof); 4108 } 4109 ctx.ncommands += nvals+addon; 4110 4111 // transform commands 4112 int i = nvals+addon; 4113 while (i > 0) { 4114 int nlen = 1; 4115 final switch (cast(Command)(*vp)) { 4116 case Command.MoveTo: 4117 case Command.LineTo: 4118 assert(i >= 3); 4119 state.xform.point(vp+1, vp+2, vp[1], vp[2]); 4120 nlen = 3; 4121 break; 4122 case Command.BezierTo: 4123 assert(i >= 7); 4124 state.xform.point(vp+1, vp+2, vp[1], vp[2]); 4125 state.xform.point(vp+3, vp+4, vp[3], vp[4]); 4126 state.xform.point(vp+5, vp+6, vp[5], vp[6]); 4127 nlen = 7; 4128 break; 4129 case Command.Close: 4130 nlen = 1; 4131 break; 4132 case Command.Winding: 4133 nlen = 2; 4134 break; 4135 } 4136 assert(nlen > 0 && nlen <= i); 4137 i -= nlen; 4138 vp += nlen; 4139 } 4140 } 4141 4142 void nvg__clearPathCache (NVGContext ctx) nothrow @trusted @nogc { 4143 // no need to clear paths, as data is not copied there 4144 //foreach (ref p; ctx.cache.paths[0..ctx.cache.npaths]) p.clear(); 4145 ctx.cache.npoints = 0; 4146 ctx.cache.npaths = 0; 4147 ctx.cache.fillReady = ctx.cache.strokeReady = false; 4148 ctx.cache.clipmode = NVGClipMode.None; 4149 } 4150 4151 NVGpath* nvg__lastPath (NVGContext ctx) nothrow @trusted @nogc { 4152 return (ctx.cache.npaths > 0 ? &ctx.cache.paths[ctx.cache.npaths-1] : null); 4153 } 4154 4155 void nvg__addPath (NVGContext ctx) nothrow @trusted @nogc { 4156 import core.stdc.stdlib : realloc; 4157 import core.stdc.string : memset; 4158 4159 if (ctx.cache.npaths+1 > ctx.cache.cpaths) { 4160 int cpaths = ctx.cache.npaths+1+ctx.cache.cpaths/2; 4161 NVGpath* paths = cast(NVGpath*)realloc(ctx.cache.paths, NVGpath.sizeof*cpaths); 4162 if (paths is null) assert(0, "NanoVega: out of memory"); 4163 ctx.cache.paths = paths; 4164 ctx.cache.cpaths = cpaths; 4165 } 4166 4167 NVGpath* path = &ctx.cache.paths[ctx.cache.npaths++]; 4168 memset(path, 0, NVGpath.sizeof); 4169 path.first = ctx.cache.npoints; 4170 path.mWinding = NVGWinding.CCW; 4171 } 4172 4173 NVGpoint* nvg__lastPoint (NVGContext ctx) nothrow @trusted @nogc { 4174 return (ctx.cache.npoints > 0 ? &ctx.cache.points[ctx.cache.npoints-1] : null); 4175 } 4176 4177 void nvg__addPoint (NVGContext ctx, float x, float y, int flags) nothrow @trusted @nogc { 4178 NVGpath* path = nvg__lastPath(ctx); 4179 if (path is null) return; 4180 4181 if (path.count > 0 && ctx.cache.npoints > 0) { 4182 NVGpoint* pt = nvg__lastPoint(ctx); 4183 if (nvg__ptEquals(pt.x, pt.y, x, y, ctx.distTol)) { 4184 pt.flags |= flags; 4185 return; 4186 } 4187 } 4188 4189 if (ctx.cache.npoints+1 > ctx.cache.cpoints) { 4190 int cpoints = ctx.cache.npoints+1+ctx.cache.cpoints/2; 4191 NVGpoint* points = cast(NVGpoint*)realloc(ctx.cache.points, NVGpoint.sizeof*cpoints); 4192 if (points is null) return; 4193 ctx.cache.points = points; 4194 ctx.cache.cpoints = cpoints; 4195 } 4196 4197 NVGpoint* pt = &ctx.cache.points[ctx.cache.npoints]; 4198 memset(pt, 0, (*pt).sizeof); 4199 pt.x = x; 4200 pt.y = y; 4201 pt.flags = cast(ubyte)flags; 4202 4203 ++ctx.cache.npoints; 4204 ++path.count; 4205 } 4206 4207 void nvg__closePath (NVGContext ctx) nothrow @trusted @nogc { 4208 NVGpath* path = nvg__lastPath(ctx); 4209 if (path is null) return; 4210 path.closed = true; 4211 } 4212 4213 void nvg__pathWinding (NVGContext ctx, NVGWinding winding) nothrow @trusted @nogc { 4214 NVGpath* path = nvg__lastPath(ctx); 4215 if (path is null) return; 4216 path.mWinding = winding; 4217 } 4218 4219 float nvg__getAverageScale() (in auto ref NVGMatrix t) /*pure*/ nothrow @trusted @nogc { 4220 immutable float sx = nvg__sqrtf(t.mat.ptr[0]*t.mat.ptr[0]+t.mat.ptr[2]*t.mat.ptr[2]); 4221 immutable float sy = nvg__sqrtf(t.mat.ptr[1]*t.mat.ptr[1]+t.mat.ptr[3]*t.mat.ptr[3]); 4222 return (sx+sy)*0.5f; 4223 } 4224 4225 NVGVertex* nvg__allocTempVerts (NVGContext ctx, int nverts) nothrow @trusted @nogc { 4226 if (nverts > ctx.cache.cverts) { 4227 int cverts = (nverts+0xff)&~0xff; // Round up to prevent allocations when things change just slightly. 4228 NVGVertex* verts = cast(NVGVertex*)realloc(ctx.cache.verts, NVGVertex.sizeof*cverts); 4229 if (verts is null) return null; 4230 ctx.cache.verts = verts; 4231 ctx.cache.cverts = cverts; 4232 } 4233 4234 return ctx.cache.verts; 4235 } 4236 4237 float nvg__triarea2 (float ax, float ay, float bx, float by, float cx, float cy) pure nothrow @safe @nogc { 4238 immutable float abx = bx-ax; 4239 immutable float aby = by-ay; 4240 immutable float acx = cx-ax; 4241 immutable float acy = cy-ay; 4242 return acx*aby-abx*acy; 4243 } 4244 4245 float nvg__polyArea (NVGpoint* pts, int npts) nothrow @trusted @nogc { 4246 float area = 0; 4247 foreach (int i; 2..npts) { 4248 NVGpoint* a = &pts[0]; 4249 NVGpoint* b = &pts[i-1]; 4250 NVGpoint* c = &pts[i]; 4251 area += nvg__triarea2(a.x, a.y, b.x, b.y, c.x, c.y); 4252 } 4253 return area*0.5f; 4254 } 4255 4256 void nvg__polyReverse (NVGpoint* pts, int npts) nothrow @trusted @nogc { 4257 NVGpoint tmp = void; 4258 int i = 0, j = npts-1; 4259 while (i < j) { 4260 tmp = pts[i]; 4261 pts[i] = pts[j]; 4262 pts[j] = tmp; 4263 ++i; 4264 --j; 4265 } 4266 } 4267 4268 void nvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc { 4269 vtx.x = x; 4270 vtx.y = y; 4271 vtx.u = u; 4272 vtx.v = v; 4273 } 4274 4275 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 { 4276 if (level > 10) return; 4277 4278 // check for collinear points, and use AFD tesselator on such curves (it is WAY faster for this case) 4279 /* 4280 if (level == 0 && ctx.tesselatortype == NVGTesselation.Combined) { 4281 static bool collinear (in float v0x, in float v0y, in float v1x, in float v1y, in float v2x, in float v2y) nothrow @trusted @nogc { 4282 immutable float cz = (v1x-v0x)*(v2y-v0y)-(v2x-v0x)*(v1y-v0y); 4283 return (nvg__absf(cz*cz) <= 0.01f); // arbitrary number, seems to work ok with NanoSVG output 4284 } 4285 if (collinear(x1, y1, x2, y2, x3, y3) && collinear(x2, y2, x3, y3, x3, y4)) { 4286 //{ import core.stdc.stdio; printf("AFD fallback!\n"); } 4287 ctx.nvg__tesselateBezierAFD(x1, y1, x2, y2, x3, y3, x4, y4, type); 4288 return; 4289 } 4290 } 4291 */ 4292 4293 immutable float x12 = (x1+x2)*0.5f; 4294 immutable float y12 = (y1+y2)*0.5f; 4295 immutable float x23 = (x2+x3)*0.5f; 4296 immutable float y23 = (y2+y3)*0.5f; 4297 immutable float x34 = (x3+x4)*0.5f; 4298 immutable float y34 = (y3+y4)*0.5f; 4299 immutable float x123 = (x12+x23)*0.5f; 4300 immutable float y123 = (y12+y23)*0.5f; 4301 4302 immutable float dx = x4-x1; 4303 immutable float dy = y4-y1; 4304 immutable float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx)); 4305 immutable float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx)); 4306 4307 if ((d2+d3)*(d2+d3) < ctx.tessTol*(dx*dx+dy*dy)) { 4308 nvg__addPoint(ctx, x4, y4, type); 4309 return; 4310 } 4311 4312 immutable float x234 = (x23+x34)*0.5f; 4313 immutable float y234 = (y23+y34)*0.5f; 4314 immutable float x1234 = (x123+x234)*0.5f; 4315 immutable float y1234 = (y123+y234)*0.5f; 4316 4317 // "taxicab" / "manhattan" check for flat curves 4318 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) { 4319 nvg__addPoint(ctx, x1234, y1234, type); 4320 return; 4321 } 4322 4323 nvg__tesselateBezier(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0); 4324 nvg__tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type); 4325 } 4326 4327 // based on the ideas and code of Maxim Shemanarev. Rest in Peace, bro! 4328 // see http://www.antigrain.com/research/adaptive_bezier/index.html 4329 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 { 4330 enum CollinearEPS = 0.00000001f; // 0.00001f; 4331 enum AngleTolEPS = 0.01f; 4332 4333 static float distSquared (in float x1, in float y1, in float x2, in float y2) pure nothrow @safe @nogc { 4334 pragma(inline, true); 4335 immutable float dx = x2-x1; 4336 immutable float dy = y2-y1; 4337 return dx*dx+dy*dy; 4338 } 4339 4340 if (level == 0) { 4341 nvg__addPoint(ctx, x1, y1, 0); 4342 nvg__tesselateBezierMcSeem(ctx, x1, y1, x2, y2, x3, y3, x4, y4, 1, type); 4343 nvg__addPoint(ctx, x4, y4, type); 4344 return; 4345 } 4346 4347 if (level >= 32) return; // recurse limit; practically, it should be never reached, but... 4348 4349 // calculate all the mid-points of the line segments 4350 immutable float x12 = (x1+x2)*0.5f; 4351 immutable float y12 = (y1+y2)*0.5f; 4352 immutable float x23 = (x2+x3)*0.5f; 4353 immutable float y23 = (y2+y3)*0.5f; 4354 immutable float x34 = (x3+x4)*0.5f; 4355 immutable float y34 = (y3+y4)*0.5f; 4356 immutable float x123 = (x12+x23)*0.5f; 4357 immutable float y123 = (y12+y23)*0.5f; 4358 immutable float x234 = (x23+x34)*0.5f; 4359 immutable float y234 = (y23+y34)*0.5f; 4360 immutable float x1234 = (x123+x234)*0.5f; 4361 immutable float y1234 = (y123+y234)*0.5f; 4362 4363 // try to approximate the full cubic curve by a single straight line 4364 immutable float dx = x4-x1; 4365 immutable float dy = y4-y1; 4366 4367 float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx)); 4368 float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx)); 4369 //immutable float da1, da2, k; 4370 4371 final switch ((cast(int)(d2 > CollinearEPS)<<1)+cast(int)(d3 > CollinearEPS)) { 4372 case 0: 4373 // all collinear or p1 == p4 4374 float k = dx*dx+dy*dy; 4375 if (k == 0) { 4376 d2 = distSquared(x1, y1, x2, y2); 4377 d3 = distSquared(x4, y4, x3, y3); 4378 } else { 4379 k = 1.0f/k; 4380 float da1 = x2-x1; 4381 float da2 = y2-y1; 4382 d2 = k*(da1*dx+da2*dy); 4383 da1 = x3-x1; 4384 da2 = y3-y1; 4385 d3 = k*(da1*dx+da2*dy); 4386 if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) { 4387 // Simple collinear case, 1---2---3---4 4388 // We can leave just two endpoints 4389 return; 4390 } 4391 if (d2 <= 0) d2 = distSquared(x2, y2, x1, y1); 4392 else if (d2 >= 1) d2 = distSquared(x2, y2, x4, y4); 4393 else d2 = distSquared(x2, y2, x1+d2*dx, y1+d2*dy); 4394 4395 if (d3 <= 0) d3 = distSquared(x3, y3, x1, y1); 4396 else if (d3 >= 1) d3 = distSquared(x3, y3, x4, y4); 4397 else d3 = distSquared(x3, y3, x1+d3*dx, y1+d3*dy); 4398 } 4399 if (d2 > d3) { 4400 if (d2 < ctx.tessTol) { 4401 nvg__addPoint(ctx, x2, y2, type); 4402 return; 4403 } 4404 } if (d3 < ctx.tessTol) { 4405 nvg__addPoint(ctx, x3, y3, type); 4406 return; 4407 } 4408 break; 4409 case 1: 4410 // p1,p2,p4 are collinear, p3 is significant 4411 if (d3*d3 <= ctx.tessTol*(dx*dx+dy*dy)) { 4412 if (ctx.angleTol < AngleTolEPS) { 4413 nvg__addPoint(ctx, x23, y23, type); 4414 return; 4415 } else { 4416 // angle condition 4417 float da1 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-nvg__atan2f(y3-y2, x3-x2)); 4418 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4419 if (da1 < ctx.angleTol) { 4420 nvg__addPoint(ctx, x2, y2, type); 4421 nvg__addPoint(ctx, x3, y3, type); 4422 return; 4423 } 4424 if (ctx.cuspLimit != 0.0) { 4425 if (da1 > ctx.cuspLimit) { 4426 nvg__addPoint(ctx, x3, y3, type); 4427 return; 4428 } 4429 } 4430 } 4431 } 4432 break; 4433 case 2: 4434 // p1,p3,p4 are collinear, p2 is significant 4435 if (d2*d2 <= ctx.tessTol*(dx*dx+dy*dy)) { 4436 if (ctx.angleTol < AngleTolEPS) { 4437 nvg__addPoint(ctx, x23, y23, type); 4438 return; 4439 } else { 4440 // angle condition 4441 float da1 = nvg__absf(nvg__atan2f(y3-y2, x3-x2)-nvg__atan2f(y2-y1, x2-x1)); 4442 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4443 if (da1 < ctx.angleTol) { 4444 nvg__addPoint(ctx, x2, y2, type); 4445 nvg__addPoint(ctx, x3, y3, type); 4446 return; 4447 } 4448 if (ctx.cuspLimit != 0.0) { 4449 if (da1 > ctx.cuspLimit) { 4450 nvg__addPoint(ctx, x2, y2, type); 4451 return; 4452 } 4453 } 4454 } 4455 } 4456 break; 4457 case 3: 4458 // regular case 4459 if ((d2+d3)*(d2+d3) <= ctx.tessTol*(dx*dx+dy*dy)) { 4460 // if the curvature doesn't exceed the distance tolerance value, we tend to finish subdivisions 4461 if (ctx.angleTol < AngleTolEPS) { 4462 nvg__addPoint(ctx, x23, y23, type); 4463 return; 4464 } else { 4465 // angle and cusp condition 4466 immutable float k = nvg__atan2f(y3-y2, x3-x2); 4467 float da1 = nvg__absf(k-nvg__atan2f(y2-y1, x2-x1)); 4468 float da2 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-k); 4469 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4470 if (da2 >= NVG_PI) da2 = 2*NVG_PI-da2; 4471 if (da1+da2 < ctx.angleTol) { 4472 // finally we can stop the recursion 4473 nvg__addPoint(ctx, x23, y23, type); 4474 return; 4475 } 4476 if (ctx.cuspLimit != 0.0) { 4477 if (da1 > ctx.cuspLimit) { 4478 nvg__addPoint(ctx, x2, y2, type); 4479 return; 4480 } 4481 if (da2 > ctx.cuspLimit) { 4482 nvg__addPoint(ctx, x3, y3, type); 4483 return; 4484 } 4485 } 4486 } 4487 } 4488 break; 4489 } 4490 4491 // continue subdivision 4492 nvg__tesselateBezierMcSeem(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0); 4493 nvg__tesselateBezierMcSeem(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type); 4494 } 4495 4496 4497 // Adaptive forward differencing for bezier tesselation. 4498 // See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt. 4499 // "Adaptive forward differencing for rendering curves and surfaces." 4500 // ACM SIGGRAPH Computer Graphics. Vol. 21. No. 4. ACM, 1987. 4501 // original code by Taylor Holliday <taylor@audulus.com> 4502 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 { 4503 enum AFD_ONE = (1<<10); 4504 4505 // power basis 4506 immutable float ax = -x1+3*x2-3*x3+x4; 4507 immutable float ay = -y1+3*y2-3*y3+y4; 4508 immutable float bx = 3*x1-6*x2+3*x3; 4509 immutable float by = 3*y1-6*y2+3*y3; 4510 immutable float cx = -3*x1+3*x2; 4511 immutable float cy = -3*y1+3*y2; 4512 4513 // Transform to forward difference basis (stepsize 1) 4514 float px = x1; 4515 float py = y1; 4516 float dx = ax+bx+cx; 4517 float dy = ay+by+cy; 4518 float ddx = 6*ax+2*bx; 4519 float ddy = 6*ay+2*by; 4520 float dddx = 6*ax; 4521 float dddy = 6*ay; 4522 4523 //printf("dx: %f, dy: %f\n", dx, dy); 4524 //printf("ddx: %f, ddy: %f\n", ddx, ddy); 4525 //printf("dddx: %f, dddy: %f\n", dddx, dddy); 4526 4527 int t = 0; 4528 int dt = AFD_ONE; 4529 4530 immutable float tol = ctx.tessTol*4; 4531 4532 while (t < AFD_ONE) { 4533 // Flatness measure. 4534 float d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4535 4536 // printf("d: %f, th: %f\n", d, th); 4537 4538 // Go to higher resolution if we're moving a lot or overshooting the end. 4539 while ((d > tol && dt > 1) || (t+dt > AFD_ONE)) { 4540 // printf("up\n"); 4541 4542 // Apply L to the curve. Increase curve resolution. 4543 dx = 0.5f*dx-(1.0f/8.0f)*ddx+(1.0f/16.0f)*dddx; 4544 dy = 0.5f*dy-(1.0f/8.0f)*ddy+(1.0f/16.0f)*dddy; 4545 ddx = (1.0f/4.0f)*ddx-(1.0f/8.0f)*dddx; 4546 ddy = (1.0f/4.0f)*ddy-(1.0f/8.0f)*dddy; 4547 dddx = (1.0f/8.0f)*dddx; 4548 dddy = (1.0f/8.0f)*dddy; 4549 4550 // Half the stepsize. 4551 dt >>= 1; 4552 4553 // Recompute d 4554 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4555 } 4556 4557 // Go to lower resolution if we're really flat 4558 // and we aren't going to overshoot the end. 4559 // XXX: tol/32 is just a guess for when we are too flat. 4560 while ((d > 0 && d < tol/32.0f && dt < AFD_ONE) && (t+2*dt <= AFD_ONE)) { 4561 // printf("down\n"); 4562 4563 // Apply L^(-1) to the curve. Decrease curve resolution. 4564 dx = 2*dx+ddx; 4565 dy = 2*dy+ddy; 4566 ddx = 4*ddx+4*dddx; 4567 ddy = 4*ddy+4*dddy; 4568 dddx = 8*dddx; 4569 dddy = 8*dddy; 4570 4571 // Double the stepsize. 4572 dt <<= 1; 4573 4574 // Recompute d 4575 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4576 } 4577 4578 // Forward differencing. 4579 px += dx; 4580 py += dy; 4581 dx += ddx; 4582 dy += ddy; 4583 ddx += dddx; 4584 ddy += dddy; 4585 4586 // Output a point. 4587 nvg__addPoint(ctx, px, py, (t > 0 ? type : 0)); 4588 4589 // Advance along the curve. 4590 t += dt; 4591 4592 // Ensure we don't overshoot. 4593 assert(t <= AFD_ONE); 4594 } 4595 } 4596 4597 4598 void nvg__dashLastPath (NVGContext ctx) nothrow @trusted @nogc { 4599 import core.stdc.stdlib : realloc; 4600 import core.stdc.string : memcpy; 4601 4602 NVGpathCache* cache = ctx.cache; 4603 if (cache.npaths == 0) return; 4604 4605 NVGpath* path = nvg__lastPath(ctx); 4606 if (path is null) return; 4607 4608 NVGstate* state = nvg__getState(ctx); 4609 if (!state.dasherActive) return; 4610 4611 static NVGpoint* pts = null; 4612 static uint ptsCount = 0; 4613 static uint ptsSize = 0; 4614 4615 if (path.count < 2) return; // just in case 4616 4617 // copy path points (reserve one point for closed pathes) 4618 if (ptsSize < path.count+1) { 4619 ptsSize = cast(uint)(path.count+1); 4620 pts = cast(NVGpoint*)realloc(pts, ptsSize*NVGpoint.sizeof); 4621 if (pts is null) assert(0, "NanoVega: out of memory"); 4622 } 4623 ptsCount = cast(uint)path.count; 4624 memcpy(pts, &cache.points[path.first], ptsCount*NVGpoint.sizeof); 4625 // add closing point for closed pathes 4626 if (path.closed && !nvg__ptEquals(pts[0].x, pts[0].y, pts[ptsCount-1].x, pts[ptsCount-1].y, ctx.distTol)) { 4627 pts[ptsCount++] = pts[0]; 4628 } 4629 4630 // remove last path (with its points) 4631 --cache.npaths; 4632 cache.npoints -= path.count; 4633 4634 // add stroked pathes 4635 const(float)* dashes = state.dashes.ptr; 4636 immutable uint dashCount = state.dashCount; 4637 float currDashStart = 0; 4638 uint currDashIdx = 0; 4639 immutable bool firstIsGap = state.firstDashIsGap; 4640 4641 // calculate lengthes 4642 { 4643 NVGpoint* v1 = &pts[0]; 4644 NVGpoint* v2 = &pts[1]; 4645 foreach (immutable _; 0..ptsCount) { 4646 float dx = v2.x-v1.x; 4647 float dy = v2.y-v1.y; 4648 v1.len = nvg__normalize(&dx, &dy); 4649 v1 = v2++; 4650 } 4651 } 4652 4653 void calcDashStart (float ds) { 4654 if (ds < 0) { 4655 ds = ds%state.totalDashLen; 4656 while (ds < 0) ds += state.totalDashLen; 4657 } 4658 currDashIdx = 0; 4659 currDashStart = 0; 4660 while (ds > 0) { 4661 if (ds > dashes[currDashIdx]) { 4662 ds -= dashes[currDashIdx]; 4663 ++currDashIdx; 4664 currDashStart = 0; 4665 if (currDashIdx >= dashCount) currDashIdx = 0; 4666 } else { 4667 currDashStart = ds; 4668 ds = 0; 4669 } 4670 } 4671 } 4672 4673 calcDashStart(state.dashStart); 4674 4675 uint srcPointIdx = 1; 4676 const(NVGpoint)* v1 = &pts[0]; 4677 const(NVGpoint)* v2 = &pts[1]; 4678 float currRest = v1.len; 4679 nvg__addPath(ctx); 4680 nvg__addPoint(ctx, v1.x, v1.y, PointFlag.Corner); 4681 4682 void fixLastPoint () { 4683 auto lpt = nvg__lastPath(ctx); 4684 if (lpt !is null && lpt.count > 0) { 4685 // fix last point 4686 if (auto lps = nvg__lastPoint(ctx)) lps.flags = PointFlag.Corner; 4687 // fix first point 4688 NVGpathCache* cache = ctx.cache; 4689 cache.points[lpt.first].flags = PointFlag.Corner; 4690 } 4691 } 4692 4693 for (;;) { 4694 immutable float dlen = dashes[currDashIdx]; 4695 if (dlen == 0) { 4696 ++currDashIdx; 4697 if (currDashIdx >= dashCount) currDashIdx = 0; 4698 continue; 4699 } 4700 immutable float dashRest = dlen-currDashStart; 4701 if ((currDashIdx&1) != firstIsGap) { 4702 // this is "moveto" command, so create new path 4703 fixLastPoint(); 4704 nvg__addPath(ctx); 4705 } 4706 if (currRest > dashRest) { 4707 currRest -= dashRest; 4708 ++currDashIdx; 4709 if (currDashIdx >= dashCount) currDashIdx = 0; 4710 currDashStart = 0; 4711 nvg__addPoint(ctx, 4712 v2.x-(v2.x-v1.x)*currRest/v1.len, 4713 v2.y-(v2.y-v1.y)*currRest/v1.len, 4714 PointFlag.Corner 4715 ); 4716 } else { 4717 currDashStart += currRest; 4718 nvg__addPoint(ctx, v2.x, v2.y, v1.flags); //k8:fix flags here? 4719 ++srcPointIdx; 4720 v1 = v2; 4721 currRest = v1.len; 4722 if (srcPointIdx >= ptsCount) break; 4723 v2 = &pts[srcPointIdx]; 4724 } 4725 } 4726 fixLastPoint(); 4727 } 4728 4729 4730 version(nanovg_bench_flatten) import iv.timer : Timer; 4731 4732 void nvg__flattenPaths(bool asStroke) (NVGContext ctx) nothrow @trusted @nogc { 4733 version(nanovg_bench_flatten) { 4734 Timer timer; 4735 char[128] tmbuf; 4736 int bzcount; 4737 } 4738 NVGpathCache* cache = ctx.cache; 4739 NVGstate* state = nvg__getState(ctx); 4740 4741 // check if we already did flattening 4742 static if (asStroke) { 4743 if (state.dashCount >= 2) { 4744 if (cache.npaths > 0 && state.lastFlattenDashCount == state.dashCount) return; // already flattened 4745 state.dasherActive = true; 4746 state.lastFlattenDashCount = state.dashCount; 4747 } else { 4748 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened 4749 state.dasherActive = false; 4750 state.lastFlattenDashCount = 0; 4751 } 4752 } else { 4753 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened 4754 state.lastFlattenDashCount = 0; // so next stroke flattening will redo it 4755 state.dasherActive = false; 4756 } 4757 4758 // clear path cache 4759 cache.npaths = 0; 4760 cache.npoints = 0; 4761 4762 // flatten 4763 version(nanovg_bench_flatten) timer.restart(); 4764 int i = 0; 4765 while (i < ctx.ncommands) { 4766 final switch (cast(Command)ctx.commands[i]) { 4767 case Command.MoveTo: 4768 //assert(i+3 <= ctx.ncommands); 4769 static if (asStroke) { 4770 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx); 4771 } 4772 nvg__addPath(ctx); 4773 const p = &ctx.commands[i+1]; 4774 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner); 4775 i += 3; 4776 break; 4777 case Command.LineTo: 4778 //assert(i+3 <= ctx.ncommands); 4779 const p = &ctx.commands[i+1]; 4780 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner); 4781 i += 3; 4782 break; 4783 case Command.BezierTo: 4784 //assert(i+7 <= ctx.ncommands); 4785 const last = nvg__lastPoint(ctx); 4786 if (last !is null) { 4787 const cp1 = &ctx.commands[i+1]; 4788 const cp2 = &ctx.commands[i+3]; 4789 const p = &ctx.commands[i+5]; 4790 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) { 4791 nvg__tesselateBezier(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner); 4792 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) { 4793 nvg__tesselateBezierMcSeem(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner); 4794 } else { 4795 nvg__tesselateBezierAFD(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], PointFlag.Corner); 4796 } 4797 version(nanovg_bench_flatten) ++bzcount; 4798 } 4799 i += 7; 4800 break; 4801 case Command.Close: 4802 //assert(i+1 <= ctx.ncommands); 4803 nvg__closePath(ctx); 4804 i += 1; 4805 break; 4806 case Command.Winding: 4807 //assert(i+2 <= ctx.ncommands); 4808 nvg__pathWinding(ctx, cast(NVGWinding)ctx.commands[i+1]); 4809 i += 2; 4810 break; 4811 } 4812 } 4813 static if (asStroke) { 4814 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx); 4815 } 4816 version(nanovg_bench_flatten) {{ 4817 timer.stop(); 4818 auto xb = timer.toBuffer(tmbuf[]); 4819 import core.stdc.stdio : printf; 4820 printf("flattening time: [%.*s] (%d beziers)\n", cast(uint)xb.length, xb.ptr, bzcount); 4821 }} 4822 4823 cache.bounds.ptr[0] = cache.bounds.ptr[1] = float.max; 4824 cache.bounds.ptr[2] = cache.bounds.ptr[3] = -float.max; 4825 4826 // calculate the direction and length of line segments 4827 version(nanovg_bench_flatten) timer.restart(); 4828 foreach (int j; 0..cache.npaths) { 4829 NVGpath* path = &cache.paths[j]; 4830 NVGpoint* pts = &cache.points[path.first]; 4831 4832 // if the first and last points are the same, remove the last, mark as closed path 4833 NVGpoint* p0 = &pts[path.count-1]; 4834 NVGpoint* p1 = &pts[0]; 4835 if (nvg__ptEquals(p0.x, p0.y, p1.x, p1.y, ctx.distTol)) { 4836 --path.count; 4837 p0 = &pts[path.count-1]; 4838 path.closed = true; 4839 } 4840 4841 // enforce winding 4842 if (path.count > 2) { 4843 immutable float area = nvg__polyArea(pts, path.count); 4844 if (path.mWinding == NVGWinding.CCW && area < 0.0f) nvg__polyReverse(pts, path.count); 4845 if (path.mWinding == NVGWinding.CW && area > 0.0f) nvg__polyReverse(pts, path.count); 4846 } 4847 4848 foreach (immutable _; 0..path.count) { 4849 // calculate segment direction and length 4850 p0.dx = p1.x-p0.x; 4851 p0.dy = p1.y-p0.y; 4852 p0.len = nvg__normalize(&p0.dx, &p0.dy); 4853 // update bounds 4854 cache.bounds.ptr[0] = nvg__min(cache.bounds.ptr[0], p0.x); 4855 cache.bounds.ptr[1] = nvg__min(cache.bounds.ptr[1], p0.y); 4856 cache.bounds.ptr[2] = nvg__max(cache.bounds.ptr[2], p0.x); 4857 cache.bounds.ptr[3] = nvg__max(cache.bounds.ptr[3], p0.y); 4858 // advance 4859 p0 = p1++; 4860 } 4861 } 4862 version(nanovg_bench_flatten) {{ 4863 timer.stop(); 4864 auto xb = timer.toBuffer(tmbuf[]); 4865 import core.stdc.stdio : printf; 4866 printf("segment calculation time: [%.*s]\n", cast(uint)xb.length, xb.ptr); 4867 }} 4868 } 4869 4870 int nvg__curveDivs (float r, float arc, float tol) nothrow @trusted @nogc { 4871 immutable float da = nvg__acosf(r/(r+tol))*2.0f; 4872 return nvg__max(2, cast(int)nvg__ceilf(arc/da)); 4873 } 4874 4875 void nvg__chooseBevel (int bevel, NVGpoint* p0, NVGpoint* p1, float w, float* x0, float* y0, float* x1, float* y1) nothrow @trusted @nogc { 4876 if (bevel) { 4877 *x0 = p1.x+p0.dy*w; 4878 *y0 = p1.y-p0.dx*w; 4879 *x1 = p1.x+p1.dy*w; 4880 *y1 = p1.y-p1.dx*w; 4881 } else { 4882 *x0 = p1.x+p1.dmx*w; 4883 *y0 = p1.y+p1.dmy*w; 4884 *x1 = p1.x+p1.dmx*w; 4885 *y1 = p1.y+p1.dmy*w; 4886 } 4887 } 4888 4889 NVGVertex* nvg__roundJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, int ncap, float fringe) nothrow @trusted @nogc { 4890 float dlx0 = p0.dy; 4891 float dly0 = -p0.dx; 4892 float dlx1 = p1.dy; 4893 float dly1 = -p1.dx; 4894 //NVG_NOTUSED(fringe); 4895 4896 if (p1.flags&PointFlag.Left) { 4897 float lx0 = void, ly0 = void, lx1 = void, ly1 = void; 4898 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1); 4899 immutable float a0 = nvg__atan2f(-dly0, -dlx0); 4900 float a1 = nvg__atan2f(-dly1, -dlx1); 4901 if (a1 > a0) a1 -= NVG_PI*2; 4902 4903 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4904 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4905 4906 int n = nvg__clamp(cast(int)nvg__ceilf(((a0-a1)/NVG_PI)*ncap), 2, ncap); 4907 for (int i = 0; i < n; ++i) { 4908 float u = i/cast(float)(n-1); 4909 float a = a0+u*(a1-a0); 4910 float rx = p1.x+nvg__cosf(a)*rw; 4911 float ry = p1.y+nvg__sinf(a)*rw; 4912 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4913 nvg__vset(dst, rx, ry, ru, 1); ++dst; 4914 } 4915 4916 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4917 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4918 4919 } else { 4920 float rx0 = void, ry0 = void, rx1 = void, ry1 = void; 4921 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1); 4922 immutable float a0 = nvg__atan2f(dly0, dlx0); 4923 float a1 = nvg__atan2f(dly1, dlx1); 4924 if (a1 < a0) a1 += NVG_PI*2; 4925 4926 nvg__vset(dst, p1.x+dlx0*rw, p1.y+dly0*rw, lu, 1); ++dst; 4927 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4928 4929 int n = nvg__clamp(cast(int)nvg__ceilf(((a1-a0)/NVG_PI)*ncap), 2, ncap); 4930 for (int i = 0; i < n; i++) { 4931 float u = i/cast(float)(n-1); 4932 float a = a0+u*(a1-a0); 4933 float lx = p1.x+nvg__cosf(a)*lw; 4934 float ly = p1.y+nvg__sinf(a)*lw; 4935 nvg__vset(dst, lx, ly, lu, 1); ++dst; 4936 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4937 } 4938 4939 nvg__vset(dst, p1.x+dlx1*rw, p1.y+dly1*rw, lu, 1); ++dst; 4940 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 4941 4942 } 4943 return dst; 4944 } 4945 4946 NVGVertex* nvg__bevelJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, float fringe) nothrow @trusted @nogc { 4947 float rx0, ry0, rx1, ry1; 4948 float lx0, ly0, lx1, ly1; 4949 float dlx0 = p0.dy; 4950 float dly0 = -p0.dx; 4951 float dlx1 = p1.dy; 4952 float dly1 = -p1.dx; 4953 //NVG_NOTUSED(fringe); 4954 4955 if (p1.flags&PointFlag.Left) { 4956 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1); 4957 4958 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4959 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4960 4961 if (p1.flags&PointFlag.Bevel) { 4962 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4963 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4964 4965 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4966 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4967 } else { 4968 rx0 = p1.x-p1.dmx*rw; 4969 ry0 = p1.y-p1.dmy*rw; 4970 4971 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4972 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4973 4974 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4975 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4976 4977 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4978 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4979 } 4980 4981 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4982 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4983 4984 } else { 4985 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1); 4986 4987 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 4988 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4989 4990 if (p1.flags&PointFlag.Bevel) { 4991 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 4992 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4993 4994 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 4995 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 4996 } else { 4997 lx0 = p1.x+p1.dmx*lw; 4998 ly0 = p1.y+p1.dmy*lw; 4999 5000 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 5001 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 5002 5003 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 5004 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 5005 5006 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 5007 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 5008 } 5009 5010 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 5011 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 5012 } 5013 5014 return dst; 5015 } 5016 5017 NVGVertex* nvg__buttCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc { 5018 immutable float px = p.x-dx*d; 5019 immutable float py = p.y-dy*d; 5020 immutable float dlx = dy; 5021 immutable float dly = -dx; 5022 nvg__vset(dst, px+dlx*w-dx*aa, py+dly*w-dy*aa, 0, 0); ++dst; 5023 nvg__vset(dst, px-dlx*w-dx*aa, py-dly*w-dy*aa, 1, 0); ++dst; 5024 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5025 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5026 return dst; 5027 } 5028 5029 NVGVertex* nvg__buttCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc { 5030 immutable float px = p.x+dx*d; 5031 immutable float py = p.y+dy*d; 5032 immutable float dlx = dy; 5033 immutable float dly = -dx; 5034 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5035 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5036 nvg__vset(dst, px+dlx*w+dx*aa, py+dly*w+dy*aa, 0, 0); ++dst; 5037 nvg__vset(dst, px-dlx*w+dx*aa, py-dly*w+dy*aa, 1, 0); ++dst; 5038 return dst; 5039 } 5040 5041 NVGVertex* nvg__roundCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc { 5042 immutable float px = p.x; 5043 immutable float py = p.y; 5044 immutable float dlx = dy; 5045 immutable float dly = -dx; 5046 //NVG_NOTUSED(aa); 5047 immutable float ncpf = cast(float)(ncap-1); 5048 foreach (int i; 0..ncap) { 5049 float a = i/*/cast(float)(ncap-1)*//ncpf*NVG_PI; 5050 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w; 5051 nvg__vset(dst, px-dlx*ax-dx*ay, py-dly*ax-dy*ay, 0, 1); ++dst; 5052 nvg__vset(dst, px, py, 0.5f, 1); ++dst; 5053 } 5054 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5055 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5056 return dst; 5057 } 5058 5059 NVGVertex* nvg__roundCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc { 5060 immutable float px = p.x; 5061 immutable float py = p.y; 5062 immutable float dlx = dy; 5063 immutable float dly = -dx; 5064 //NVG_NOTUSED(aa); 5065 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5066 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5067 immutable float ncpf = cast(float)(ncap-1); 5068 foreach (int i; 0..ncap) { 5069 float a = i/*cast(float)(ncap-1)*//ncpf*NVG_PI; 5070 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w; 5071 nvg__vset(dst, px, py, 0.5f, 1); ++dst; 5072 nvg__vset(dst, px-dlx*ax+dx*ay, py-dly*ax+dy*ay, 0, 1); ++dst; 5073 } 5074 return dst; 5075 } 5076 5077 void nvg__calculateJoins (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5078 NVGpathCache* cache = ctx.cache; 5079 float iw = 0.0f; 5080 5081 if (w > 0.0f) iw = 1.0f/w; 5082 5083 // Calculate which joins needs extra vertices to append, and gather vertex count. 5084 foreach (int i; 0..cache.npaths) { 5085 NVGpath* path = &cache.paths[i]; 5086 NVGpoint* pts = &cache.points[path.first]; 5087 NVGpoint* p0 = &pts[path.count-1]; 5088 NVGpoint* p1 = &pts[0]; 5089 int nleft = 0; 5090 5091 path.nbevel = 0; 5092 5093 foreach (int j; 0..path.count) { 5094 //float dlx0, dly0, dlx1, dly1, dmr2, cross, limit; 5095 immutable float dlx0 = p0.dy; 5096 immutable float dly0 = -p0.dx; 5097 immutable float dlx1 = p1.dy; 5098 immutable float dly1 = -p1.dx; 5099 // Calculate extrusions 5100 p1.dmx = (dlx0+dlx1)*0.5f; 5101 p1.dmy = (dly0+dly1)*0.5f; 5102 immutable float dmr2 = p1.dmx*p1.dmx+p1.dmy*p1.dmy; 5103 if (dmr2 > 0.000001f) { 5104 float scale = 1.0f/dmr2; 5105 if (scale > 600.0f) scale = 600.0f; 5106 p1.dmx *= scale; 5107 p1.dmy *= scale; 5108 } 5109 5110 // Clear flags, but keep the corner. 5111 p1.flags = (p1.flags&PointFlag.Corner) ? PointFlag.Corner : 0; 5112 5113 // Keep track of left turns. 5114 immutable float cross = p1.dx*p0.dy-p0.dx*p1.dy; 5115 if (cross > 0.0f) { 5116 nleft++; 5117 p1.flags |= PointFlag.Left; 5118 } 5119 5120 // Calculate if we should use bevel or miter for inner join. 5121 immutable float limit = nvg__max(1.01f, nvg__min(p0.len, p1.len)*iw); 5122 if ((dmr2*limit*limit) < 1.0f) p1.flags |= PointFlag.InnerBevelPR; 5123 5124 // Check to see if the corner needs to be beveled. 5125 if (p1.flags&PointFlag.Corner) { 5126 if ((dmr2*miterLimit*miterLimit) < 1.0f || lineJoin == NVGLineCap.Bevel || lineJoin == NVGLineCap.Round) { 5127 p1.flags |= PointFlag.Bevel; 5128 } 5129 } 5130 5131 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) path.nbevel++; 5132 5133 p0 = p1++; 5134 } 5135 5136 path.convex = (nleft == path.count); 5137 } 5138 } 5139 5140 void nvg__expandStroke (NVGContext ctx, float w, int lineCap, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5141 NVGpathCache* cache = ctx.cache; 5142 immutable float aa = ctx.fringeWidth; 5143 int ncap = nvg__curveDivs(w, NVG_PI, ctx.tessTol); // Calculate divisions per half circle. 5144 5145 nvg__calculateJoins(ctx, w, lineJoin, miterLimit); 5146 5147 // Calculate max vertex usage. 5148 int cverts = 0; 5149 foreach (int i; 0..cache.npaths) { 5150 NVGpath* path = &cache.paths[i]; 5151 immutable bool loop = path.closed; 5152 if (lineJoin == NVGLineCap.Round) { 5153 cverts += (path.count+path.nbevel*(ncap+2)+1)*2; // plus one for loop 5154 } else { 5155 cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop 5156 } 5157 if (!loop) { 5158 // space for caps 5159 if (lineCap == NVGLineCap.Round) { 5160 cverts += (ncap*2+2)*2; 5161 } else { 5162 cverts += (3+3)*2; 5163 } 5164 } 5165 } 5166 5167 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts); 5168 if (verts is null) return; 5169 5170 foreach (int i; 0..cache.npaths) { 5171 NVGpath* path = &cache.paths[i]; 5172 NVGpoint* pts = &cache.points[path.first]; 5173 NVGpoint* p0; 5174 NVGpoint* p1; 5175 int s, e; 5176 5177 path.fill = null; 5178 path.nfill = 0; 5179 5180 // Calculate fringe or stroke 5181 immutable bool loop = path.closed; 5182 NVGVertex* dst = verts; 5183 path.stroke = dst; 5184 5185 if (loop) { 5186 // Looping 5187 p0 = &pts[path.count-1]; 5188 p1 = &pts[0]; 5189 s = 0; 5190 e = path.count; 5191 } else { 5192 // Add cap 5193 p0 = &pts[0]; 5194 p1 = &pts[1]; 5195 s = 1; 5196 e = path.count-1; 5197 } 5198 5199 if (!loop) { 5200 // Add cap 5201 float dx = p1.x-p0.x; 5202 float dy = p1.y-p0.y; 5203 nvg__normalize(&dx, &dy); 5204 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa); 5205 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa); 5206 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa); 5207 } 5208 5209 foreach (int j; s..e) { 5210 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) { 5211 if (lineJoin == NVGLineCap.Round) { 5212 dst = nvg__roundJoin(dst, p0, p1, w, w, 0, 1, ncap, aa); 5213 } else { 5214 dst = nvg__bevelJoin(dst, p0, p1, w, w, 0, 1, aa); 5215 } 5216 } else { 5217 nvg__vset(dst, p1.x+(p1.dmx*w), p1.y+(p1.dmy*w), 0, 1); ++dst; 5218 nvg__vset(dst, p1.x-(p1.dmx*w), p1.y-(p1.dmy*w), 1, 1); ++dst; 5219 } 5220 p0 = p1++; 5221 } 5222 5223 if (loop) { 5224 // Loop it 5225 nvg__vset(dst, verts[0].x, verts[0].y, 0, 1); ++dst; 5226 nvg__vset(dst, verts[1].x, verts[1].y, 1, 1); ++dst; 5227 } else { 5228 // Add cap 5229 float dx = p1.x-p0.x; 5230 float dy = p1.y-p0.y; 5231 nvg__normalize(&dx, &dy); 5232 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa); 5233 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa); 5234 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa); 5235 } 5236 5237 path.nstroke = cast(int)(dst-verts); 5238 5239 verts = dst; 5240 } 5241 } 5242 5243 void nvg__expandFill (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5244 NVGpathCache* cache = ctx.cache; 5245 immutable float aa = ctx.fringeWidth; 5246 bool fringe = (w > 0.0f); 5247 5248 nvg__calculateJoins(ctx, w, lineJoin, miterLimit); 5249 5250 // Calculate max vertex usage. 5251 int cverts = 0; 5252 foreach (int i; 0..cache.npaths) { 5253 NVGpath* path = &cache.paths[i]; 5254 cverts += path.count+path.nbevel+1; 5255 if (fringe) cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop 5256 } 5257 5258 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts); 5259 if (verts is null) return; 5260 5261 bool convex = (cache.npaths == 1 && cache.paths[0].convex); 5262 5263 foreach (int i; 0..cache.npaths) { 5264 NVGpath* path = &cache.paths[i]; 5265 NVGpoint* pts = &cache.points[path.first]; 5266 5267 // Calculate shape vertices. 5268 immutable float woff = 0.5f*aa; 5269 NVGVertex* dst = verts; 5270 path.fill = dst; 5271 5272 if (fringe) { 5273 // Looping 5274 NVGpoint* p0 = &pts[path.count-1]; 5275 NVGpoint* p1 = &pts[0]; 5276 foreach (int j; 0..path.count) { 5277 if (p1.flags&PointFlag.Bevel) { 5278 immutable float dlx0 = p0.dy; 5279 immutable float dly0 = -p0.dx; 5280 immutable float dlx1 = p1.dy; 5281 immutable float dly1 = -p1.dx; 5282 if (p1.flags&PointFlag.Left) { 5283 immutable float lx = p1.x+p1.dmx*woff; 5284 immutable float ly = p1.y+p1.dmy*woff; 5285 nvg__vset(dst, lx, ly, 0.5f, 1); ++dst; 5286 } else { 5287 immutable float lx0 = p1.x+dlx0*woff; 5288 immutable float ly0 = p1.y+dly0*woff; 5289 immutable float lx1 = p1.x+dlx1*woff; 5290 immutable float ly1 = p1.y+dly1*woff; 5291 nvg__vset(dst, lx0, ly0, 0.5f, 1); ++dst; 5292 nvg__vset(dst, lx1, ly1, 0.5f, 1); ++dst; 5293 } 5294 } else { 5295 nvg__vset(dst, p1.x+(p1.dmx*woff), p1.y+(p1.dmy*woff), 0.5f, 1); ++dst; 5296 } 5297 p0 = p1++; 5298 } 5299 } else { 5300 foreach (int j; 0..path.count) { 5301 nvg__vset(dst, pts[j].x, pts[j].y, 0.5f, 1); 5302 ++dst; 5303 } 5304 } 5305 5306 path.nfill = cast(int)(dst-verts); 5307 verts = dst; 5308 5309 // Calculate fringe 5310 if (fringe) { 5311 float lw = w+woff; 5312 immutable float rw = w-woff; 5313 float lu = 0; 5314 immutable float ru = 1; 5315 dst = verts; 5316 path.stroke = dst; 5317 5318 // Create only half a fringe for convex shapes so that 5319 // the shape can be rendered without stenciling. 5320 if (convex) { 5321 lw = woff; // This should generate the same vertex as fill inset above. 5322 lu = 0.5f; // Set outline fade at middle. 5323 } 5324 5325 // Looping 5326 NVGpoint* p0 = &pts[path.count-1]; 5327 NVGpoint* p1 = &pts[0]; 5328 5329 foreach (int j; 0..path.count) { 5330 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) { 5331 dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx.fringeWidth); 5332 } else { 5333 nvg__vset(dst, p1.x+(p1.dmx*lw), p1.y+(p1.dmy*lw), lu, 1); ++dst; 5334 nvg__vset(dst, p1.x-(p1.dmx*rw), p1.y-(p1.dmy*rw), ru, 1); ++dst; 5335 } 5336 p0 = p1++; 5337 } 5338 5339 // Loop it 5340 nvg__vset(dst, verts[0].x, verts[0].y, lu, 1); ++dst; 5341 nvg__vset(dst, verts[1].x, verts[1].y, ru, 1); ++dst; 5342 5343 path.nstroke = cast(int)(dst-verts); 5344 verts = dst; 5345 } else { 5346 path.stroke = null; 5347 path.nstroke = 0; 5348 } 5349 } 5350 } 5351 5352 5353 // ////////////////////////////////////////////////////////////////////////// // 5354 // Paths 5355 5356 /// Clears the current path and sub-paths. 5357 /// Group: paths 5358 @scriptable 5359 public void beginPath (NVGContext ctx) nothrow @trusted @nogc { 5360 ctx.ncommands = 0; 5361 ctx.pathPickRegistered &= NVGPickKind.All; // reset "registered" flags 5362 nvg__clearPathCache(ctx); 5363 } 5364 5365 public alias newPath = beginPath; /// Ditto. 5366 5367 /// Starts new sub-path with specified point as first point. 5368 /// Group: paths 5369 @scriptable 5370 public void moveTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 5371 nvg__appendCommands(ctx, Command.MoveTo, x, y); 5372 } 5373 5374 /// Starts new sub-path with specified point as first point. 5375 /// Arguments: [x, y]* 5376 /// Group: paths 5377 public void moveTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5378 enum ArgC = 2; 5379 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [moveTo] call"); 5380 if (args.length < ArgC) return; 5381 nvg__appendCommands(ctx, Command.MoveTo, args[$-2..$]); 5382 } 5383 5384 /// Adds line segment from the last point in the path to the specified point. 5385 /// Group: paths 5386 @scriptable 5387 public void lineTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 5388 nvg__appendCommands(ctx, Command.LineTo, x, y); 5389 } 5390 5391 /// Adds line segment from the last point in the path to the specified point. 5392 /// Arguments: [x, y]* 5393 /// Group: paths 5394 public void lineTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5395 enum ArgC = 2; 5396 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [lineTo] call"); 5397 if (args.length < ArgC) return; 5398 foreach (immutable idx; 0..args.length/ArgC) { 5399 nvg__appendCommands(ctx, Command.LineTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]); 5400 } 5401 } 5402 5403 /// Adds cubic bezier segment from last point in the path via two control points to the specified point. 5404 /// Group: paths 5405 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 { 5406 nvg__appendCommands(ctx, Command.BezierTo, c1x, c1y, c2x, c2y, x, y); 5407 } 5408 5409 /// Adds cubic bezier segment from last point in the path via two control points to the specified point. 5410 /// Arguments: [c1x, c1y, c2x, c2y, x, y]* 5411 /// Group: paths 5412 public void bezierTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5413 enum ArgC = 6; 5414 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [bezierTo] call"); 5415 if (args.length < ArgC) return; 5416 foreach (immutable idx; 0..args.length/ArgC) { 5417 nvg__appendCommands(ctx, Command.BezierTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]); 5418 } 5419 } 5420 5421 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point. 5422 /// Group: paths 5423 public void quadTo (NVGContext ctx, in float cx, in float cy, in float x, in float y) nothrow @trusted @nogc { 5424 immutable float x0 = ctx.commandx; 5425 immutable float y0 = ctx.commandy; 5426 nvg__appendCommands(ctx, 5427 Command.BezierTo, 5428 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 5429 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 5430 x, y, 5431 ); 5432 } 5433 5434 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point. 5435 /// Arguments: [cx, cy, x, y]* 5436 /// Group: paths 5437 public void quadTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5438 enum ArgC = 4; 5439 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [quadTo] call"); 5440 if (args.length < ArgC) return; 5441 const(float)* aptr = args.ptr; 5442 foreach (immutable idx; 0..args.length/ArgC) { 5443 immutable float x0 = ctx.commandx; 5444 immutable float y0 = ctx.commandy; 5445 immutable float cx = *aptr++; 5446 immutable float cy = *aptr++; 5447 immutable float x = *aptr++; 5448 immutable float y = *aptr++; 5449 nvg__appendCommands(ctx, 5450 Command.BezierTo, 5451 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 5452 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 5453 x, y, 5454 ); 5455 } 5456 } 5457 5458 /// Adds an arc segment at the corner defined by the last path point, and two specified points. 5459 /// Group: paths 5460 public void arcTo (NVGContext ctx, in float x1, in float y1, in float x2, in float y2, in float radius) nothrow @trusted @nogc { 5461 if (ctx.ncommands == 0) return; 5462 5463 immutable float x0 = ctx.commandx; 5464 immutable float y0 = ctx.commandy; 5465 5466 // handle degenerate cases 5467 if (nvg__ptEquals(x0, y0, x1, y1, ctx.distTol) || 5468 nvg__ptEquals(x1, y1, x2, y2, ctx.distTol) || 5469 nvg__distPtSeg(x1, y1, x0, y0, x2, y2) < ctx.distTol*ctx.distTol || 5470 radius < ctx.distTol) 5471 { 5472 ctx.lineTo(x1, y1); 5473 return; 5474 } 5475 5476 // calculate tangential circle to lines (x0, y0)-(x1, y1) and (x1, y1)-(x2, y2) 5477 float dx0 = x0-x1; 5478 float dy0 = y0-y1; 5479 float dx1 = x2-x1; 5480 float dy1 = y2-y1; 5481 nvg__normalize(&dx0, &dy0); 5482 nvg__normalize(&dx1, &dy1); 5483 immutable float a = nvg__acosf(dx0*dx1+dy0*dy1); 5484 immutable float d = radius/nvg__tanf(a/2.0f); 5485 5486 //printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d); 5487 5488 if (d > 10000.0f) { 5489 ctx.lineTo(x1, y1); 5490 return; 5491 } 5492 5493 float cx = void, cy = void, a0 = void, a1 = void; 5494 NVGWinding dir; 5495 if (nvg__cross(dx0, dy0, dx1, dy1) > 0.0f) { 5496 cx = x1+dx0*d+dy0*radius; 5497 cy = y1+dy0*d+-dx0*radius; 5498 a0 = nvg__atan2f(dx0, -dy0); 5499 a1 = nvg__atan2f(-dx1, dy1); 5500 dir = NVGWinding.CW; 5501 //printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); 5502 } else { 5503 cx = x1+dx0*d+-dy0*radius; 5504 cy = y1+dy0*d+dx0*radius; 5505 a0 = nvg__atan2f(-dx0, dy0); 5506 a1 = nvg__atan2f(dx1, -dy1); 5507 dir = NVGWinding.CCW; 5508 //printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); 5509 } 5510 5511 ctx.arc(dir, cx, cy, radius, a0, a1); // first is line 5512 } 5513 5514 5515 /// Adds an arc segment at the corner defined by the last path point, and two specified points. 5516 /// Arguments: [x1, y1, x2, y2, radius]* 5517 /// Group: paths 5518 public void arcTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5519 enum ArgC = 5; 5520 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arcTo] call"); 5521 if (args.length < ArgC) return; 5522 if (ctx.ncommands == 0) return; 5523 const(float)* aptr = args.ptr; 5524 foreach (immutable idx; 0..args.length/ArgC) { 5525 immutable float x0 = ctx.commandx; 5526 immutable float y0 = ctx.commandy; 5527 immutable float x1 = *aptr++; 5528 immutable float y1 = *aptr++; 5529 immutable float x2 = *aptr++; 5530 immutable float y2 = *aptr++; 5531 immutable float radius = *aptr++; 5532 ctx.arcTo(x1, y1, x2, y2, radius); 5533 } 5534 } 5535 5536 /// Closes current sub-path with a line segment. 5537 /// Group: paths 5538 @scriptable 5539 public void closePath (NVGContext ctx) nothrow @trusted @nogc { 5540 nvg__appendCommands(ctx, Command.Close); 5541 } 5542 5543 /// Sets the current sub-path winding, see NVGWinding and NVGSolidity. 5544 /// Group: paths 5545 public void pathWinding (NVGContext ctx, NVGWinding dir) nothrow @trusted @nogc { 5546 nvg__appendCommands(ctx, Command.Winding, cast(float)dir); 5547 } 5548 5549 /// Ditto. 5550 public void pathWinding (NVGContext ctx, NVGSolidity dir) nothrow @trusted @nogc { 5551 nvg__appendCommands(ctx, Command.Winding, cast(float)dir); 5552 } 5553 5554 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r, 5555 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW). 5556 * Angles are specified in radians. 5557 * 5558 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo 5559 * 5560 * Group: paths 5561 */ 5562 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 { 5563 static assert(mode == "original" || mode == "move" || mode == "line"); 5564 5565 float[3+5*7+100] vals = void; 5566 //int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo); 5567 static if (mode == "original") { 5568 immutable int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo); 5569 } else static if (mode == "move") { 5570 enum move = Command.MoveTo; 5571 } else static if (mode == "line") { 5572 enum move = Command.LineTo; 5573 } else { 5574 static assert(0, "wtf?!"); 5575 } 5576 5577 // Clamp angles 5578 float da = a1-a0; 5579 if (dir == NVGWinding.CW) { 5580 if (nvg__absf(da) >= NVG_PI*2) { 5581 da = NVG_PI*2; 5582 } else { 5583 while (da < 0.0f) da += NVG_PI*2; 5584 } 5585 } else { 5586 if (nvg__absf(da) >= NVG_PI*2) { 5587 da = -NVG_PI*2; 5588 } else { 5589 while (da > 0.0f) da -= NVG_PI*2; 5590 } 5591 } 5592 5593 // Split arc into max 90 degree segments. 5594 immutable int ndivs = nvg__max(1, nvg__min(cast(int)(nvg__absf(da)/(NVG_PI*0.5f)+0.5f), 5)); 5595 immutable float hda = (da/cast(float)ndivs)/2.0f; 5596 float kappa = nvg__absf(4.0f/3.0f*(1.0f-nvg__cosf(hda))/nvg__sinf(hda)); 5597 5598 if (dir == NVGWinding.CCW) kappa = -kappa; 5599 5600 int nvals = 0; 5601 float px = 0, py = 0, ptanx = 0, ptany = 0; 5602 foreach (int i; 0..ndivs+1) { 5603 immutable float a = a0+da*(i/cast(float)ndivs); 5604 immutable float dx = nvg__cosf(a); 5605 immutable float dy = nvg__sinf(a); 5606 immutable float x = cx+dx*r; 5607 immutable float y = cy+dy*r; 5608 immutable float tanx = -dy*r*kappa; 5609 immutable float tany = dx*r*kappa; 5610 5611 if (i == 0) { 5612 if (vals.length-nvals < 3) { 5613 // flush 5614 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5615 nvals = 0; 5616 } 5617 vals.ptr[nvals++] = cast(float)move; 5618 vals.ptr[nvals++] = x; 5619 vals.ptr[nvals++] = y; 5620 } else { 5621 if (vals.length-nvals < 7) { 5622 // flush 5623 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5624 nvals = 0; 5625 } 5626 vals.ptr[nvals++] = Command.BezierTo; 5627 vals.ptr[nvals++] = px+ptanx; 5628 vals.ptr[nvals++] = py+ptany; 5629 vals.ptr[nvals++] = x-tanx; 5630 vals.ptr[nvals++] = y-tany; 5631 vals.ptr[nvals++] = x; 5632 vals.ptr[nvals++] = y; 5633 } 5634 px = x; 5635 py = y; 5636 ptanx = tanx; 5637 ptany = tany; 5638 } 5639 5640 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5641 } 5642 5643 5644 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r, 5645 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW). 5646 * Angles are specified in radians. 5647 * 5648 * Arguments: [cx, cy, r, a0, a1]* 5649 * 5650 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo 5651 * 5652 * Group: paths 5653 */ 5654 public void arc(string mode="original") (NVGContext ctx, NVGWinding dir, in float[] args) nothrow @trusted @nogc { 5655 static assert(mode == "original" || mode == "move" || mode == "line"); 5656 enum ArgC = 5; 5657 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arc] call"); 5658 if (args.length < ArgC) return; 5659 const(float)* aptr = args.ptr; 5660 foreach (immutable idx; 0..args.length/ArgC) { 5661 immutable cx = *aptr++; 5662 immutable cy = *aptr++; 5663 immutable r = *aptr++; 5664 immutable a0 = *aptr++; 5665 immutable a1 = *aptr++; 5666 ctx.arc!mode(dir, cx, cy, r, a0, a1); 5667 } 5668 } 5669 5670 /// Creates new rectangle shaped sub-path. 5671 /// Group: paths 5672 @scriptable 5673 public void rect (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc { 5674 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5675 Command.MoveTo, x, y, 5676 Command.LineTo, x, y+h, 5677 Command.LineTo, x+w, y+h, 5678 Command.LineTo, x+w, y, 5679 Command.Close, 5680 ); 5681 } 5682 5683 /// Creates new rectangle shaped sub-path. 5684 /// Arguments: [x, y, w, h]* 5685 /// Group: paths 5686 public void rect (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5687 enum ArgC = 4; 5688 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [rect] call"); 5689 if (args.length < ArgC) return; 5690 const(float)* aptr = args.ptr; 5691 foreach (immutable idx; 0..args.length/ArgC) { 5692 immutable x = *aptr++; 5693 immutable y = *aptr++; 5694 immutable w = *aptr++; 5695 immutable h = *aptr++; 5696 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5697 Command.MoveTo, x, y, 5698 Command.LineTo, x, y+h, 5699 Command.LineTo, x+w, y+h, 5700 Command.LineTo, x+w, y, 5701 Command.Close, 5702 ); 5703 } 5704 } 5705 5706 /// Creates new rounded rectangle shaped sub-path. 5707 /// Group: paths 5708 @scriptable 5709 public void roundedRect (NVGContext ctx, in float x, in float y, in float w, in float h, in float radius) nothrow @trusted @nogc { 5710 ctx.roundedRectVarying(x, y, w, h, radius, radius, radius, radius); 5711 } 5712 5713 /// Creates new rounded rectangle shaped sub-path. 5714 /// Arguments: [x, y, w, h, radius]* 5715 /// Group: paths 5716 public void roundedRect (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5717 enum ArgC = 5; 5718 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRect] call"); 5719 if (args.length < ArgC) return; 5720 const(float)* aptr = args.ptr; 5721 foreach (immutable idx; 0..args.length/ArgC) { 5722 immutable x = *aptr++; 5723 immutable y = *aptr++; 5724 immutable w = *aptr++; 5725 immutable h = *aptr++; 5726 immutable r = *aptr++; 5727 ctx.roundedRectVarying(x, y, w, h, r, r, r, r); 5728 } 5729 } 5730 5731 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it. 5732 /// Group: paths 5733 @scriptable 5734 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 { 5735 if (rw < 0.1f || rh < 0.1f) { 5736 rect(ctx, x, y, w, h); 5737 } else { 5738 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5739 Command.MoveTo, x+rw, y, 5740 Command.LineTo, x+w-rw, y, 5741 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh, 5742 Command.LineTo, x+w, y+h-rh, 5743 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h, 5744 Command.LineTo, x+rw, y+h, 5745 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh, 5746 Command.LineTo, x, y+rh, 5747 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y, 5748 Command.Close, 5749 ); 5750 } 5751 } 5752 5753 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it. 5754 /// Arguments: [x, y, w, h, rw, rh]* 5755 /// Group: paths 5756 public void roundedRectEllipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5757 enum ArgC = 6; 5758 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectEllipse] call"); 5759 if (args.length < ArgC) return; 5760 const(float)* aptr = args.ptr; 5761 foreach (immutable idx; 0..args.length/ArgC) { 5762 immutable x = *aptr++; 5763 immutable y = *aptr++; 5764 immutable w = *aptr++; 5765 immutable h = *aptr++; 5766 immutable rw = *aptr++; 5767 immutable rh = *aptr++; 5768 if (rw < 0.1f || rh < 0.1f) { 5769 rect(ctx, x, y, w, h); 5770 } else { 5771 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5772 Command.MoveTo, x+rw, y, 5773 Command.LineTo, x+w-rw, y, 5774 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh, 5775 Command.LineTo, x+w, y+h-rh, 5776 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h, 5777 Command.LineTo, x+rw, y+h, 5778 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh, 5779 Command.LineTo, x, y+rh, 5780 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y, 5781 Command.Close, 5782 ); 5783 } 5784 } 5785 } 5786 5787 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner. 5788 /// Group: paths 5789 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 { 5790 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { 5791 ctx.rect(x, y, w, h); 5792 } else { 5793 immutable float halfw = nvg__absf(w)*0.5f; 5794 immutable float halfh = nvg__absf(h)*0.5f; 5795 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h); 5796 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h); 5797 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h); 5798 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h); 5799 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5800 Command.MoveTo, x, y+ryTL, 5801 Command.LineTo, x, y+h-ryBL, 5802 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h, 5803 Command.LineTo, x+w-rxBR, y+h, 5804 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR, 5805 Command.LineTo, x+w, y+ryTR, 5806 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y, 5807 Command.LineTo, x+rxTL, y, 5808 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL, 5809 Command.Close, 5810 ); 5811 } 5812 } 5813 5814 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner. 5815 /// Arguments: [x, y, w, h, radTopLeft, radTopRight, radBottomRight, radBottomLeft]* 5816 /// Group: paths 5817 public void roundedRectVarying (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5818 enum ArgC = 8; 5819 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectVarying] call"); 5820 if (args.length < ArgC) return; 5821 const(float)* aptr = args.ptr; 5822 foreach (immutable idx; 0..args.length/ArgC) { 5823 immutable x = *aptr++; 5824 immutable y = *aptr++; 5825 immutable w = *aptr++; 5826 immutable h = *aptr++; 5827 immutable radTopLeft = *aptr++; 5828 immutable radTopRight = *aptr++; 5829 immutable radBottomRight = *aptr++; 5830 immutable radBottomLeft = *aptr++; 5831 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { 5832 ctx.rect(x, y, w, h); 5833 } else { 5834 immutable float halfw = nvg__absf(w)*0.5f; 5835 immutable float halfh = nvg__absf(h)*0.5f; 5836 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h); 5837 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h); 5838 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h); 5839 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h); 5840 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5841 Command.MoveTo, x, y+ryTL, 5842 Command.LineTo, x, y+h-ryBL, 5843 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h, 5844 Command.LineTo, x+w-rxBR, y+h, 5845 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR, 5846 Command.LineTo, x+w, y+ryTR, 5847 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y, 5848 Command.LineTo, x+rxTL, y, 5849 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL, 5850 Command.Close, 5851 ); 5852 } 5853 } 5854 } 5855 5856 /// Creates new ellipse shaped sub-path. 5857 /// Group: paths 5858 public void ellipse (NVGContext ctx, in float cx, in float cy, in float rx, in float ry) nothrow @trusted @nogc { 5859 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5860 Command.MoveTo, cx-rx, cy, 5861 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry, 5862 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy, 5863 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry, 5864 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy, 5865 Command.Close, 5866 ); 5867 } 5868 5869 /// Creates new ellipse shaped sub-path. 5870 /// Arguments: [cx, cy, rx, ry]* 5871 /// Group: paths 5872 public void ellipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5873 enum ArgC = 4; 5874 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [ellipse] call"); 5875 if (args.length < ArgC) return; 5876 const(float)* aptr = args.ptr; 5877 foreach (immutable idx; 0..args.length/ArgC) { 5878 immutable cx = *aptr++; 5879 immutable cy = *aptr++; 5880 immutable rx = *aptr++; 5881 immutable ry = *aptr++; 5882 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5883 Command.MoveTo, cx-rx, cy, 5884 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry, 5885 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy, 5886 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry, 5887 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy, 5888 Command.Close, 5889 ); 5890 } 5891 } 5892 5893 /// Creates new circle shaped sub-path. 5894 /// Group: paths 5895 public void circle (NVGContext ctx, in float cx, in float cy, in float r) nothrow @trusted @nogc { 5896 ctx.ellipse(cx, cy, r, r); 5897 } 5898 5899 /// Creates new circle shaped sub-path. 5900 /// Arguments: [cx, cy, r]* 5901 /// Group: paths 5902 public void circle (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5903 enum ArgC = 3; 5904 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [circle] call"); 5905 if (args.length < ArgC) return; 5906 const(float)* aptr = args.ptr; 5907 foreach (immutable idx; 0..args.length/ArgC) { 5908 immutable cx = *aptr++; 5909 immutable cy = *aptr++; 5910 immutable r = *aptr++; 5911 ctx.ellipse(cx, cy, r, r); 5912 } 5913 } 5914 5915 // Debug function to dump cached path data. 5916 debug public void debugDumpPathCache (NVGContext ctx) nothrow @trusted @nogc { 5917 import core.stdc.stdio : printf; 5918 const(NVGpath)* path; 5919 printf("Dumping %d cached paths\n", ctx.cache.npaths); 5920 for (int i = 0; i < ctx.cache.npaths; ++i) { 5921 path = &ctx.cache.paths[i]; 5922 printf("-Path %d\n", i); 5923 if (path.nfill) { 5924 printf("-fill: %d\n", path.nfill); 5925 for (int j = 0; j < path.nfill; ++j) printf("%f\t%f\n", path.fill[j].x, path.fill[j].y); 5926 } 5927 if (path.nstroke) { 5928 printf("-stroke: %d\n", path.nstroke); 5929 for (int j = 0; j < path.nstroke; ++j) printf("%f\t%f\n", path.stroke[j].x, path.stroke[j].y); 5930 } 5931 } 5932 } 5933 5934 // Flatten path, prepare it for fill operation. 5935 void nvg__prepareFill (NVGContext ctx) nothrow @trusted @nogc { 5936 NVGpathCache* cache = ctx.cache; 5937 NVGstate* state = nvg__getState(ctx); 5938 5939 nvg__flattenPaths!false(ctx); 5940 5941 if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) { 5942 nvg__expandFill(ctx, ctx.fringeWidth, NVGLineCap.Miter, 2.4f); 5943 } else { 5944 nvg__expandFill(ctx, 0.0f, NVGLineCap.Miter, 2.4f); 5945 } 5946 5947 cache.evenOddMode = state.evenOddMode; 5948 cache.fringeWidth = ctx.fringeWidth; 5949 cache.fillReady = true; 5950 cache.strokeReady = false; 5951 cache.clipmode = NVGClipMode.None; 5952 } 5953 5954 // Flatten path, prepare it for stroke operation. 5955 void nvg__prepareStroke (NVGContext ctx) nothrow @trusted @nogc { 5956 NVGstate* state = nvg__getState(ctx); 5957 NVGpathCache* cache = ctx.cache; 5958 5959 nvg__flattenPaths!true(ctx); 5960 5961 immutable float scale = nvg__getAverageScale(state.xform); 5962 float strokeWidth = nvg__clamp(state.strokeWidth*scale, 0.0f, 200.0f); 5963 5964 if (strokeWidth < ctx.fringeWidth) { 5965 // If the stroke width is less than pixel size, use alpha to emulate coverage. 5966 // Since coverage is area, scale by alpha*alpha. 5967 immutable float alpha = nvg__clamp(strokeWidth/ctx.fringeWidth, 0.0f, 1.0f); 5968 cache.strokeAlphaMul = alpha*alpha; 5969 strokeWidth = ctx.fringeWidth; 5970 } else { 5971 cache.strokeAlphaMul = 1.0f; 5972 } 5973 cache.strokeWidth = strokeWidth; 5974 5975 if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) { 5976 nvg__expandStroke(ctx, strokeWidth*0.5f+ctx.fringeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit); 5977 } else { 5978 nvg__expandStroke(ctx, strokeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit); 5979 } 5980 5981 cache.fringeWidth = ctx.fringeWidth; 5982 cache.fillReady = false; 5983 cache.strokeReady = true; 5984 cache.clipmode = NVGClipMode.None; 5985 } 5986 5987 /// Fills the current path with current fill style. 5988 /// Group: paths 5989 @scriptable 5990 public void fill (NVGContext ctx) nothrow @trusted @nogc { 5991 NVGstate* state = nvg__getState(ctx); 5992 5993 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) { 5994 ctx.pathPickRegistered |= NVGPickKind.Fill<<16; 5995 ctx.currFillHitId = ctx.pathPickId; 5996 } 5997 5998 nvg__prepareFill(ctx); 5999 6000 // apply global alpha 6001 NVGPaint fillPaint = state.fill; 6002 fillPaint.innerColor.a *= state.alpha; 6003 fillPaint.middleColor.a *= state.alpha; 6004 fillPaint.outerColor.a *= state.alpha; 6005 6006 ctx.appendCurrentPathToCache(ctx.recset, state.fill); 6007 6008 if (ctx.recblockdraw) return; 6009 6010 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); 6011 6012 // count triangles 6013 foreach (int i; 0..ctx.cache.npaths) { 6014 NVGpath* path = &ctx.cache.paths[i]; 6015 ctx.fillTriCount += path.nfill-2; 6016 ctx.fillTriCount += path.nstroke-2; 6017 ctx.drawCallCount += 2; 6018 } 6019 } 6020 6021 /// Fills the current path with current stroke style. 6022 /// Group: paths 6023 @scriptable 6024 public void stroke (NVGContext ctx) nothrow @trusted @nogc { 6025 NVGstate* state = nvg__getState(ctx); 6026 6027 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) { 6028 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16; 6029 ctx.currStrokeHitId = ctx.pathPickId; 6030 } 6031 6032 nvg__prepareStroke(ctx); 6033 6034 NVGpathCache* cache = ctx.cache; 6035 6036 NVGPaint strokePaint = state.stroke; 6037 strokePaint.innerColor.a *= cache.strokeAlphaMul; 6038 strokePaint.middleColor.a *= cache.strokeAlphaMul; 6039 strokePaint.outerColor.a *= cache.strokeAlphaMul; 6040 6041 // apply global alpha 6042 strokePaint.innerColor.a *= state.alpha; 6043 strokePaint.middleColor.a *= state.alpha; 6044 strokePaint.outerColor.a *= state.alpha; 6045 6046 ctx.appendCurrentPathToCache(ctx.recset, state.stroke); 6047 6048 if (ctx.recblockdraw) return; 6049 6050 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths); 6051 6052 // count triangles 6053 foreach (int i; 0..ctx.cache.npaths) { 6054 NVGpath* path = &ctx.cache.paths[i]; 6055 ctx.strokeTriCount += path.nstroke-2; 6056 ++ctx.drawCallCount; 6057 } 6058 } 6059 6060 /// Sets current path as clipping region. 6061 /// Group: clipping 6062 public void clip (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc { 6063 NVGstate* state = nvg__getState(ctx); 6064 6065 if (aclipmode == NVGClipMode.None) return; 6066 if (ctx.recblockdraw) return; //??? 6067 6068 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr); 6069 6070 /* 6071 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) { 6072 ctx.pathPickRegistered |= NVGPickKind.Fill<<16; 6073 ctx.currFillHitId = ctx.pathPickId; 6074 } 6075 */ 6076 6077 nvg__prepareFill(ctx); 6078 6079 // apply global alpha 6080 NVGPaint fillPaint = state.fill; 6081 fillPaint.innerColor.a *= state.alpha; 6082 fillPaint.middleColor.a *= state.alpha; 6083 fillPaint.outerColor.a *= state.alpha; 6084 6085 //ctx.appendCurrentPathToCache(ctx.recset, state.fill); 6086 6087 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); 6088 6089 // count triangles 6090 foreach (int i; 0..ctx.cache.npaths) { 6091 NVGpath* path = &ctx.cache.paths[i]; 6092 ctx.fillTriCount += path.nfill-2; 6093 ctx.fillTriCount += path.nstroke-2; 6094 ctx.drawCallCount += 2; 6095 } 6096 } 6097 6098 /// Sets current path as clipping region. 6099 /// Group: clipping 6100 public alias clipFill = clip; 6101 6102 /// Sets current path' stroke as clipping region. 6103 /// Group: clipping 6104 public void clipStroke (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc { 6105 NVGstate* state = nvg__getState(ctx); 6106 6107 if (aclipmode == NVGClipMode.None) return; 6108 if (ctx.recblockdraw) return; //??? 6109 6110 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr); 6111 6112 /* 6113 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) { 6114 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16; 6115 ctx.currStrokeHitId = ctx.pathPickId; 6116 } 6117 */ 6118 6119 nvg__prepareStroke(ctx); 6120 6121 NVGpathCache* cache = ctx.cache; 6122 6123 NVGPaint strokePaint = state.stroke; 6124 strokePaint.innerColor.a *= cache.strokeAlphaMul; 6125 strokePaint.middleColor.a *= cache.strokeAlphaMul; 6126 strokePaint.outerColor.a *= cache.strokeAlphaMul; 6127 6128 // apply global alpha 6129 strokePaint.innerColor.a *= state.alpha; 6130 strokePaint.middleColor.a *= state.alpha; 6131 strokePaint.outerColor.a *= state.alpha; 6132 6133 //ctx.appendCurrentPathToCache(ctx.recset, state.stroke); 6134 6135 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, aclipmode, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths); 6136 6137 // count triangles 6138 foreach (int i; 0..ctx.cache.npaths) { 6139 NVGpath* path = &ctx.cache.paths[i]; 6140 ctx.strokeTriCount += path.nstroke-2; 6141 ++ctx.drawCallCount; 6142 } 6143 } 6144 6145 6146 // ////////////////////////////////////////////////////////////////////////// // 6147 // Picking API 6148 6149 // most of the code is by Michael Wynne <mike@mikesspace.net> 6150 // https://github.com/memononen/nanovg/pull/230 6151 // https://github.com/MikeWW/nanovg 6152 6153 /// Pick type query. Used in [hitTest] and [hitTestAll]. 6154 /// Group: picking_api 6155 public enum NVGPickKind : ubyte { 6156 Fill = 0x01, /// 6157 Stroke = 0x02, /// 6158 All = 0x03, /// 6159 } 6160 6161 /// Marks the fill of the current path as pickable with the specified id. 6162 /// Note that you can create and mark path without rasterizing it. 6163 /// Group: picking_api 6164 public void currFillHitId (NVGContext ctx, int id) nothrow @trusted @nogc { 6165 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6166 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/false); 6167 nvg__pickSceneInsert(ps, pp); 6168 } 6169 6170 public alias currFillPickId = currFillHitId; /// Ditto. 6171 6172 /// Marks the stroke of the current path as pickable with the specified id. 6173 /// Note that you can create and mark path without rasterizing it. 6174 /// Group: picking_api 6175 public void currStrokeHitId (NVGContext ctx, int id) nothrow @trusted @nogc { 6176 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6177 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/true); 6178 nvg__pickSceneInsert(ps, pp); 6179 } 6180 6181 public alias currStrokePickId = currStrokeHitId; /// Ditto. 6182 6183 // Marks the saved path set (fill) as pickable with the specified id. 6184 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!) 6185 // Group: picking_api 6186 /+ 6187 public void pathSetFillHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc { 6188 if (svp is null) return; 6189 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context"); 6190 foreach (ref cp; svp.caches[0..svp.ncaches]) { 6191 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6192 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/false); 6193 nvg__pickSceneInsert(ps, pp); 6194 } 6195 } 6196 +/ 6197 6198 // Marks the saved path set (stroke) as pickable with the specified id. 6199 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!) 6200 // Group: picking_api 6201 /+ 6202 public void pathSetStrokeHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc { 6203 if (svp is null) return; 6204 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context"); 6205 foreach (ref cp; svp.caches[0..svp.ncaches]) { 6206 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6207 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/true); 6208 nvg__pickSceneInsert(ps, pp); 6209 } 6210 } 6211 +/ 6212 6213 private template IsGoodHitTestDG(DG) { 6214 enum IsGoodHitTestDG = 6215 __traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); }) || 6216 __traits(compiles, (){ DG dg; dg(cast(int)42, cast(int)666); }); 6217 } 6218 6219 private template IsGoodHitTestInternalDG(DG) { 6220 enum IsGoodHitTestInternalDG = 6221 __traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); }) || 6222 __traits(compiles, (){ DG dg; NVGpickPath* pp; dg(pp); }); 6223 } 6224 6225 /// Call delegate [dg] for each path under the specified position (in no particular order). 6226 /// Returns the id of the path for which delegate [dg] returned true or [NVGNoPick]. 6227 /// dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending). 6228 /// Group: picking_api 6229 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) { 6230 if (ctx.pickScene is null || ctx.pickScene.npaths == 0 || (kind&NVGPickKind.All) == 0) return -1; 6231 6232 NVGpickScene* ps = ctx.pickScene; 6233 int levelwidth = 1<<(ps.nlevels-1); 6234 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth); 6235 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth); 6236 int npicked = 0; 6237 6238 // if we are interested only in most-toplevel path, there is no reason to check paths with worser order. 6239 // but we cannot just get out on the first path found, 'cause we are using quad tree to speed up bounds 6240 // checking, so path walking order is not guaranteed. 6241 static if (bestOrder) { 6242 int lastBestOrder = int.min; 6243 } 6244 6245 //{ import core.stdc.stdio; printf("npaths=%d\n", ps.npaths); } 6246 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) { 6247 for (NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; pp !is null; pp = pp.next) { 6248 //{ 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); } 6249 static if (bestOrder) { 6250 // reject earlier paths 6251 if (pp.order <= lastBestOrder) continue; // not interesting 6252 } 6253 immutable uint kpx = kind&pp.flags&3; 6254 if (kpx == 0) continue; // not interesting 6255 if (!nvg__pickPathTestBounds(ctx, ps, pp, x, y)) continue; // not interesting 6256 //{ import core.stdc.stdio; printf("in bounds!\n"); } 6257 int hit = 0; 6258 if (kpx&NVGPickKind.Stroke) hit = nvg__pickPathStroke(ps, pp, x, y); 6259 if (!hit && (kpx&NVGPickKind.Fill)) hit = nvg__pickPath(ps, pp, x, y); 6260 if (!hit) continue; 6261 //{ import core.stdc.stdio; printf(" HIT!\n"); } 6262 static if (bestOrder) lastBestOrder = pp.order; 6263 static if (IsGoodHitTestDG!DG) { 6264 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) { 6265 if (dg(pp.id, cast(int)pp.order)) return pp.id; 6266 } else { 6267 dg(pp.id, cast(int)pp.order); 6268 } 6269 } else { 6270 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) { 6271 if (dg(pp)) return pp.id; 6272 } else { 6273 dg(pp); 6274 } 6275 } 6276 } 6277 cellx >>= 1; 6278 celly >>= 1; 6279 levelwidth >>= 1; 6280 } 6281 6282 return -1; 6283 } 6284 6285 /// Fills ids with a list of the top most hit ids (from bottom to top) under the specified position. 6286 /// Returns the slice of [ids]. 6287 /// Group: picking_api 6288 public int[] hitTestAll (NVGContext ctx, in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc { 6289 if (ctx.pickScene is null || ids.length == 0) return ids[0..0]; 6290 6291 int npicked = 0; 6292 NVGpickScene* ps = ctx.pickScene; 6293 6294 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 6295 if (npicked == ps.cpicked) { 6296 int cpicked = ps.cpicked+ps.cpicked; 6297 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked); 6298 if (picked is null) return true; // abort 6299 ps.cpicked = cpicked; 6300 ps.picked = picked; 6301 } 6302 ps.picked[npicked] = pp; 6303 ++npicked; 6304 return false; // go on 6305 }); 6306 6307 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths); 6308 6309 assert(npicked >= 0); 6310 if (npicked > ids.length) npicked = cast(int)ids.length; 6311 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id; 6312 6313 return ids[0..npicked]; 6314 } 6315 6316 /// Returns the id of the pickable shape containing x,y or [NVGNoPick] if no shape was found. 6317 /// Group: picking_api 6318 public int hitTest (NVGContext ctx, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc { 6319 if (ctx.pickScene is null) return -1; 6320 6321 int bestOrder = int.min; 6322 int bestID = -1; 6323 6324 ctx.hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) { 6325 if (pp.order > bestOrder) { 6326 bestOrder = pp.order; 6327 bestID = pp.id; 6328 } 6329 }); 6330 6331 return bestID; 6332 } 6333 6334 /// Returns `true` if the path with the given id contains x,y. 6335 /// Group: picking_api 6336 public bool hitTestForId (NVGContext ctx, in int id, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc { 6337 if (ctx.pickScene is null || id == NVGNoPick) return false; 6338 6339 bool res = false; 6340 6341 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) { 6342 if (pp.id == id) { 6343 res = true; 6344 return true; // stop 6345 } 6346 return false; // continue 6347 }); 6348 6349 return res; 6350 } 6351 6352 /// Returns `true` if the given point is within the fill of the currently defined path. 6353 /// This operation can be done before rasterizing the current path. 6354 /// Group: picking_api 6355 public bool hitTestCurrFill (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 6356 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6357 int oldnpoints = ps.npoints; 6358 int oldnsegments = ps.nsegments; 6359 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/false); 6360 if (pp is null) return false; // oops 6361 scope(exit) { 6362 nvg__freePickPath(ps, pp); 6363 ps.npoints = oldnpoints; 6364 ps.nsegments = oldnsegments; 6365 } 6366 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPath(ps, pp, x, y) : false); 6367 } 6368 6369 public alias isPointInPath = hitTestCurrFill; /// Ditto. 6370 6371 /// Returns `true` if the given point is within the stroke of the currently defined path. 6372 /// This operation can be done before rasterizing the current path. 6373 /// Group: picking_api 6374 public bool hitTestCurrStroke (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 6375 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6376 int oldnpoints = ps.npoints; 6377 int oldnsegments = ps.nsegments; 6378 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/true); 6379 if (pp is null) return false; // oops 6380 scope(exit) { 6381 nvg__freePickPath(ps, pp); 6382 ps.npoints = oldnpoints; 6383 ps.nsegments = oldnsegments; 6384 } 6385 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPathStroke(ps, pp, x, y) : false); 6386 } 6387 6388 6389 nothrow @trusted @nogc { 6390 extern(C) { 6391 private alias _compare_fp_t = int function (const void*, const void*) nothrow @nogc; 6392 private extern(C) void qsort (scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc; 6393 6394 extern(C) int nvg__comparePaths (const void* a, const void* b) { 6395 return (*cast(const(NVGpickPath)**)b).order-(*cast(const(NVGpickPath)**)a).order; 6396 } 6397 } 6398 6399 enum NVGPickEPS = 0.0001f; 6400 6401 // Segment flags 6402 enum NVGSegmentFlags { 6403 Corner = 1, 6404 Bevel = 2, 6405 InnerBevel = 4, 6406 Cap = 8, 6407 Endcap = 16, 6408 } 6409 6410 // Path flags 6411 enum NVGPathFlags : ushort { 6412 Fill = NVGPickKind.Fill, 6413 Stroke = NVGPickKind.Stroke, 6414 Scissor = 0x80, 6415 } 6416 6417 struct NVGsegment { 6418 int firstPoint; // Index into NVGpickScene.points 6419 short type; // NVG_LINETO or NVG_BEZIERTO 6420 short flags; // Flags relate to the corner between the prev segment and this one. 6421 float[4] bounds; 6422 float[2] startDir; // Direction at t == 0 6423 float[2] endDir; // Direction at t == 1 6424 float[2] miterDir; // Direction of miter of corner between the prev segment and this one. 6425 } 6426 6427 struct NVGpickSubPath { 6428 short winding; // TODO: Merge to flag field 6429 bool closed; // TODO: Merge to flag field 6430 6431 int firstSegment; // Index into NVGpickScene.segments 6432 int nsegments; 6433 6434 float[4] bounds; 6435 6436 NVGpickSubPath* next; 6437 } 6438 6439 struct NVGpickPath { 6440 int id; 6441 short flags; 6442 short order; 6443 float strokeWidth; 6444 float miterLimit; 6445 short lineCap; 6446 short lineJoin; 6447 bool evenOddMode; 6448 6449 float[4] bounds; 6450 int scissor; // Indexes into ps->points and defines scissor rect as XVec, YVec and Center 6451 6452 NVGpickSubPath* subPaths; 6453 NVGpickPath* next; 6454 NVGpickPath* cellnext; 6455 } 6456 6457 struct NVGpickScene { 6458 int npaths; 6459 6460 NVGpickPath* paths; // Linked list of paths 6461 NVGpickPath* lastPath; // The last path in the paths linked list (the first path added) 6462 NVGpickPath* freePaths; // Linked list of free paths 6463 6464 NVGpickSubPath* freeSubPaths; // Linked list of free sub paths 6465 6466 int width; 6467 int height; 6468 6469 // Points for all path sub paths. 6470 float* points; 6471 int npoints; 6472 int cpoints; 6473 6474 // Segments for all path sub paths 6475 NVGsegment* segments; 6476 int nsegments; 6477 int csegments; 6478 6479 // Implicit quadtree 6480 float xdim; // Width / (1 << nlevels) 6481 float ydim; // Height / (1 << nlevels) 6482 int ncells; // Total number of cells in all levels 6483 int nlevels; 6484 NVGpickPath*** levels; // Index: [Level][LevelY * LevelW + LevelX] Value: Linked list of paths 6485 6486 // Temp storage for picking 6487 int cpicked; 6488 NVGpickPath** picked; 6489 } 6490 6491 6492 // bounds utilities 6493 void nvg__initBounds (ref float[4] bounds) { 6494 bounds.ptr[0] = bounds.ptr[1] = float.max; 6495 bounds.ptr[2] = bounds.ptr[3] = -float.max; 6496 } 6497 6498 void nvg__expandBounds (ref float[4] bounds, const(float)* points, int npoints) { 6499 npoints *= 2; 6500 for (int i = 0; i < npoints; i += 2) { 6501 bounds.ptr[0] = nvg__min(bounds.ptr[0], points[i]); 6502 bounds.ptr[1] = nvg__min(bounds.ptr[1], points[i+1]); 6503 bounds.ptr[2] = nvg__max(bounds.ptr[2], points[i]); 6504 bounds.ptr[3] = nvg__max(bounds.ptr[3], points[i+1]); 6505 } 6506 } 6507 6508 void nvg__unionBounds (ref float[4] bounds, in ref float[4] boundsB) { 6509 bounds.ptr[0] = nvg__min(bounds.ptr[0], boundsB.ptr[0]); 6510 bounds.ptr[1] = nvg__min(bounds.ptr[1], boundsB.ptr[1]); 6511 bounds.ptr[2] = nvg__max(bounds.ptr[2], boundsB.ptr[2]); 6512 bounds.ptr[3] = nvg__max(bounds.ptr[3], boundsB.ptr[3]); 6513 } 6514 6515 void nvg__intersectBounds (ref float[4] bounds, in ref float[4] boundsB) { 6516 bounds.ptr[0] = nvg__max(boundsB.ptr[0], bounds.ptr[0]); 6517 bounds.ptr[1] = nvg__max(boundsB.ptr[1], bounds.ptr[1]); 6518 bounds.ptr[2] = nvg__min(boundsB.ptr[2], bounds.ptr[2]); 6519 bounds.ptr[3] = nvg__min(boundsB.ptr[3], bounds.ptr[3]); 6520 6521 bounds.ptr[2] = nvg__max(bounds.ptr[0], bounds.ptr[2]); 6522 bounds.ptr[3] = nvg__max(bounds.ptr[1], bounds.ptr[3]); 6523 } 6524 6525 bool nvg__pointInBounds (in float x, in float y, in ref float[4] bounds) { 6526 pragma(inline, true); 6527 return (x >= bounds.ptr[0] && x <= bounds.ptr[2] && y >= bounds.ptr[1] && y <= bounds.ptr[3]); 6528 } 6529 6530 // building paths & sub paths 6531 int nvg__pickSceneAddPoints (NVGpickScene* ps, const(float)* xy, int n) { 6532 import core.stdc.string : memcpy; 6533 if (ps.npoints+n > ps.cpoints) { 6534 import core.stdc.stdlib : realloc; 6535 int cpoints = ps.npoints+n+(ps.cpoints<<1); 6536 float* points = cast(float*)realloc(ps.points, float.sizeof*2*cpoints); 6537 if (points is null) assert(0, "NanoVega: out of memory"); 6538 ps.points = points; 6539 ps.cpoints = cpoints; 6540 } 6541 int i = ps.npoints; 6542 if (xy !is null) memcpy(&ps.points[i*2], xy, float.sizeof*2*n); 6543 ps.npoints += n; 6544 return i; 6545 } 6546 6547 void nvg__pickSubPathAddSegment (NVGpickScene* ps, NVGpickSubPath* psp, int firstPoint, int type, short flags) { 6548 NVGsegment* seg = null; 6549 if (ps.nsegments == ps.csegments) { 6550 int csegments = 1+ps.csegments+(ps.csegments<<1); 6551 NVGsegment* segments = cast(NVGsegment*)realloc(ps.segments, NVGsegment.sizeof*csegments); 6552 if (segments is null) assert(0, "NanoVega: out of memory"); 6553 ps.segments = segments; 6554 ps.csegments = csegments; 6555 } 6556 6557 if (psp.firstSegment == -1) psp.firstSegment = ps.nsegments; 6558 6559 seg = &ps.segments[ps.nsegments]; 6560 ++ps.nsegments; 6561 seg.firstPoint = firstPoint; 6562 seg.type = cast(short)type; 6563 seg.flags = flags; 6564 ++psp.nsegments; 6565 6566 nvg__segmentDir(ps, psp, seg, 0, seg.startDir); 6567 nvg__segmentDir(ps, psp, seg, 1, seg.endDir); 6568 } 6569 6570 void nvg__segmentDir (NVGpickScene* ps, NVGpickSubPath* psp, NVGsegment* seg, float t, ref float[2] d) { 6571 const(float)* points = &ps.points[seg.firstPoint*2]; 6572 immutable float x0 = points[0*2+0], x1 = points[1*2+0]; 6573 immutable float y0 = points[0*2+1], y1 = points[1*2+1]; 6574 switch (seg.type) { 6575 case Command.LineTo: 6576 d.ptr[0] = x1-x0; 6577 d.ptr[1] = y1-y0; 6578 nvg__normalize(&d.ptr[0], &d.ptr[1]); 6579 break; 6580 case Command.BezierTo: 6581 immutable float x2 = points[2*2+0]; 6582 immutable float y2 = points[2*2+1]; 6583 immutable float x3 = points[3*2+0]; 6584 immutable float y3 = points[3*2+1]; 6585 6586 immutable float omt = 1.0f-t; 6587 immutable float omt2 = omt*omt; 6588 immutable float t2 = t*t; 6589 6590 d.ptr[0] = 6591 3.0f*omt2*(x1-x0)+ 6592 6.0f*omt*t*(x2-x1)+ 6593 3.0f*t2*(x3-x2); 6594 6595 d.ptr[1] = 6596 3.0f*omt2*(y1-y0)+ 6597 6.0f*omt*t*(y2-y1)+ 6598 3.0f*t2*(y3-y2); 6599 6600 nvg__normalize(&d.ptr[0], &d.ptr[1]); 6601 break; 6602 default: 6603 break; 6604 } 6605 } 6606 6607 void nvg__pickSubPathAddFillSupports (NVGpickScene* ps, NVGpickSubPath* psp) { 6608 if (psp.firstSegment == -1) return; 6609 NVGsegment* segments = &ps.segments[psp.firstSegment]; 6610 for (int s = 0; s < psp.nsegments; ++s) { 6611 NVGsegment* seg = &segments[s]; 6612 const(float)* points = &ps.points[seg.firstPoint*2]; 6613 if (seg.type == Command.LineTo) { 6614 nvg__initBounds(seg.bounds); 6615 nvg__expandBounds(seg.bounds, points, 2); 6616 } else { 6617 nvg__bezierBounds(points, seg.bounds); 6618 } 6619 } 6620 } 6621 6622 void nvg__pickSubPathAddStrokeSupports (NVGpickScene* ps, NVGpickSubPath* psp, float strokeWidth, int lineCap, int lineJoin, float miterLimit) { 6623 if (psp.firstSegment == -1) return; 6624 immutable bool closed = psp.closed; 6625 const(float)* points = ps.points; 6626 NVGsegment* seg = null; 6627 NVGsegment* segments = &ps.segments[psp.firstSegment]; 6628 int nsegments = psp.nsegments; 6629 NVGsegment* prevseg = (closed ? &segments[psp.nsegments-1] : null); 6630 6631 int ns = 0; // nsupports 6632 float[32] supportingPoints = void; 6633 int firstPoint, lastPoint; 6634 6635 if (!closed) { 6636 segments[0].flags |= NVGSegmentFlags.Cap; 6637 segments[nsegments-1].flags |= NVGSegmentFlags.Endcap; 6638 } 6639 6640 for (int s = 0; s < nsegments; ++s) { 6641 seg = &segments[s]; 6642 nvg__initBounds(seg.bounds); 6643 6644 firstPoint = seg.firstPoint*2; 6645 lastPoint = firstPoint+(seg.type == Command.LineTo ? 2 : 6); 6646 6647 ns = 0; 6648 6649 // First two supporting points are either side of the start point 6650 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[1]*strokeWidth; 6651 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.startDir.ptr[0]*strokeWidth; 6652 6653 supportingPoints.ptr[ns++] = points[firstPoint]+seg.startDir.ptr[1]*strokeWidth; 6654 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[0]*strokeWidth; 6655 6656 // Second two supporting points are either side of the end point 6657 supportingPoints.ptr[ns++] = points[lastPoint]-seg.endDir.ptr[1]*strokeWidth; 6658 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[0]*strokeWidth; 6659 6660 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[1]*strokeWidth; 6661 supportingPoints.ptr[ns++] = points[lastPoint+1]-seg.endDir.ptr[0]*strokeWidth; 6662 6663 if ((seg.flags&NVGSegmentFlags.Corner) && prevseg !is null) { 6664 seg.miterDir.ptr[0] = 0.5f*(-prevseg.endDir.ptr[1]-seg.startDir.ptr[1]); 6665 seg.miterDir.ptr[1] = 0.5f*(prevseg.endDir.ptr[0]+seg.startDir.ptr[0]); 6666 6667 immutable float M2 = seg.miterDir.ptr[0]*seg.miterDir.ptr[0]+seg.miterDir.ptr[1]*seg.miterDir.ptr[1]; 6668 6669 if (M2 > 0.000001f) { 6670 float scale = 1.0f/M2; 6671 if (scale > 600.0f) scale = 600.0f; 6672 seg.miterDir.ptr[0] *= scale; 6673 seg.miterDir.ptr[1] *= scale; 6674 } 6675 6676 //NVG_PICK_DEBUG_VECTOR_SCALE(&points[firstPoint], seg.miterDir, 10); 6677 6678 // Add an additional support at the corner on the other line 6679 supportingPoints.ptr[ns++] = points[firstPoint]-prevseg.endDir.ptr[1]*strokeWidth; 6680 supportingPoints.ptr[ns++] = points[firstPoint+1]+prevseg.endDir.ptr[0]*strokeWidth; 6681 6682 if (lineJoin == NVGLineCap.Miter || lineJoin == NVGLineCap.Bevel) { 6683 // Set a corner as beveled if the join type is bevel or mitered and 6684 // miterLimit is hit. 6685 if (lineJoin == NVGLineCap.Bevel || (M2*miterLimit*miterLimit) < 1.0f) { 6686 seg.flags |= NVGSegmentFlags.Bevel; 6687 } else { 6688 // Corner is mitered - add miter point as a support 6689 supportingPoints.ptr[ns++] = points[firstPoint]+seg.miterDir.ptr[0]*strokeWidth; 6690 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.miterDir.ptr[1]*strokeWidth; 6691 } 6692 } else if (lineJoin == NVGLineCap.Round) { 6693 // ... and at the midpoint of the corner arc 6694 float[2] vertexN = [ -seg.startDir.ptr[0]+prevseg.endDir.ptr[0], -seg.startDir.ptr[1]+prevseg.endDir.ptr[1] ]; 6695 nvg__normalize(&vertexN[0], &vertexN[1]); 6696 6697 supportingPoints.ptr[ns++] = points[firstPoint]+vertexN[0]*strokeWidth; 6698 supportingPoints.ptr[ns++] = points[firstPoint+1]+vertexN[1]*strokeWidth; 6699 } 6700 } 6701 6702 if (seg.flags&NVGSegmentFlags.Cap) { 6703 switch (lineCap) { 6704 case NVGLineCap.Butt: 6705 // supports for butt already added 6706 break; 6707 case NVGLineCap.Square: 6708 // square cap supports are just the original two supports moved out along the direction 6709 supportingPoints.ptr[ns++] = supportingPoints.ptr[0]-seg.startDir.ptr[0]*strokeWidth; 6710 supportingPoints.ptr[ns++] = supportingPoints.ptr[1]-seg.startDir.ptr[1]*strokeWidth; 6711 supportingPoints.ptr[ns++] = supportingPoints.ptr[2]-seg.startDir.ptr[0]*strokeWidth; 6712 supportingPoints.ptr[ns++] = supportingPoints.ptr[3]-seg.startDir.ptr[1]*strokeWidth; 6713 break; 6714 case NVGLineCap.Round: 6715 // add one additional support for the round cap along the dir 6716 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[0]*strokeWidth; 6717 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[1]*strokeWidth; 6718 break; 6719 default: 6720 break; 6721 } 6722 } 6723 6724 if (seg.flags&NVGSegmentFlags.Endcap) { 6725 // end supporting points, either side of line 6726 int end = 4; 6727 switch(lineCap) { 6728 case NVGLineCap.Butt: 6729 // supports for butt already added 6730 break; 6731 case NVGLineCap.Square: 6732 // square cap supports are just the original two supports moved out along the direction 6733 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+0]+seg.endDir.ptr[0]*strokeWidth; 6734 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+1]+seg.endDir.ptr[1]*strokeWidth; 6735 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+2]+seg.endDir.ptr[0]*strokeWidth; 6736 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+3]+seg.endDir.ptr[1]*strokeWidth; 6737 break; 6738 case NVGLineCap.Round: 6739 // add one additional support for the round cap along the dir 6740 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[0]*strokeWidth; 6741 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[1]*strokeWidth; 6742 break; 6743 default: 6744 break; 6745 } 6746 } 6747 6748 nvg__expandBounds(seg.bounds, supportingPoints.ptr, ns/2); 6749 6750 prevseg = seg; 6751 } 6752 } 6753 6754 NVGpickPath* nvg__pickPathCreate (NVGContext context, const(float)[] acommands, int id, bool forStroke) { 6755 NVGpickScene* ps = nvg__pickSceneGet(context); 6756 if (ps is null) return null; 6757 6758 int i = 0; 6759 6760 int ncommands = cast(int)acommands.length; 6761 const(float)* commands = acommands.ptr; 6762 6763 NVGpickPath* pp = null; 6764 NVGpickSubPath* psp = null; 6765 float[2] start = void; 6766 int firstPoint; 6767 6768 //bool hasHoles = false; 6769 NVGpickSubPath* prev = null; 6770 6771 float[8] points = void; 6772 float[2] inflections = void; 6773 int ninflections = 0; 6774 6775 NVGstate* state = nvg__getState(context); 6776 float[4] totalBounds = void; 6777 NVGsegment* segments = null; 6778 const(NVGsegment)* seg = null; 6779 NVGpickSubPath *curpsp; 6780 6781 pp = nvg__allocPickPath(ps); 6782 if (pp is null) return null; 6783 6784 pp.id = id; 6785 6786 bool hasPoints = false; 6787 6788 void closeIt () { 6789 if (psp is null || !hasPoints) return; 6790 if (ps.points[(ps.npoints-1)*2] != start.ptr[0] || ps.points[(ps.npoints-1)*2+1] != start.ptr[1]) { 6791 firstPoint = nvg__pickSceneAddPoints(ps, start.ptr, 1); 6792 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, Command.LineTo, NVGSegmentFlags.Corner); 6793 } 6794 psp.closed = true; 6795 } 6796 6797 while (i < ncommands) { 6798 int cmd = cast(int)commands[i++]; 6799 switch (cmd) { 6800 case Command.MoveTo: // one coordinate pair 6801 const(float)* tfxy = commands+i; 6802 i += 2; 6803 6804 // new starting point 6805 start.ptr[0..2] = tfxy[0..2]; 6806 6807 // start a new path for each sub path to handle sub paths that intersect other sub paths 6808 prev = psp; 6809 psp = nvg__allocPickSubPath(ps); 6810 if (psp is null) { psp = prev; break; } 6811 psp.firstSegment = -1; 6812 psp.winding = NVGSolidity.Solid; 6813 psp.next = prev; 6814 6815 nvg__pickSceneAddPoints(ps, tfxy, 1); 6816 hasPoints = true; 6817 break; 6818 case Command.LineTo: // one coordinate pair 6819 const(float)* tfxy = commands+i; 6820 i += 2; 6821 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 1); 6822 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner); 6823 hasPoints = true; 6824 break; 6825 case Command.BezierTo: // three coordinate pairs 6826 const(float)* tfxy = commands+i; 6827 i += 3*2; 6828 6829 // Split the curve at it's dx==0 or dy==0 inflection points. 6830 // Thus: 6831 // A horizontal line only ever interects the curves once. 6832 // and 6833 // Finding the closest point on any curve converges more reliably. 6834 6835 // NOTE: We could just split on dy==0 here. 6836 6837 memcpy(&points.ptr[0], &ps.points[(ps.npoints-1)*2], float.sizeof*2); 6838 memcpy(&points.ptr[2], tfxy, float.sizeof*2*3); 6839 6840 ninflections = 0; 6841 nvg__bezierInflections(points.ptr, 1, &ninflections, inflections.ptr); 6842 nvg__bezierInflections(points.ptr, 0, &ninflections, inflections.ptr); 6843 6844 if (ninflections) { 6845 float previnfl = 0; 6846 float[8] pointsA = void, pointsB = void; 6847 6848 nvg__smallsort(inflections.ptr, ninflections); 6849 6850 for (int infl = 0; infl < ninflections; ++infl) { 6851 if (nvg__absf(inflections.ptr[infl]-previnfl) < NVGPickEPS) continue; 6852 6853 immutable float t = (inflections.ptr[infl]-previnfl)*(1.0f/(1.0f-previnfl)); 6854 6855 previnfl = inflections.ptr[infl]; 6856 6857 nvg__splitBezier(points.ptr, t, pointsA.ptr, pointsB.ptr); 6858 6859 firstPoint = nvg__pickSceneAddPoints(ps, &pointsA.ptr[2], 3); 6860 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, (infl == 0) ? NVGSegmentFlags.Corner : 0); 6861 6862 memcpy(points.ptr, pointsB.ptr, float.sizeof*8); 6863 } 6864 6865 firstPoint = nvg__pickSceneAddPoints(ps, &pointsB.ptr[2], 3); 6866 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, 0); 6867 } else { 6868 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 3); 6869 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner); 6870 } 6871 hasPoints = true; 6872 break; 6873 case Command.Close: 6874 closeIt(); 6875 break; 6876 case Command.Winding: 6877 psp.winding = cast(short)cast(int)commands[i]; 6878 //if (psp.winding == NVGSolidity.Hole) hasHoles = true; 6879 i += 1; 6880 break; 6881 default: 6882 break; 6883 } 6884 } 6885 6886 // force-close filled paths 6887 if (psp !is null && !forStroke && hasPoints && !psp.closed) closeIt(); 6888 6889 pp.flags = (forStroke ? NVGPathFlags.Stroke : NVGPathFlags.Fill); 6890 pp.subPaths = psp; 6891 pp.strokeWidth = state.strokeWidth*0.5f; 6892 pp.miterLimit = state.miterLimit; 6893 pp.lineCap = cast(short)state.lineCap; 6894 pp.lineJoin = cast(short)state.lineJoin; 6895 pp.evenOddMode = nvg__getState(context).evenOddMode; 6896 6897 nvg__initBounds(totalBounds); 6898 6899 for (curpsp = psp; curpsp; curpsp = curpsp.next) { 6900 if (forStroke) { 6901 nvg__pickSubPathAddStrokeSupports(ps, curpsp, pp.strokeWidth, pp.lineCap, pp.lineJoin, pp.miterLimit); 6902 } else { 6903 nvg__pickSubPathAddFillSupports(ps, curpsp); 6904 } 6905 6906 if (curpsp.firstSegment == -1) continue; 6907 segments = &ps.segments[curpsp.firstSegment]; 6908 nvg__initBounds(curpsp.bounds); 6909 for (int s = 0; s < curpsp.nsegments; ++s) { 6910 seg = &segments[s]; 6911 //NVG_PICK_DEBUG_BOUNDS(seg.bounds); 6912 nvg__unionBounds(curpsp.bounds, seg.bounds); 6913 } 6914 6915 nvg__unionBounds(totalBounds, curpsp.bounds); 6916 } 6917 6918 // Store the scissor rect if present. 6919 if (state.scissor.extent.ptr[0] != -1.0f) { 6920 // Use points storage to store the scissor data 6921 pp.scissor = nvg__pickSceneAddPoints(ps, null, 4); 6922 float* scissor = &ps.points[pp.scissor*2]; 6923 6924 //memcpy(scissor, state.scissor.xform.ptr, 6*float.sizeof); 6925 scissor[0..6] = state.scissor.xform.mat[]; 6926 memcpy(scissor+6, state.scissor.extent.ptr, 2*float.sizeof); 6927 6928 pp.flags |= NVGPathFlags.Scissor; 6929 } 6930 6931 memcpy(pp.bounds.ptr, totalBounds.ptr, float.sizeof*4); 6932 6933 return pp; 6934 } 6935 6936 6937 // Struct management 6938 NVGpickPath* nvg__allocPickPath (NVGpickScene* ps) { 6939 NVGpickPath* pp = ps.freePaths; 6940 if (pp !is null) { 6941 ps.freePaths = pp.next; 6942 } else { 6943 pp = cast(NVGpickPath*)malloc(NVGpickPath.sizeof); 6944 } 6945 memset(pp, 0, NVGpickPath.sizeof); 6946 return pp; 6947 } 6948 6949 // Put a pick path and any sub paths (back) to the free lists. 6950 void nvg__freePickPath (NVGpickScene* ps, NVGpickPath* pp) { 6951 // Add all sub paths to the sub path free list. 6952 // Finds the end of the path sub paths, links that to the current 6953 // sub path free list head and replaces the head ptr with the 6954 // head path sub path entry. 6955 NVGpickSubPath* psp = null; 6956 for (psp = pp.subPaths; psp !is null && psp.next !is null; psp = psp.next) {} 6957 6958 if (psp) { 6959 psp.next = ps.freeSubPaths; 6960 ps.freeSubPaths = pp.subPaths; 6961 } 6962 pp.subPaths = null; 6963 6964 // Add the path to the path freelist 6965 pp.next = ps.freePaths; 6966 ps.freePaths = pp; 6967 if (pp.next is null) ps.lastPath = pp; 6968 } 6969 6970 NVGpickSubPath* nvg__allocPickSubPath (NVGpickScene* ps) { 6971 NVGpickSubPath* psp = ps.freeSubPaths; 6972 if (psp !is null) { 6973 ps.freeSubPaths = psp.next; 6974 } else { 6975 psp = cast(NVGpickSubPath*)malloc(NVGpickSubPath.sizeof); 6976 if (psp is null) return null; 6977 } 6978 memset(psp, 0, NVGpickSubPath.sizeof); 6979 return psp; 6980 } 6981 6982 void nvg__returnPickSubPath (NVGpickScene* ps, NVGpickSubPath* psp) { 6983 psp.next = ps.freeSubPaths; 6984 ps.freeSubPaths = psp; 6985 } 6986 6987 NVGpickScene* nvg__allocPickScene () { 6988 NVGpickScene* ps = cast(NVGpickScene*)malloc(NVGpickScene.sizeof); 6989 if (ps is null) return null; 6990 memset(ps, 0, NVGpickScene.sizeof); 6991 ps.nlevels = 5; 6992 return ps; 6993 } 6994 6995 void nvg__deletePickScene (NVGpickScene* ps) { 6996 NVGpickPath* pp; 6997 NVGpickSubPath* psp; 6998 6999 // Add all paths (and thus sub paths) to the free list(s). 7000 while (ps.paths !is null) { 7001 pp = ps.paths.next; 7002 nvg__freePickPath(ps, ps.paths); 7003 ps.paths = pp; 7004 } 7005 7006 // Delete all paths 7007 while (ps.freePaths !is null) { 7008 pp = ps.freePaths; 7009 ps.freePaths = pp.next; 7010 while (pp.subPaths !is null) { 7011 psp = pp.subPaths; 7012 pp.subPaths = psp.next; 7013 free(psp); 7014 } 7015 free(pp); 7016 } 7017 7018 // Delete all sub paths 7019 while (ps.freeSubPaths !is null) { 7020 psp = ps.freeSubPaths.next; 7021 free(ps.freeSubPaths); 7022 ps.freeSubPaths = psp; 7023 } 7024 7025 ps.npoints = 0; 7026 ps.nsegments = 0; 7027 7028 if (ps.levels !is null) { 7029 free(ps.levels[0]); 7030 free(ps.levels); 7031 } 7032 7033 if (ps.picked !is null) free(ps.picked); 7034 if (ps.points !is null) free(ps.points); 7035 if (ps.segments !is null) free(ps.segments); 7036 7037 free(ps); 7038 } 7039 7040 NVGpickScene* nvg__pickSceneGet (NVGContext ctx) { 7041 if (ctx.pickScene is null) ctx.pickScene = nvg__allocPickScene(); 7042 return ctx.pickScene; 7043 } 7044 7045 7046 // Applies Casteljau's algorithm to a cubic bezier for a given parameter t 7047 // points is 4 points (8 floats) 7048 // lvl1 is 3 points (6 floats) 7049 // lvl2 is 2 points (4 floats) 7050 // lvl3 is 1 point (2 floats) 7051 void nvg__casteljau (const(float)* points, float t, float* lvl1, float* lvl2, float* lvl3) { 7052 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0; 7053 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1; 7054 7055 // Level 1 7056 lvl1[x0] = (points[x1]-points[x0])*t+points[x0]; 7057 lvl1[y0] = (points[y1]-points[y0])*t+points[y0]; 7058 7059 lvl1[x1] = (points[x2]-points[x1])*t+points[x1]; 7060 lvl1[y1] = (points[y2]-points[y1])*t+points[y1]; 7061 7062 lvl1[x2] = (points[x3]-points[x2])*t+points[x2]; 7063 lvl1[y2] = (points[y3]-points[y2])*t+points[y2]; 7064 7065 // Level 2 7066 lvl2[x0] = (lvl1[x1]-lvl1[x0])*t+lvl1[x0]; 7067 lvl2[y0] = (lvl1[y1]-lvl1[y0])*t+lvl1[y0]; 7068 7069 lvl2[x1] = (lvl1[x2]-lvl1[x1])*t+lvl1[x1]; 7070 lvl2[y1] = (lvl1[y2]-lvl1[y1])*t+lvl1[y1]; 7071 7072 // Level 3 7073 lvl3[x0] = (lvl2[x1]-lvl2[x0])*t+lvl2[x0]; 7074 lvl3[y0] = (lvl2[y1]-lvl2[y0])*t+lvl2[y0]; 7075 } 7076 7077 // Calculates a point on a bezier at point t. 7078 void nvg__bezierEval (const(float)* points, float t, ref float[2] tpoint) { 7079 immutable float omt = 1-t; 7080 immutable float omt3 = omt*omt*omt; 7081 immutable float omt2 = omt*omt; 7082 immutable float t3 = t*t*t; 7083 immutable float t2 = t*t; 7084 7085 tpoint.ptr[0] = 7086 points[0]*omt3+ 7087 points[2]*3.0f*omt2*t+ 7088 points[4]*3.0f*omt*t2+ 7089 points[6]*t3; 7090 7091 tpoint.ptr[1] = 7092 points[1]*omt3+ 7093 points[3]*3.0f*omt2*t+ 7094 points[5]*3.0f*omt*t2+ 7095 points[7]*t3; 7096 } 7097 7098 // Splits a cubic bezier curve into two parts at point t. 7099 void nvg__splitBezier (const(float)* points, float t, float* pointsA, float* pointsB) { 7100 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0; 7101 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1; 7102 7103 float[6] lvl1 = void; 7104 float[4] lvl2 = void; 7105 float[2] lvl3 = void; 7106 7107 nvg__casteljau(points, t, lvl1.ptr, lvl2.ptr, lvl3.ptr); 7108 7109 // First half 7110 pointsA[x0] = points[x0]; 7111 pointsA[y0] = points[y0]; 7112 7113 pointsA[x1] = lvl1.ptr[x0]; 7114 pointsA[y1] = lvl1.ptr[y0]; 7115 7116 pointsA[x2] = lvl2.ptr[x0]; 7117 pointsA[y2] = lvl2.ptr[y0]; 7118 7119 pointsA[x3] = lvl3.ptr[x0]; 7120 pointsA[y3] = lvl3.ptr[y0]; 7121 7122 // Second half 7123 pointsB[x0] = lvl3.ptr[x0]; 7124 pointsB[y0] = lvl3.ptr[y0]; 7125 7126 pointsB[x1] = lvl2.ptr[x1]; 7127 pointsB[y1] = lvl2.ptr[y1]; 7128 7129 pointsB[x2] = lvl1.ptr[x2]; 7130 pointsB[y2] = lvl1.ptr[y2]; 7131 7132 pointsB[x3] = points[x3]; 7133 pointsB[y3] = points[y3]; 7134 } 7135 7136 // Calculates the inflection points in coordinate coord (X = 0, Y = 1) of a cubic bezier. 7137 // Appends any found inflection points to the array inflections and increments *ninflections. 7138 // So finds the parameters where dx/dt or dy/dt is 0 7139 void nvg__bezierInflections (const(float)* points, int coord, int* ninflections, float* inflections) { 7140 immutable float v0 = points[0*2+coord], v1 = points[1*2+coord], v2 = points[2*2+coord], v3 = points[3*2+coord]; 7141 float[2] t = void; 7142 int nvalid = *ninflections; 7143 7144 immutable float a = 3.0f*( -v0+3.0f*v1-3.0f*v2+v3 ); 7145 immutable float b = 6.0f*( v0-2.0f*v1+v2 ); 7146 immutable float c = 3.0f*( v1-v0 ); 7147 7148 float d = b*b-4.0f*a*c; 7149 if (nvg__absf(d-0.0f) < NVGPickEPS) { 7150 // Zero or one root 7151 t.ptr[0] = -b/2.0f*a; 7152 if (t.ptr[0] > NVGPickEPS && t.ptr[0] < (1.0f-NVGPickEPS)) { 7153 inflections[nvalid] = t.ptr[0]; 7154 ++nvalid; 7155 } 7156 } else if (d > NVGPickEPS) { 7157 // zero, one or two roots 7158 d = nvg__sqrtf(d); 7159 7160 t.ptr[0] = (-b+d)/(2.0f*a); 7161 t.ptr[1] = (-b-d)/(2.0f*a); 7162 7163 for (int i = 0; i < 2; ++i) { 7164 if (t.ptr[i] > NVGPickEPS && t.ptr[i] < (1.0f-NVGPickEPS)) { 7165 inflections[nvalid] = t.ptr[i]; 7166 ++nvalid; 7167 } 7168 } 7169 } else { 7170 // zero roots 7171 } 7172 7173 *ninflections = nvalid; 7174 } 7175 7176 // Sort a small number of floats in ascending order (0 < n < 6) 7177 void nvg__smallsort (float* values, int n) { 7178 bool bSwapped = true; 7179 for (int j = 0; j < n-1 && bSwapped; ++j) { 7180 bSwapped = false; 7181 for (int i = 0; i < n-1; ++i) { 7182 if (values[i] > values[i+1]) { 7183 auto tmp = values[i]; 7184 values[i] = values[i+1]; 7185 values[i+1] = tmp; 7186 } 7187 } 7188 } 7189 } 7190 7191 // Calculates the bounding rect of a given cubic bezier curve. 7192 void nvg__bezierBounds (const(float)* points, ref float[4] bounds) { 7193 float[4] inflections = void; 7194 int ninflections = 0; 7195 float[2] tpoint = void; 7196 7197 nvg__initBounds(bounds); 7198 7199 // Include start and end points in bounds 7200 nvg__expandBounds(bounds, &points[0], 1); 7201 nvg__expandBounds(bounds, &points[6], 1); 7202 7203 // Calculate dx==0 and dy==0 inflection points and add them to the bounds 7204 7205 nvg__bezierInflections(points, 0, &ninflections, inflections.ptr); 7206 nvg__bezierInflections(points, 1, &ninflections, inflections.ptr); 7207 7208 foreach (immutable int i; 0..ninflections) { 7209 nvg__bezierEval(points, inflections[i], tpoint); 7210 nvg__expandBounds(bounds, tpoint.ptr, 1); 7211 } 7212 } 7213 7214 // Checks to see if a line originating from x,y along the +ve x axis 7215 // intersects the given line (points[0],points[1]) -> (points[2], points[3]). 7216 // Returns `true` on intersection. 7217 // Horizontal lines are never hit. 7218 bool nvg__intersectLine (const(float)* points, float x, float y) { 7219 immutable float x1 = points[0]; 7220 immutable float y1 = points[1]; 7221 immutable float x2 = points[2]; 7222 immutable float y2 = points[3]; 7223 immutable float d = y2-y1; 7224 if (d > NVGPickEPS || d < -NVGPickEPS) { 7225 immutable float s = (x2-x1)/d; 7226 immutable float lineX = x1+(y-y1)*s; 7227 return (lineX > x); 7228 } else { 7229 return false; 7230 } 7231 } 7232 7233 // Checks to see if a line originating from x,y along the +ve x axis intersects the given bezier. 7234 // It is assumed that the line originates from within the bounding box of 7235 // the bezier and that the curve has no dy=0 inflection points. 7236 // Returns the number of intersections found (which is either 1 or 0). 7237 int nvg__intersectBezier (const(float)* points, float x, float y) { 7238 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0]; 7239 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1]; 7240 7241 if (y0 == y1 && y1 == y2 && y2 == y3) return 0; 7242 7243 // Initial t guess 7244 float t = void; 7245 if (y3 != y0) t = (y-y0)/(y3-y0); 7246 else if (x3 != x0) t = (x-x0)/(x3-x0); 7247 else t = 0.5f; 7248 7249 // A few Newton iterations 7250 for (int iter = 0; iter < 6; ++iter) { 7251 immutable float omt = 1-t; 7252 immutable float omt2 = omt*omt; 7253 immutable float t2 = t*t; 7254 immutable float omt3 = omt2*omt; 7255 immutable float t3 = t2*t; 7256 7257 immutable float ty = y0*omt3 + 7258 y1*3.0f*omt2*t + 7259 y2*3.0f*omt*t2 + 7260 y3*t3; 7261 7262 // Newton iteration 7263 immutable float dty = 3.0f*omt2*(y1-y0) + 7264 6.0f*omt*t*(y2-y1) + 7265 3.0f*t2*(y3-y2); 7266 7267 // dty will never == 0 since: 7268 // Either omt, omt2 are zero OR t2 is zero 7269 // y0 != y1 != y2 != y3 (checked above) 7270 t = t-(ty-y)/dty; 7271 } 7272 7273 { 7274 immutable float omt = 1-t; 7275 immutable float omt2 = omt*omt; 7276 immutable float t2 = t*t; 7277 immutable float omt3 = omt2*omt; 7278 immutable float t3 = t2*t; 7279 7280 immutable float tx = 7281 x0*omt3+ 7282 x1*3.0f*omt2*t+ 7283 x2*3.0f*omt*t2+ 7284 x3*t3; 7285 7286 return (tx > x ? 1 : 0); 7287 } 7288 } 7289 7290 // Finds the closest point on a line to a given point 7291 void nvg__closestLine (const(float)* points, float x, float y, float* closest, float* ot) { 7292 immutable float x1 = points[0]; 7293 immutable float y1 = points[1]; 7294 immutable float x2 = points[2]; 7295 immutable float y2 = points[3]; 7296 immutable float pqx = x2-x1; 7297 immutable float pqz = y2-y1; 7298 immutable float dx = x-x1; 7299 immutable float dz = y-y1; 7300 immutable float d = pqx*pqx+pqz*pqz; 7301 float t = pqx*dx+pqz*dz; 7302 if (d > 0) t /= d; 7303 if (t < 0) t = 0; else if (t > 1) t = 1; 7304 closest[0] = x1+t*pqx; 7305 closest[1] = y1+t*pqz; 7306 *ot = t; 7307 } 7308 7309 // Finds the closest point on a curve for a given point (x,y). 7310 // Assumes that the curve has no dx==0 or dy==0 inflection points. 7311 void nvg__closestBezier (const(float)* points, float x, float y, float* closest, float *ot) { 7312 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0]; 7313 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1]; 7314 7315 // This assumes that the curve has no dy=0 inflection points. 7316 7317 // Initial t guess 7318 float t = 0.5f; 7319 7320 // A few Newton iterations 7321 for (int iter = 0; iter < 6; ++iter) { 7322 immutable float omt = 1-t; 7323 immutable float omt2 = omt*omt; 7324 immutable float t2 = t*t; 7325 immutable float omt3 = omt2*omt; 7326 immutable float t3 = t2*t; 7327 7328 immutable float ty = 7329 y0*omt3+ 7330 y1*3.0f*omt2*t+ 7331 y2*3.0f*omt*t2+ 7332 y3*t3; 7333 7334 immutable float tx = 7335 x0*omt3+ 7336 x1*3.0f*omt2*t+ 7337 x2*3.0f*omt*t2+ 7338 x3*t3; 7339 7340 // Newton iteration 7341 immutable float dty = 7342 3.0f*omt2*(y1-y0)+ 7343 6.0f*omt*t*(y2-y1)+ 7344 3.0f*t2*(y3-y2); 7345 7346 immutable float ddty = 7347 6.0f*omt*(y2-2.0f*y1+y0)+ 7348 6.0f*t*(y3-2.0f*y2+y1); 7349 7350 immutable float dtx = 7351 3.0f*omt2*(x1-x0)+ 7352 6.0f*omt*t*(x2-x1)+ 7353 3.0f*t2*(x3-x2); 7354 7355 immutable float ddtx = 7356 6.0f*omt*(x2-2.0f*x1+x0)+ 7357 6.0f*t*(x3-2.0f*x2+x1); 7358 7359 immutable float errorx = tx-x; 7360 immutable float errory = ty-y; 7361 7362 immutable float n = errorx*dtx+errory*dty; 7363 if (n == 0) break; 7364 7365 immutable float d = dtx*dtx+dty*dty+errorx*ddtx+errory*ddty; 7366 if (d != 0) t = t-n/d; else break; 7367 } 7368 7369 t = nvg__max(0, nvg__min(1.0, t)); 7370 *ot = t; 7371 { 7372 immutable float omt = 1-t; 7373 immutable float omt2 = omt*omt; 7374 immutable float t2 = t*t; 7375 immutable float omt3 = omt2*omt; 7376 immutable float t3 = t2*t; 7377 7378 immutable float ty = 7379 y0*omt3+ 7380 y1*3.0f*omt2*t+ 7381 y2*3.0f*omt*t2+ 7382 y3*t3; 7383 7384 immutable float tx = 7385 x0*omt3+ 7386 x1*3.0f*omt2*t+ 7387 x2*3.0f*omt*t2+ 7388 x3*t3; 7389 7390 closest[0] = tx; 7391 closest[1] = ty; 7392 } 7393 } 7394 7395 // Returns: 7396 // 1 If (x,y) is contained by the stroke of the path 7397 // 0 If (x,y) is not contained by the path. 7398 int nvg__pickSubPathStroke (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, float strokeWidth, int lineCap, int lineJoin) { 7399 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0; 7400 if (psp.firstSegment == -1) return 0; 7401 7402 float[2] closest = void; 7403 float[2] d = void; 7404 float t = void; 7405 7406 // trace a line from x,y out along the positive x axis and count the number of intersections 7407 int nsegments = psp.nsegments; 7408 const(NVGsegment)* seg = ps.segments+psp.firstSegment; 7409 const(NVGsegment)* prevseg = (psp.closed ? &ps.segments[psp.firstSegment+nsegments-1] : null); 7410 immutable float strokeWidthSqd = strokeWidth*strokeWidth; 7411 7412 for (int s = 0; s < nsegments; ++s, prevseg = seg, ++seg) { 7413 if (nvg__pointInBounds(x, y, seg.bounds)) { 7414 // Line potentially hits stroke. 7415 switch (seg.type) { 7416 case Command.LineTo: 7417 nvg__closestLine(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t); 7418 break; 7419 case Command.BezierTo: 7420 nvg__closestBezier(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t); 7421 break; 7422 default: 7423 continue; 7424 } 7425 7426 d.ptr[0] = x-closest.ptr[0]; 7427 d.ptr[1] = y-closest.ptr[1]; 7428 7429 if ((t >= NVGPickEPS && t <= 1.0f-NVGPickEPS) || 7430 (seg.flags&(NVGSegmentFlags.Corner|NVGSegmentFlags.Cap|NVGSegmentFlags.Endcap)) == 0 || 7431 (lineJoin == NVGLineCap.Round)) 7432 { 7433 // Closest point is in the middle of the line/curve, at a rounded join/cap 7434 // or at a smooth join 7435 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7436 if (distSqd < strokeWidthSqd) return 1; 7437 } else if ((t > 1.0f-NVGPickEPS && (seg.flags&NVGSegmentFlags.Endcap)) || 7438 (t < NVGPickEPS && (seg.flags&NVGSegmentFlags.Cap))) { 7439 switch (lineCap) { 7440 case NVGLineCap.Butt: 7441 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7442 immutable float dirD = (t < NVGPickEPS ? 7443 -(d.ptr[0]*seg.startDir.ptr[0]+d.ptr[1]*seg.startDir.ptr[1]) : 7444 d.ptr[0]*seg.endDir.ptr[0]+d.ptr[1]*seg.endDir.ptr[1]); 7445 if (dirD < -NVGPickEPS && distSqd < strokeWidthSqd) return 1; 7446 break; 7447 case NVGLineCap.Square: 7448 if (nvg__absf(d.ptr[0]) < strokeWidth && nvg__absf(d.ptr[1]) < strokeWidth) return 1; 7449 break; 7450 case NVGLineCap.Round: 7451 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7452 if (distSqd < strokeWidthSqd) return 1; 7453 break; 7454 default: 7455 break; 7456 } 7457 } else if (seg.flags&NVGSegmentFlags.Corner) { 7458 // Closest point is at a corner 7459 const(NVGsegment)* seg0, seg1; 7460 7461 if (t < NVGPickEPS) { 7462 seg0 = prevseg; 7463 seg1 = seg; 7464 } else { 7465 seg0 = seg; 7466 seg1 = (s == nsegments-1 ? &ps.segments[psp.firstSegment] : seg+1); 7467 } 7468 7469 if (!(seg1.flags&NVGSegmentFlags.Bevel)) { 7470 immutable float prevNDist = -seg0.endDir.ptr[1]*d.ptr[0]+seg0.endDir.ptr[0]*d.ptr[1]; 7471 immutable float curNDist = seg1.startDir.ptr[1]*d.ptr[0]-seg1.startDir.ptr[0]*d.ptr[1]; 7472 if (nvg__absf(prevNDist) < strokeWidth && nvg__absf(curNDist) < strokeWidth) return 1; 7473 } else { 7474 d.ptr[0] -= -seg1.startDir.ptr[1]*strokeWidth; 7475 d.ptr[1] -= +seg1.startDir.ptr[0]*strokeWidth; 7476 if (seg1.miterDir.ptr[0]*d.ptr[0]+seg1.miterDir.ptr[1]*d.ptr[1] < 0) return 1; 7477 } 7478 } 7479 } 7480 } 7481 7482 return 0; 7483 } 7484 7485 // Returns: 7486 // 1 If (x,y) is contained by the path and the path is solid. 7487 // -1 If (x,y) is contained by the path and the path is a hole. 7488 // 0 If (x,y) is not contained by the path. 7489 int nvg__pickSubPath (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, bool evenOddMode) { 7490 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0; 7491 if (psp.firstSegment == -1) return 0; 7492 7493 const(NVGsegment)* seg = &ps.segments[psp.firstSegment]; 7494 int nsegments = psp.nsegments; 7495 int nintersections = 0; 7496 7497 // trace a line from x,y out along the positive x axis and count the number of intersections 7498 for (int s = 0; s < nsegments; ++s, ++seg) { 7499 if ((seg.bounds.ptr[1]-NVGPickEPS) < y && 7500 (seg.bounds.ptr[3]-NVGPickEPS) > y && 7501 seg.bounds.ptr[2] > x) 7502 { 7503 // Line hits the box. 7504 switch (seg.type) { 7505 case Command.LineTo: 7506 if (seg.bounds.ptr[0] > x) { 7507 // line originates outside the box 7508 ++nintersections; 7509 } else { 7510 // line originates inside the box 7511 nintersections += nvg__intersectLine(&ps.points[seg.firstPoint*2], x, y); 7512 } 7513 break; 7514 case Command.BezierTo: 7515 if (seg.bounds.ptr[0] > x) { 7516 // line originates outside the box 7517 ++nintersections; 7518 } else { 7519 // line originates inside the box 7520 nintersections += nvg__intersectBezier(&ps.points[seg.firstPoint*2], x, y); 7521 } 7522 break; 7523 default: 7524 break; 7525 } 7526 } 7527 } 7528 7529 if (evenOddMode) { 7530 return nintersections; 7531 } else { 7532 return (nintersections&1 ? (psp.winding == NVGSolidity.Solid ? 1 : -1) : 0); 7533 } 7534 } 7535 7536 bool nvg__pickPath (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) { 7537 int pickCount = 0; 7538 const(NVGpickSubPath)* psp = pp.subPaths; 7539 while (psp !is null) { 7540 pickCount += nvg__pickSubPath(ps, psp, x, y, pp.evenOddMode); 7541 psp = psp.next; 7542 } 7543 return ((pp.evenOddMode ? pickCount&1 : pickCount) != 0); 7544 } 7545 7546 bool nvg__pickPathStroke (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) { 7547 const(NVGpickSubPath)* psp = pp.subPaths; 7548 while (psp !is null) { 7549 if (nvg__pickSubPathStroke(ps, psp, x, y, pp.strokeWidth, pp.lineCap, pp.lineJoin)) return true; 7550 psp = psp.next; 7551 } 7552 return false; 7553 } 7554 7555 bool nvg__pickPathTestBounds (NVGContext ctx, const NVGpickScene* ps, const NVGpickPath* pp, float x, float y) { 7556 if (nvg__pointInBounds(x, y, pp.bounds)) { 7557 //{ import core.stdc.stdio; printf(" (0): in bounds!\n"); } 7558 if (pp.flags&NVGPathFlags.Scissor) { 7559 const(float)* scissor = &ps.points[pp.scissor*2]; 7560 // untransform scissor translation 7561 float stx = void, sty = void; 7562 ctx.gpuUntransformPoint(&stx, &sty, scissor[4], scissor[5]); 7563 immutable float rx = x-stx; 7564 immutable float ry = y-sty; 7565 //{ 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]); } 7566 if (nvg__absf((scissor[0]*rx)+(scissor[1]*ry)) > scissor[6] || 7567 nvg__absf((scissor[2]*rx)+(scissor[3]*ry)) > scissor[7]) 7568 { 7569 //{ import core.stdc.stdio; printf(" (1): scissor reject!\n"); } 7570 return false; 7571 } 7572 } 7573 return true; 7574 } 7575 return false; 7576 } 7577 7578 int nvg__countBitsUsed (uint v) pure { 7579 pragma(inline, true); 7580 import core.bitop : bsr; 7581 return (v != 0 ? bsr(v)+1 : 0); 7582 } 7583 7584 void nvg__pickSceneInsert (NVGpickScene* ps, NVGpickPath* pp) { 7585 if (ps is null || pp is null) return; 7586 7587 int[4] cellbounds; 7588 int base = ps.nlevels-1; 7589 int level; 7590 int levelwidth; 7591 int levelshift; 7592 int levelx; 7593 int levely; 7594 NVGpickPath** cell = null; 7595 7596 // Bit tricks for inserting into an implicit quadtree. 7597 7598 // Calc bounds of path in cells at the lowest level 7599 cellbounds.ptr[0] = cast(int)(pp.bounds.ptr[0]/ps.xdim); 7600 cellbounds.ptr[1] = cast(int)(pp.bounds.ptr[1]/ps.ydim); 7601 cellbounds.ptr[2] = cast(int)(pp.bounds.ptr[2]/ps.xdim); 7602 cellbounds.ptr[3] = cast(int)(pp.bounds.ptr[3]/ps.ydim); 7603 7604 // Find which bits differ between the min/max x/y coords 7605 cellbounds.ptr[0] ^= cellbounds.ptr[2]; 7606 cellbounds.ptr[1] ^= cellbounds.ptr[3]; 7607 7608 // Use the number of bits used (countBitsUsed(x) == sizeof(int) * 8 - clz(x); 7609 // to calculate the level to insert at (the level at which the bounds fit in a single cell) 7610 level = nvg__min(base-nvg__countBitsUsed(cellbounds.ptr[0]), base-nvg__countBitsUsed(cellbounds.ptr[1])); 7611 if (level < 0) level = 0; 7612 //{ 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]); } 7613 //level = 0; 7614 7615 // Find the correct cell in the chosen level, clamping to the edges. 7616 levelwidth = 1<<level; 7617 levelshift = (ps.nlevels-level)-1; 7618 levelx = nvg__clamp(cellbounds.ptr[2]>>levelshift, 0, levelwidth-1); 7619 levely = nvg__clamp(cellbounds.ptr[3]>>levelshift, 0, levelwidth-1); 7620 7621 // Insert the path into the linked list at that cell. 7622 cell = &ps.levels[level][levely*levelwidth+levelx]; 7623 7624 pp.cellnext = *cell; 7625 *cell = pp; 7626 7627 if (ps.paths is null) ps.lastPath = pp; 7628 pp.next = ps.paths; 7629 ps.paths = pp; 7630 7631 // Store the order (depth) of the path for picking ops. 7632 pp.order = cast(short)ps.npaths; 7633 ++ps.npaths; 7634 } 7635 7636 void nvg__pickBeginFrame (NVGContext ctx, int width, int height) { 7637 NVGpickScene* ps = nvg__pickSceneGet(ctx); 7638 7639 //NVG_PICK_DEBUG_NEWFRAME(); 7640 7641 // Return all paths & sub paths from last frame to the free list 7642 while (ps.paths !is null) { 7643 NVGpickPath* pp = ps.paths.next; 7644 nvg__freePickPath(ps, ps.paths); 7645 ps.paths = pp; 7646 } 7647 7648 ps.paths = null; 7649 ps.npaths = 0; 7650 7651 // Store the screen metrics for the quadtree 7652 ps.width = width; 7653 ps.height = height; 7654 7655 immutable float lowestSubDiv = cast(float)(1<<(ps.nlevels-1)); 7656 ps.xdim = cast(float)width/lowestSubDiv; 7657 ps.ydim = cast(float)height/lowestSubDiv; 7658 7659 // Allocate the quadtree if required. 7660 if (ps.levels is null) { 7661 int ncells = 1; 7662 7663 ps.levels = cast(NVGpickPath***)malloc((NVGpickPath**).sizeof*ps.nlevels); 7664 for (int l = 0; l < ps.nlevels; ++l) { 7665 int leveldim = 1<<l; 7666 ncells += leveldim*leveldim; 7667 } 7668 7669 ps.levels[0] = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ncells); 7670 7671 int cell = 1; 7672 for (int l = 1; l < ps.nlevels; ++l) { 7673 ps.levels[l] = &ps.levels[0][cell]; 7674 int leveldim = 1<<l; 7675 cell += leveldim*leveldim; 7676 } 7677 7678 ps.ncells = ncells; 7679 } 7680 memset(ps.levels[0], 0, ps.ncells*(NVGpickPath*).sizeof); 7681 7682 // Allocate temporary storage for nvgHitTestAll results if required. 7683 if (ps.picked is null) { 7684 ps.cpicked = 16; 7685 ps.picked = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ps.cpicked); 7686 } 7687 7688 ps.npoints = 0; 7689 ps.nsegments = 0; 7690 } 7691 } // nothrow @trusted @nogc 7692 7693 7694 /// Return outline of the current path. Returned outline is not flattened. 7695 /// Group: paths 7696 public NVGPathOutline getCurrPathOutline (NVGContext ctx) nothrow @trusted @nogc { 7697 if (ctx is null || !ctx.contextAlive || ctx.ncommands == 0) return NVGPathOutline.init; 7698 7699 auto res = NVGPathOutline.createNew(); 7700 7701 const(float)[] acommands = ctx.commands[0..ctx.ncommands]; 7702 int ncommands = cast(int)acommands.length; 7703 const(float)* commands = acommands.ptr; 7704 7705 float cx = 0, cy = 0; 7706 float[2] start = void; 7707 float[4] totalBounds = [float.max, float.max, -float.max, -float.max]; 7708 float[8] bcp = void; // bezier curve points; used to calculate bounds 7709 7710 void addToBounds (in float x, in float y) nothrow @trusted @nogc { 7711 totalBounds.ptr[0] = nvg__min(totalBounds.ptr[0], x); 7712 totalBounds.ptr[1] = nvg__min(totalBounds.ptr[1], y); 7713 totalBounds.ptr[2] = nvg__max(totalBounds.ptr[2], x); 7714 totalBounds.ptr[3] = nvg__max(totalBounds.ptr[3], y); 7715 } 7716 7717 bool hasPoints = false; 7718 7719 void closeIt () nothrow @trusted @nogc { 7720 if (!hasPoints) return; 7721 if (cx != start.ptr[0] || cy != start.ptr[1]) { 7722 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo); 7723 res.ds.putArgs(start[]); 7724 cx = start.ptr[0]; 7725 cy = start.ptr[1]; 7726 addToBounds(cx, cy); 7727 } 7728 } 7729 7730 int i = 0; 7731 while (i < ncommands) { 7732 int cmd = cast(int)commands[i++]; 7733 switch (cmd) { 7734 case Command.MoveTo: // one coordinate pair 7735 const(float)* tfxy = commands+i; 7736 i += 2; 7737 // add command 7738 res.ds.putCommand(NVGPathOutline.Command.Kind.MoveTo); 7739 res.ds.putArgs(tfxy[0..2]); 7740 // new starting point 7741 start.ptr[0..2] = tfxy[0..2]; 7742 cx = tfxy[0]; 7743 cy = tfxy[0]; 7744 addToBounds(cx, cy); 7745 hasPoints = true; 7746 break; 7747 case Command.LineTo: // one coordinate pair 7748 const(float)* tfxy = commands+i; 7749 i += 2; 7750 // add command 7751 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo); 7752 res.ds.putArgs(tfxy[0..2]); 7753 cx = tfxy[0]; 7754 cy = tfxy[0]; 7755 addToBounds(cx, cy); 7756 hasPoints = true; 7757 break; 7758 case Command.BezierTo: // three coordinate pairs 7759 const(float)* tfxy = commands+i; 7760 i += 3*2; 7761 // add command 7762 res.ds.putCommand(NVGPathOutline.Command.Kind.BezierTo); 7763 res.ds.putArgs(tfxy[0..6]); 7764 // bounds 7765 bcp.ptr[0] = cx; 7766 bcp.ptr[1] = cy; 7767 bcp.ptr[2..8] = tfxy[0..6]; 7768 nvg__bezierBounds(bcp.ptr, totalBounds); 7769 cx = tfxy[4]; 7770 cy = tfxy[5]; 7771 hasPoints = true; 7772 break; 7773 case Command.Close: 7774 closeIt(); 7775 hasPoints = false; 7776 break; 7777 case Command.Winding: 7778 //psp.winding = cast(short)cast(int)commands[i]; 7779 i += 1; 7780 break; 7781 default: 7782 break; 7783 } 7784 } 7785 7786 res.ds.bounds[] = totalBounds[]; 7787 return res; 7788 } 7789 7790 7791 // ////////////////////////////////////////////////////////////////////////// // 7792 // Text 7793 7794 /** Creates font by loading it from the disk from specified file name. 7795 * Returns handle to the font or FONS_INVALID (aka -1) on error. 7796 * Use "fontname:noaa" as [name] to turn off antialiasing (if font driver supports that). 7797 * 7798 * On POSIX systems it is possible to use fontconfig font names too. 7799 * `:noaa` in font path is still allowed, but it must be the last option. 7800 * 7801 * Group: text_api 7802 */ 7803 public int createFont (NVGContext ctx, const(char)[] name, const(char)[] path) nothrow @trusted { 7804 return ctx.fs.addFont(name, path, ctx.params.fontAA); 7805 } 7806 7807 /** Creates font by loading it from the specified memory chunk. 7808 * Returns handle to the font or FONS_INVALID (aka -1) on error. 7809 * Won't free data on error. 7810 * 7811 * Group: text_api 7812 */ 7813 public int createFontMem (NVGContext ctx, const(char)[] name, ubyte* data, int ndata, bool freeData) nothrow @trusted @nogc { 7814 return ctx.fs.addFontMem(name, data, ndata, freeData, ctx.params.fontAA); 7815 } 7816 7817 /// Add fonts from another context. 7818 /// This is more effective than reloading fonts, 'cause font data will be shared. 7819 /// Group: text_api 7820 public void addFontsFrom (NVGContext ctx, NVGContext source) nothrow @trusted @nogc { 7821 if (ctx is null || source is null) return; 7822 ctx.fs.addFontsFrom(source.fs); 7823 } 7824 7825 /// Finds a loaded font of specified name, and returns handle to it, or FONS_INVALID (aka -1) if the font is not found. 7826 /// Group: text_api 7827 public int findFont (NVGContext ctx, const(char)[] name) nothrow @trusted @nogc { 7828 pragma(inline, true); 7829 return (name.length == 0 ? FONS_INVALID : ctx.fs.getFontByName(name)); 7830 } 7831 7832 /// Sets the font size of current text style. 7833 /// Group: text_api 7834 public void fontSize (NVGContext ctx, float size) nothrow @trusted @nogc { 7835 pragma(inline, true); 7836 nvg__getState(ctx).fontSize = size; 7837 } 7838 7839 /// Gets the font size of current text style. 7840 /// Group: text_api 7841 public float fontSize (NVGContext ctx) nothrow @trusted @nogc { 7842 pragma(inline, true); 7843 return nvg__getState(ctx).fontSize; 7844 } 7845 7846 /// Sets the blur of current text style. 7847 /// Group: text_api 7848 public void fontBlur (NVGContext ctx, float blur) nothrow @trusted @nogc { 7849 pragma(inline, true); 7850 nvg__getState(ctx).fontBlur = blur; 7851 } 7852 7853 /// Gets the blur of current text style. 7854 /// Group: text_api 7855 public float fontBlur (NVGContext ctx) nothrow @trusted @nogc { 7856 pragma(inline, true); 7857 return nvg__getState(ctx).fontBlur; 7858 } 7859 7860 /// Sets the letter spacing of current text style. 7861 /// Group: text_api 7862 public void textLetterSpacing (NVGContext ctx, float spacing) nothrow @trusted @nogc { 7863 pragma(inline, true); 7864 nvg__getState(ctx).letterSpacing = spacing; 7865 } 7866 7867 /// Gets the letter spacing of current text style. 7868 /// Group: text_api 7869 public float textLetterSpacing (NVGContext ctx) nothrow @trusted @nogc { 7870 pragma(inline, true); 7871 return nvg__getState(ctx).letterSpacing; 7872 } 7873 7874 /// Sets the proportional line height of current text style. The line height is specified as multiple of font size. 7875 /// Group: text_api 7876 public void textLineHeight (NVGContext ctx, float lineHeight) nothrow @trusted @nogc { 7877 pragma(inline, true); 7878 nvg__getState(ctx).lineHeight = lineHeight; 7879 } 7880 7881 /// Gets the proportional line height of current text style. The line height is specified as multiple of font size. 7882 /// Group: text_api 7883 public float textLineHeight (NVGContext ctx) nothrow @trusted @nogc { 7884 pragma(inline, true); 7885 return nvg__getState(ctx).lineHeight; 7886 } 7887 7888 /// Sets the text align of current text style, see [NVGTextAlign] for options. 7889 /// Group: text_api 7890 public void textAlign (NVGContext ctx, NVGTextAlign talign) nothrow @trusted @nogc { 7891 pragma(inline, true); 7892 nvg__getState(ctx).textAlign = talign; 7893 } 7894 7895 /// Ditto. 7896 public void textAlign (NVGContext ctx, NVGTextAlign.H h) nothrow @trusted @nogc { 7897 pragma(inline, true); 7898 nvg__getState(ctx).textAlign.horizontal = h; 7899 } 7900 7901 /// Ditto. 7902 public void textAlign (NVGContext ctx, NVGTextAlign.V v) nothrow @trusted @nogc { 7903 pragma(inline, true); 7904 nvg__getState(ctx).textAlign.vertical = v; 7905 } 7906 7907 /// Ditto. 7908 public void textAlign (NVGContext ctx, NVGTextAlign.H h, NVGTextAlign.V v) nothrow @trusted @nogc { 7909 pragma(inline, true); 7910 nvg__getState(ctx).textAlign.reset(h, v); 7911 } 7912 7913 /// Ditto. 7914 public void textAlign (NVGContext ctx, NVGTextAlign.V v, NVGTextAlign.H h) nothrow @trusted @nogc { 7915 pragma(inline, true); 7916 nvg__getState(ctx).textAlign.reset(h, v); 7917 } 7918 7919 /// Gets the text align of current text style, see [NVGTextAlign] for options. 7920 /// Group: text_api 7921 public NVGTextAlign textAlign (NVGContext ctx) nothrow @trusted @nogc { 7922 pragma(inline, true); 7923 return nvg__getState(ctx).textAlign; 7924 } 7925 7926 /// Sets the font face based on specified id of current text style. 7927 /// Group: text_api 7928 public void fontFaceId (NVGContext ctx, int font) nothrow @trusted @nogc { 7929 pragma(inline, true); 7930 nvg__getState(ctx).fontId = font; 7931 } 7932 7933 /// Gets the font face based on specified id of current text style. 7934 /// Group: text_api 7935 public int fontFaceId (NVGContext ctx) nothrow @trusted @nogc { 7936 pragma(inline, true); 7937 return nvg__getState(ctx).fontId; 7938 } 7939 7940 /** Sets the font face based on specified name of current text style. 7941 * 7942 * The underlying implementation is using O(1) data structure to lookup 7943 * font names, so you probably should use this function instead of [fontFaceId] 7944 * to make your code more robust and less error-prone. 7945 * 7946 * Group: text_api 7947 */ 7948 public void fontFace (NVGContext ctx, const(char)[] font) nothrow @trusted @nogc { 7949 pragma(inline, true); 7950 nvg__getState(ctx).fontId = ctx.fs.getFontByName(font); 7951 } 7952 7953 static if (is(typeof(&fons__nvg__toPath))) { 7954 public enum NanoVegaHasCharToPath = true; /// 7955 } else { 7956 public enum NanoVegaHasCharToPath = false; /// 7957 } 7958 7959 /// Adds glyph outlines to the current path. Vertical 0 is baseline. 7960 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead. 7961 /// Returns `false` if there is no such glyph, or current font is not scalable. 7962 /// Group: text_api 7963 public bool charToPath (NVGContext ctx, dchar dch, float[] bounds=null) nothrow @trusted @nogc { 7964 NVGstate* state = nvg__getState(ctx); 7965 ctx.fs.fontId = state.fontId; 7966 return ctx.fs.toPath(ctx, dch, bounds); 7967 } 7968 7969 static if (is(typeof(&fons__nvg__bounds))) { 7970 public enum NanoVegaHasCharPathBounds = true; /// 7971 } else { 7972 public enum NanoVegaHasCharPathBounds = false; /// 7973 } 7974 7975 /// Returns bounds of the glyph outlines. Vertical 0 is baseline. 7976 /// The glyph is not scaled in any way. 7977 /// Returns `false` if there is no such glyph, or current font is not scalable. 7978 /// Group: text_api 7979 public bool charPathBounds (NVGContext ctx, dchar dch, float[] bounds) nothrow @trusted @nogc { 7980 NVGstate* state = nvg__getState(ctx); 7981 ctx.fs.fontId = state.fontId; 7982 return ctx.fs.getPathBounds(dch, bounds); 7983 } 7984 7985 /** [charOutline] will return [NVGPathOutline]. 7986 7987 some usage samples: 7988 7989 --- 7990 float[4] bounds = void; 7991 7992 nvg.scale(0.5, 0.5); 7993 nvg.translate(500, 800); 7994 nvg.evenOddFill; 7995 7996 nvg.newPath(); 7997 nvg.charToPath('&', bounds[]); 7998 conwriteln(bounds[]); 7999 nvg.fillPaint(nvg.linearGradient(0, 0, 600, 600, NVGColor("#f70"), NVGColor("#ff0"))); 8000 nvg.strokeColor(NVGColor("#0f0")); 8001 nvg.strokeWidth = 3; 8002 nvg.fill(); 8003 nvg.stroke(); 8004 // glyph bounds 8005 nvg.newPath(); 8006 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); 8007 nvg.strokeColor(NVGColor("#00f")); 8008 nvg.stroke(); 8009 8010 nvg.newPath(); 8011 nvg.charToPath('g', bounds[]); 8012 conwriteln(bounds[]); 8013 nvg.fill(); 8014 nvg.strokeColor(NVGColor("#0f0")); 8015 nvg.stroke(); 8016 // glyph bounds 8017 nvg.newPath(); 8018 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); 8019 nvg.strokeColor(NVGColor("#00f")); 8020 nvg.stroke(); 8021 8022 nvg.newPath(); 8023 nvg.moveTo(0, 0); 8024 nvg.lineTo(600, 0); 8025 nvg.strokeColor(NVGColor("#0ff")); 8026 nvg.stroke(); 8027 8028 if (auto ol = nvg.charOutline('Q')) { 8029 scope(exit) ol.kill(); 8030 nvg.newPath(); 8031 conwriteln("==== length: ", ol.length, " ===="); 8032 foreach (const ref cmd; ol.commands) { 8033 //conwriteln(" ", cmd.code, ": ", cmd.args[]); 8034 assert(cmd.valid); 8035 final switch (cmd.code) { 8036 case cmd.Kind.MoveTo: nvg.moveTo(cmd.args[0], cmd.args[1]); break; 8037 case cmd.Kind.LineTo: nvg.lineTo(cmd.args[0], cmd.args[1]); break; 8038 case cmd.Kind.QuadTo: nvg.quadTo(cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]); break; 8039 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; 8040 } 8041 } 8042 nvg.strokeColor(NVGColor("#f00")); 8043 nvg.stroke(); 8044 } 8045 --- 8046 8047 Group: text_api 8048 */ 8049 public struct NVGPathOutline { 8050 private nothrow @trusted @nogc: 8051 struct DataStore { 8052 uint rc; // refcount 8053 ubyte* data; 8054 uint used; 8055 uint size; 8056 uint ccount; // number of commands 8057 float[4] bounds = 0; /// outline bounds 8058 nothrow @trusted @nogc: 8059 void putBytes (const(void)[] b) { 8060 if (b.length == 0) return; 8061 if (b.length >= int.max/8) assert(0, "NanoVega: out of memory"); 8062 if (int.max/8-used < b.length) assert(0, "NanoVega: out of memory"); 8063 if (used+cast(uint)b.length > size) { 8064 import core.stdc.stdlib : realloc; 8065 uint newsz = size; 8066 while (newsz < used+cast(uint)b.length) newsz = (newsz == 0 ? 1024 : newsz < 32768 ? newsz*2 : newsz+8192); 8067 assert(used+cast(uint)b.length <= newsz); 8068 data = cast(ubyte*)realloc(data, newsz); 8069 if (data is null) assert(0, "NanoVega: out of memory"); 8070 size = newsz; 8071 } 8072 import core.stdc.string : memcpy; 8073 memcpy(data+used, b.ptr, b.length); 8074 used += cast(uint)b.length; 8075 } 8076 void putCommand (ubyte cmd) { pragma(inline, true); ++ccount; putBytes((&cmd)[0..1]); } 8077 void putArgs (const(float)[] f...) { pragma(inline, true); putBytes(f[]); } 8078 } 8079 8080 static void incRef (DataStore* ds) { 8081 pragma(inline, true); 8082 if (ds !is null) { 8083 ++ds.rc; 8084 //{ import core.stdc.stdio; printf("ods(%p): incref: newrc=%u\n", ds, ds.rc); } 8085 } 8086 } 8087 8088 static void decRef (DataStore* ds) { 8089 version(aliced) pragma(inline, true); 8090 if (ds !is null) { 8091 //{ import core.stdc.stdio; printf("ods(%p): decref: newrc=%u\n", ds, ds.rc-1); } 8092 if (--ds.rc == 0) { 8093 import core.stdc.stdlib : free; 8094 import core.stdc.string : memset; 8095 if (ds.data !is null) free(ds.data); 8096 memset(ds, 0, DataStore.sizeof); // just in case 8097 free(ds); 8098 //{ import core.stdc.stdio; printf(" ods(%p): killed.\n"); } 8099 } 8100 } 8101 } 8102 8103 private: 8104 static NVGPathOutline createNew () { 8105 import core.stdc.stdlib : malloc; 8106 import core.stdc.string : memset; 8107 auto ds = cast(DataStore*)malloc(DataStore.sizeof); 8108 if (ds is null) assert(0, "NanoVega: out of memory"); 8109 memset(ds, 0, DataStore.sizeof); 8110 ds.rc = 1; 8111 NVGPathOutline res; 8112 res.dsaddr = cast(usize)ds; 8113 return res; 8114 } 8115 8116 private: 8117 usize dsaddr; // fool GC 8118 8119 @property inout(DataStore)* ds () inout pure { pragma(inline, true); return cast(DataStore*)dsaddr; } 8120 8121 public: 8122 /// commands 8123 static struct Command { 8124 /// 8125 enum Kind : ubyte { 8126 MoveTo, /// 8127 LineTo, /// 8128 QuadTo, /// 8129 BezierTo, /// 8130 End, /// no more commands (this command is not `valid`!) 8131 8132 } 8133 Kind code; /// 8134 const(float)[] args; /// 8135 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (code >= Kind.min && code < Kind.End && args.length >= 2); } /// 8136 8137 static uint arglen (Kind code) pure nothrow @safe @nogc { 8138 pragma(inline, true); 8139 return 8140 code == Kind.MoveTo || code == Kind.LineTo ? 2 : 8141 code == Kind.QuadTo ? 4 : 8142 code == Kind.BezierTo ? 6 : 8143 0; 8144 } 8145 8146 /// perform NanoVega command with stored data. 8147 void perform (NVGContext ctx) const nothrow @trusted @nogc { 8148 if (ctx is null) return; 8149 final switch (code) { 8150 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(args.ptr[0..2]); break; 8151 case Kind.LineTo: if (args.length > 1) ctx.lineTo(args.ptr[0..2]); break; 8152 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(args.ptr[0..4]); break; 8153 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(args.ptr[0..6]); break; 8154 case Kind.End: break; 8155 } 8156 } 8157 8158 /// perform NanoVega command with stored data, transforming points with [xform] transformation matrix. 8159 void perform() (NVGContext ctx, in auto ref NVGMatrix xform) const nothrow @trusted @nogc { 8160 if (ctx is null || !valid) return; 8161 float[6] pts = void; 8162 pts[0..args.length] = args[]; 8163 foreach (immutable pidx; 0..args.length/2) xform.point(pts.ptr[pidx*2+0], pts.ptr[pidx*2+1]); 8164 final switch (code) { 8165 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(pts.ptr[0..2]); break; 8166 case Kind.LineTo: if (args.length > 1) ctx.lineTo(pts.ptr[0..2]); break; 8167 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(pts.ptr[0..4]); break; 8168 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(pts.ptr[0..6]); break; 8169 case Kind.End: break; 8170 } 8171 } 8172 } 8173 8174 public: 8175 /// Create new path with quadratic bezier (first command is MoveTo, second command is QuadTo). 8176 static NVGPathOutline createNewQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) { 8177 auto res = createNew(); 8178 res.ds.putCommand(Command.Kind.MoveTo); 8179 res.ds.putArgs(x0, y0); 8180 res.ds.putCommand(Command.Kind.QuadTo); 8181 res.ds.putArgs(cx, cy, x, y); 8182 return res; 8183 } 8184 8185 /// Create new path with cubic bezier (first command is MoveTo, second command is BezierTo). 8186 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) { 8187 auto res = createNew(); 8188 res.ds.putCommand(Command.Kind.MoveTo); 8189 res.ds.putArgs(x1, y1); 8190 res.ds.putCommand(Command.Kind.BezierTo); 8191 res.ds.putArgs(x2, y2, x3, y3, x4, y4); 8192 return res; 8193 } 8194 8195 public: 8196 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); } 8197 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); } 8198 8199 void opAssign() (in auto ref NVGPathOutline a) { 8200 incRef(cast(DataStore*)a.dsaddr); 8201 decRef(cast(DataStore*)dsaddr); 8202 dsaddr = a.dsaddr; 8203 } 8204 8205 /// Clear storage. 8206 void clear () { 8207 pragma(inline, true); 8208 decRef(ds); 8209 dsaddr = 0; 8210 } 8211 8212 /// Is this outline empty? 8213 @property empty () const pure { pragma(inline, true); return (dsaddr == 0 || ds.ccount == 0); } 8214 8215 /// Returns number of commands in outline. 8216 @property int length () const pure { pragma(inline, true); return (dsaddr ? ds.ccount : 0); } 8217 8218 /// Returns "flattened" path. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8219 NVGPathOutline flatten () const { pragma(inline, true); return flattenInternal(null); } 8220 8221 /// Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8222 NVGPathOutline flatten() (in auto ref NVGMatrix mt) const { pragma(inline, true); return flattenInternal(&mt); } 8223 8224 // Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8225 private NVGPathOutline flattenInternal (scope NVGMatrix* tfm) const { 8226 import core.stdc.string : memset; 8227 8228 NVGPathOutline res; 8229 if (dsaddr == 0 || ds.ccount == 0) { res = this; return res; } // nothing to do 8230 8231 // check if we need to flatten the path 8232 if (tfm is null) { 8233 bool dowork = false; 8234 foreach (const ref cs; commands) { 8235 if (cs.code != Command.Kind.MoveTo && cs.code != Command.Kind.LineTo) { 8236 dowork = true; 8237 break; 8238 } 8239 } 8240 if (!dowork) { res = this; return res; } // nothing to do 8241 } 8242 8243 NVGcontextinternal ctx; 8244 memset(&ctx, 0, ctx.sizeof); 8245 ctx.cache = nvg__allocPathCache(); 8246 scope(exit) { 8247 import core.stdc.stdlib : free; 8248 nvg__deletePathCache(ctx.cache); 8249 } 8250 8251 ctx.tessTol = 0.25f; 8252 ctx.angleTol = 0; // 0 -- angle tolerance for McSeem Bezier rasterizer 8253 ctx.cuspLimit = 0; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps) 8254 ctx.distTol = 0.01f; 8255 ctx.tesselatortype = NVGTesselation.DeCasteljau; 8256 8257 nvg__addPath(&ctx); // we need this for `nvg__addPoint()` 8258 8259 // has some curves or transformations, convert path 8260 res = createNew(); 8261 float[8] args = void; 8262 8263 res.ds.bounds = [float.max, float.max, -float.max, -float.max]; 8264 8265 float lastX = float.max, lastY = float.max; 8266 bool lastWasMove = false; 8267 8268 void addPoint (float x, float y, Command.Kind cmd=Command.Kind.LineTo) nothrow @trusted @nogc { 8269 if (tfm !is null) tfm.point(x, y); 8270 bool isMove = (cmd == Command.Kind.MoveTo); 8271 if (isMove) { 8272 // moveto 8273 if (lastWasMove && nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return; 8274 } else { 8275 // lineto 8276 if (nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return; 8277 } 8278 lastWasMove = isMove; 8279 lastX = x; 8280 lastY = y; 8281 res.ds.putCommand(cmd); 8282 res.ds.putArgs(x, y); 8283 res.ds.bounds.ptr[0] = nvg__min(res.ds.bounds.ptr[0], x); 8284 res.ds.bounds.ptr[1] = nvg__min(res.ds.bounds.ptr[1], y); 8285 res.ds.bounds.ptr[2] = nvg__max(res.ds.bounds.ptr[2], x); 8286 res.ds.bounds.ptr[3] = nvg__max(res.ds.bounds.ptr[3], y); 8287 } 8288 8289 // sorry for this pasta 8290 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 { 8291 ctx.cache.npoints = 0; 8292 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) { 8293 nvg__tesselateBezier(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner); 8294 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) { 8295 nvg__tesselateBezierMcSeem(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner); 8296 } else { 8297 nvg__tesselateBezierAFD(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, PointFlag.Corner); 8298 } 8299 // add generated points 8300 foreach (const ref pt; ctx.cache.points[0..ctx.cache.npoints]) addPoint(pt.x, pt.y); 8301 } 8302 8303 void flattenQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) { 8304 flattenBezier( 8305 x0, y0, 8306 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 8307 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 8308 x, y, 8309 0, 8310 ); 8311 } 8312 8313 float cx = 0, cy = 0; 8314 foreach (const ref cs; commands) { 8315 switch (cs.code) { 8316 case Command.Kind.LineTo: 8317 case Command.Kind.MoveTo: 8318 addPoint(cs.args[0], cs.args[1], cs.code); 8319 cx = cs.args[0]; 8320 cy = cs.args[1]; 8321 break; 8322 case Command.Kind.QuadTo: 8323 flattenQuad(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3]); 8324 cx = cs.args[2]; 8325 cy = cs.args[3]; 8326 break; 8327 case Command.Kind.BezierTo: 8328 flattenBezier(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3], cs.args[4], cs.args[5], 0); 8329 cx = cs.args[4]; 8330 cy = cs.args[5]; 8331 break; 8332 default: 8333 break; 8334 } 8335 } 8336 8337 return res; 8338 } 8339 8340 /// Returns forward range with all glyph commands. 8341 auto commands () const nothrow @trusted @nogc { 8342 static struct Range { 8343 private nothrow @trusted @nogc: 8344 usize dsaddr; 8345 uint cpos; // current position in data 8346 uint cleft; // number of commands left 8347 @property const(ubyte)* data () inout pure { pragma(inline, true); return (dsaddr ? (cast(DataStore*)dsaddr).data : null); } 8348 public: 8349 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); } 8350 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); } 8351 void opAssign() (in auto ref Range a) { 8352 incRef(cast(DataStore*)a.dsaddr); 8353 decRef(cast(DataStore*)dsaddr); 8354 dsaddr = a.dsaddr; 8355 cpos = a.cpos; 8356 cleft = a.cleft; 8357 } 8358 float[4] bounds () const pure { float[4] res = 0; pragma(inline, true); if (dsaddr) res[] = (cast(DataStore*)dsaddr).bounds[]; return res; } /// outline bounds 8359 @property bool empty () const pure { pragma(inline, true); return (cleft == 0); } 8360 @property int length () const pure { pragma(inline, true); return cleft; } 8361 @property Range save () const { pragma(inline, true); Range res = this; return res; } 8362 @property Command front () const { 8363 Command res = void; 8364 if (cleft > 0) { 8365 res.code = cast(Command.Kind)data[cpos]; 8366 switch (res.code) { 8367 case Command.Kind.MoveTo: 8368 case Command.Kind.LineTo: 8369 res.args = (cast(const(float*))(data+cpos+1))[0..1*2]; 8370 break; 8371 case Command.Kind.QuadTo: 8372 res.args = (cast(const(float*))(data+cpos+1))[0..2*2]; 8373 break; 8374 case Command.Kind.BezierTo: 8375 res.args = (cast(const(float*))(data+cpos+1))[0..3*2]; 8376 break; 8377 default: 8378 res.code = Command.Kind.End; 8379 res.args = null; 8380 break; 8381 } 8382 } else { 8383 res.code = Command.Kind.End; 8384 res.args = null; 8385 } 8386 return res; 8387 } 8388 void popFront () { 8389 if (cleft <= 1) { cleft = 0; return; } // don't waste time skipping last command 8390 --cleft; 8391 switch (data[cpos]) { 8392 case Command.Kind.MoveTo: 8393 case Command.Kind.LineTo: 8394 cpos += 1+1*2*cast(uint)float.sizeof; 8395 break; 8396 case Command.Kind.QuadTo: 8397 cpos += 1+2*2*cast(uint)float.sizeof; 8398 break; 8399 case Command.Kind.BezierTo: 8400 cpos += 1+3*2*cast(uint)float.sizeof; 8401 break; 8402 default: 8403 cleft = 0; 8404 break; 8405 } 8406 } 8407 } 8408 if (dsaddr) { 8409 incRef(cast(DataStore*)dsaddr); // range anchors it 8410 return Range(dsaddr, 0, ds.ccount); 8411 } else { 8412 return Range.init; 8413 } 8414 } 8415 } 8416 8417 public alias NVGGlyphOutline = NVGPathOutline; /// For backwards compatibility. 8418 8419 /// Destroy glyph outiline and free allocated memory. 8420 /// Group: text_api 8421 public void kill (ref NVGPathOutline ol) nothrow @trusted @nogc { 8422 pragma(inline, true); 8423 ol.clear(); 8424 } 8425 8426 static if (is(typeof(&fons__nvg__toOutline))) { 8427 public enum NanoVegaHasCharOutline = true; /// 8428 } else { 8429 public enum NanoVegaHasCharOutline = false; /// 8430 } 8431 8432 /// Returns glyph outlines as array of commands. Vertical 0 is baseline. 8433 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead. 8434 /// Returns `null` if there is no such glyph, or current font is not scalable. 8435 /// Group: text_api 8436 public NVGPathOutline charOutline (NVGContext ctx, dchar dch) nothrow @trusted @nogc { 8437 import core.stdc.stdlib : malloc; 8438 import core.stdc.string : memcpy; 8439 NVGstate* state = nvg__getState(ctx); 8440 ctx.fs.fontId = state.fontId; 8441 auto oline = NVGPathOutline.createNew(); 8442 if (!ctx.fs.toOutline(dch, oline.ds)) oline.clear(); 8443 return oline; 8444 } 8445 8446 8447 float nvg__quantize (float a, float d) pure nothrow @safe @nogc { 8448 pragma(inline, true); 8449 return (cast(int)(a/d+0.5f))*d; 8450 } 8451 8452 float nvg__getFontScale (NVGstate* state) /*pure*/ nothrow @safe @nogc { 8453 pragma(inline, true); 8454 return nvg__min(nvg__quantize(nvg__getAverageScale(state.xform), 0.01f), 4.0f); 8455 } 8456 8457 void nvg__flushTextTexture (NVGContext ctx) nothrow @trusted @nogc { 8458 int[4] dirty = void; 8459 if (ctx.fs.validateTexture(dirty.ptr)) { 8460 auto fontImage = &ctx.fontImages[ctx.fontImageIdx]; 8461 // Update texture 8462 if (fontImage.valid) { 8463 int iw, ih; 8464 const(ubyte)* data = ctx.fs.getTextureData(&iw, &ih); 8465 int x = dirty[0]; 8466 int y = dirty[1]; 8467 int w = dirty[2]-dirty[0]; 8468 int h = dirty[3]-dirty[1]; 8469 ctx.params.renderUpdateTexture(ctx.params.userPtr, fontImage.id, x, y, w, h, data); 8470 } 8471 } 8472 } 8473 8474 bool nvg__allocTextAtlas (NVGContext ctx) nothrow @trusted @nogc { 8475 int iw, ih; 8476 nvg__flushTextTexture(ctx); 8477 if (ctx.fontImageIdx >= NVG_MAX_FONTIMAGES-1) return false; 8478 // if next fontImage already have a texture 8479 if (ctx.fontImages[ctx.fontImageIdx+1].valid) { 8480 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx+1], iw, ih); 8481 } else { 8482 // calculate the new font image size and create it 8483 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx], iw, ih); 8484 if (iw > ih) ih *= 2; else iw *= 2; 8485 if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE; 8486 ctx.fontImages[ctx.fontImageIdx+1].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, iw, ih, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); 8487 if (ctx.fontImages[ctx.fontImageIdx+1].id > 0) { 8488 ctx.fontImages[ctx.fontImageIdx+1].ctx = ctx; 8489 ctx.nvg__imageIncRef(ctx.fontImages[ctx.fontImageIdx+1].id, false); // don't increment driver refcount 8490 } 8491 } 8492 ++ctx.fontImageIdx; 8493 ctx.fs.resetAtlas(iw, ih); 8494 return true; 8495 } 8496 8497 void nvg__renderText (NVGContext ctx, NVGVertex* verts, int nverts) nothrow @trusted @nogc { 8498 NVGstate* state = nvg__getState(ctx); 8499 NVGPaint paint = state.fill; 8500 8501 // Render triangles. 8502 paint.image = ctx.fontImages[ctx.fontImageIdx]; 8503 8504 // Apply global alpha 8505 paint.innerColor.a *= state.alpha; 8506 paint.middleColor.a *= state.alpha; 8507 paint.outerColor.a *= state.alpha; 8508 8509 ctx.params.renderTriangles(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &paint, &state.scissor, verts, nverts); 8510 8511 ++ctx.drawCallCount; 8512 ctx.textTriCount += nverts/3; 8513 } 8514 8515 /// Draws text string at specified location. Returns next x position. 8516 /// Group: text_api 8517 public float text(T) (NVGContext ctx, float x, float y, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 8518 NVGstate* state = nvg__getState(ctx); 8519 FONSTextIter!T iter, prevIter; 8520 FONSQuad q; 8521 NVGVertex* verts; 8522 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8523 float invscale = 1.0f/scale; 8524 int cverts = 0; 8525 int nverts = 0; 8526 8527 if (state.fontId == FONS_INVALID) return x; 8528 if (str.length == 0) return x; 8529 8530 ctx.fs.size = state.fontSize*scale; 8531 ctx.fs.spacing = state.letterSpacing*scale; 8532 ctx.fs.blur = state.fontBlur*scale; 8533 ctx.fs.textAlign = state.textAlign; 8534 ctx.fs.fontId = state.fontId; 8535 8536 cverts = nvg__max(2, cast(int)(str.length))*6; // conservative estimate 8537 verts = nvg__allocTempVerts(ctx, cverts); 8538 if (verts is null) return x; 8539 8540 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Required)) return x; 8541 prevIter = iter; 8542 while (iter.next(q)) { 8543 float[4*2] c = void; 8544 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8545 if (nverts != 0) { 8546 // TODO: add back-end bit to do this just once per frame 8547 nvg__flushTextTexture(ctx); 8548 nvg__renderText(ctx, verts, nverts); 8549 nverts = 0; 8550 } 8551 if (!nvg__allocTextAtlas(ctx)) break; // no memory :( 8552 iter = prevIter; 8553 iter.next(q); // try again 8554 if (iter.prevGlyphIndex < 0) { 8555 // still can not find glyph, try replacement 8556 iter = prevIter; 8557 if (!iter.getDummyChar(q)) break; 8558 } 8559 } 8560 prevIter = iter; 8561 // transform corners 8562 state.xform.point(&c[0], &c[1], q.x0*invscale, q.y0*invscale); 8563 state.xform.point(&c[2], &c[3], q.x1*invscale, q.y0*invscale); 8564 state.xform.point(&c[4], &c[5], q.x1*invscale, q.y1*invscale); 8565 state.xform.point(&c[6], &c[7], q.x0*invscale, q.y1*invscale); 8566 // create triangles 8567 if (nverts+6 <= cverts) { 8568 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts; 8569 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts; 8570 nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); ++nverts; 8571 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts; 8572 nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); ++nverts; 8573 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts; 8574 } 8575 } 8576 8577 // TODO: add back-end bit to do this just once per frame 8578 if (nverts > 0) { 8579 nvg__flushTextTexture(ctx); 8580 nvg__renderText(ctx, verts, nverts); 8581 } 8582 8583 return iter.nextx/scale; 8584 } 8585 8586 /** Draws multi-line text string at specified location wrapped at the specified width. 8587 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8588 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8589 * 8590 * Group: text_api 8591 */ 8592 public void textBox(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 8593 NVGstate* state = nvg__getState(ctx); 8594 if (state.fontId == FONS_INVALID) return; 8595 8596 NVGTextRow!T[2] rows; 8597 auto oldAlign = state.textAlign; 8598 scope(exit) state.textAlign = oldAlign; 8599 auto halign = state.textAlign.horizontal; 8600 float lineh = 0; 8601 8602 ctx.textMetrics(null, null, &lineh); 8603 state.textAlign.horizontal = NVGTextAlign.H.Left; 8604 for (;;) { 8605 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]); 8606 //{ import core.stdc.stdio : printf; printf("slen=%u; rlen=%u; bw=%f\n", cast(uint)str.length, cast(uint)rres.length, cast(double)breakRowWidth); } 8607 if (rres.length == 0) break; 8608 foreach (ref row; rres) { 8609 final switch (halign) { 8610 case NVGTextAlign.H.Left: ctx.text(x, y, row.row); break; 8611 case NVGTextAlign.H.Center: ctx.text(x+breakRowWidth*0.5f-row.width*0.5f, y, row.row); break; 8612 case NVGTextAlign.H.Right: ctx.text(x+breakRowWidth-row.width, y, row.row); break; 8613 } 8614 y += lineh*state.lineHeight; 8615 } 8616 str = rres[$-1].rest; 8617 } 8618 } 8619 8620 private template isGoodPositionDelegate(DG) { 8621 private DG dg; 8622 static if (is(typeof({ NVGGlyphPosition pos; bool res = dg(pos); })) || 8623 is(typeof({ NVGGlyphPosition pos; dg(pos); }))) 8624 enum isGoodPositionDelegate = true; 8625 else 8626 enum isGoodPositionDelegate = false; 8627 } 8628 8629 /** Calculates the glyph x positions of the specified text. 8630 * Measured values are returned in local coordinate space. 8631 * 8632 * Group: text_api 8633 */ 8634 public NVGGlyphPosition[] textGlyphPositions(T) (NVGContext ctx, float x, float y, const(T)[] str, NVGGlyphPosition[] positions) nothrow @trusted @nogc 8635 if (isAnyCharType!T) 8636 { 8637 if (str.length == 0 || positions.length == 0) return positions[0..0]; 8638 usize posnum; 8639 auto len = ctx.textGlyphPositions(x, y, str, (in ref NVGGlyphPosition pos) { 8640 positions.ptr[posnum++] = pos; 8641 return (posnum < positions.length); 8642 }); 8643 return positions[0..len]; 8644 } 8645 8646 /// Ditto. 8647 public int textGlyphPositions(T, DG) (NVGContext ctx, float x, float y, const(T)[] str, scope DG dg) 8648 if (isAnyCharType!T && isGoodPositionDelegate!DG) 8649 { 8650 import std.traits : ReturnType; 8651 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true; 8652 8653 NVGstate* state = nvg__getState(ctx); 8654 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8655 float invscale = 1.0f/scale; 8656 FONSTextIter!T iter, prevIter; 8657 FONSQuad q; 8658 int npos = 0; 8659 8660 if (str.length == 0) return 0; 8661 8662 ctx.fs.size = state.fontSize*scale; 8663 ctx.fs.spacing = state.letterSpacing*scale; 8664 ctx.fs.blur = state.fontBlur*scale; 8665 ctx.fs.textAlign = state.textAlign; 8666 ctx.fs.fontId = state.fontId; 8667 8668 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Optional)) return npos; 8669 prevIter = iter; 8670 while (iter.next(q)) { 8671 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8672 if (!nvg__allocTextAtlas(ctx)) break; // no memory 8673 iter = prevIter; 8674 iter.next(q); // try again 8675 if (iter.prevGlyphIndex < 0) { 8676 // still can not find glyph, try replacement 8677 iter = prevIter; 8678 if (!iter.getDummyChar(q)) break; 8679 } 8680 } 8681 prevIter = iter; 8682 NVGGlyphPosition position = void; //WARNING! 8683 position.strpos = cast(usize)(iter.stringp-str.ptr); 8684 position.x = iter.x*invscale; 8685 position.minx = nvg__min(iter.x, q.x0)*invscale; 8686 position.maxx = nvg__max(iter.nextx, q.x1)*invscale; 8687 ++npos; 8688 static if (RetBool) { if (!dg(position)) return npos; } else dg(position); 8689 } 8690 8691 return npos; 8692 } 8693 8694 private template isGoodRowDelegate(CT, DG) { 8695 private DG dg; 8696 static if (is(typeof({ NVGTextRow!CT row; bool res = dg(row); })) || 8697 is(typeof({ NVGTextRow!CT row; dg(row); }))) 8698 enum isGoodRowDelegate = true; 8699 else 8700 enum isGoodRowDelegate = false; 8701 } 8702 8703 /** Breaks the specified text into lines. 8704 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8705 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8706 * 8707 * Group: text_api 8708 */ 8709 public NVGTextRow!T[] textBreakLines(T) (NVGContext ctx, const(T)[] str, float breakRowWidth, NVGTextRow!T[] rows) nothrow @trusted @nogc 8710 if (isAnyCharType!T) 8711 { 8712 if (rows.length == 0) return rows; 8713 if (rows.length > int.max-1) rows = rows[0..int.max-1]; 8714 int nrow = 0; 8715 auto count = ctx.textBreakLines(str, breakRowWidth, (in ref NVGTextRow!T row) { 8716 rows[nrow++] = row; 8717 return (nrow < rows.length); 8718 }); 8719 return rows[0..count]; 8720 } 8721 8722 /** Breaks the specified text into lines. 8723 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8724 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8725 * Returns number of rows. 8726 * 8727 * Group: text_api 8728 */ 8729 public int textBreakLines(T, DG) (NVGContext ctx, const(T)[] str, float breakRowWidth, scope DG dg) 8730 if (isAnyCharType!T && isGoodRowDelegate!(T, DG)) 8731 { 8732 import std.traits : ReturnType; 8733 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true; 8734 8735 enum NVGcodepointType : int { 8736 Space, 8737 NewLine, 8738 Char, 8739 } 8740 8741 NVGstate* state = nvg__getState(ctx); 8742 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8743 float invscale = 1.0f/scale; 8744 FONSTextIter!T iter, prevIter; 8745 FONSQuad q; 8746 int nrows = 0; 8747 float rowStartX = 0; 8748 float rowWidth = 0; 8749 float rowMinX = 0; 8750 float rowMaxX = 0; 8751 int rowStart = 0; 8752 int rowEnd = 0; 8753 int wordStart = 0; 8754 float wordStartX = 0; 8755 float wordMinX = 0; 8756 int breakEnd = 0; 8757 float breakWidth = 0; 8758 float breakMaxX = 0; 8759 int type = NVGcodepointType.Space, ptype = NVGcodepointType.Space; 8760 uint pcodepoint = 0; 8761 8762 if (state.fontId == FONS_INVALID) return 0; 8763 if (str.length == 0 || dg is null) return 0; 8764 8765 ctx.fs.size = state.fontSize*scale; 8766 ctx.fs.spacing = state.letterSpacing*scale; 8767 ctx.fs.blur = state.fontBlur*scale; 8768 ctx.fs.textAlign = state.textAlign; 8769 ctx.fs.fontId = state.fontId; 8770 8771 breakRowWidth *= scale; 8772 8773 enum Phase { 8774 Normal, // searching for breaking point 8775 SkipBlanks, // skip leading blanks 8776 } 8777 Phase phase = Phase.SkipBlanks; // don't skip blanks on first line 8778 8779 if (!iter.setup(ctx.fs, 0, 0, str, FONSBitmapFlag.Optional)) return 0; 8780 prevIter = iter; 8781 while (iter.next(q)) { 8782 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8783 if (!nvg__allocTextAtlas(ctx)) break; // no memory 8784 iter = prevIter; 8785 iter.next(q); // try again 8786 if (iter.prevGlyphIndex < 0) { 8787 // still can not find glyph, try replacement 8788 iter = prevIter; 8789 if (!iter.getDummyChar(q)) break; 8790 } 8791 } 8792 prevIter = iter; 8793 switch (iter.codepoint) { 8794 case 9: // \t 8795 case 11: // \v 8796 case 12: // \f 8797 case 32: // space 8798 case 0x00a0: // NBSP 8799 type = NVGcodepointType.Space; 8800 break; 8801 case 10: // \n 8802 type = (pcodepoint == 13 ? NVGcodepointType.Space : NVGcodepointType.NewLine); 8803 break; 8804 case 13: // \r 8805 type = (pcodepoint == 10 ? NVGcodepointType.Space : NVGcodepointType.NewLine); 8806 break; 8807 case 0x0085: // NEL 8808 case 0x2028: // Line Separator 8809 case 0x2029: // Paragraph Separator 8810 type = NVGcodepointType.NewLine; 8811 break; 8812 default: 8813 type = NVGcodepointType.Char; 8814 break; 8815 } 8816 if (phase == Phase.SkipBlanks) { 8817 // fix row start 8818 rowStart = cast(int)(iter.stringp-str.ptr); 8819 rowEnd = rowStart; 8820 rowStartX = iter.x; 8821 rowWidth = iter.nextx-rowStartX; // q.x1-rowStartX; 8822 rowMinX = q.x0-rowStartX; 8823 rowMaxX = q.x1-rowStartX; 8824 wordStart = rowStart; 8825 wordStartX = iter.x; 8826 wordMinX = q.x0-rowStartX; 8827 breakEnd = rowStart; 8828 breakWidth = 0.0; 8829 breakMaxX = 0.0; 8830 if (type == NVGcodepointType.Space) continue; 8831 phase = Phase.Normal; 8832 } 8833 8834 if (type == NVGcodepointType.NewLine) { 8835 // always handle new lines 8836 NVGTextRow!T row; 8837 row.string = str; 8838 row.start = rowStart; 8839 row.end = rowEnd; 8840 row.width = rowWidth*invscale; 8841 row.minx = rowMinX*invscale; 8842 row.maxx = rowMaxX*invscale; 8843 ++nrows; 8844 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8845 phase = Phase.SkipBlanks; 8846 } else { 8847 float nextWidth = iter.nextx-rowStartX; 8848 // track last non-white space character 8849 if (type == NVGcodepointType.Char) { 8850 rowEnd = cast(int)(iter.nextp-str.ptr); 8851 rowWidth = iter.nextx-rowStartX; 8852 rowMaxX = q.x1-rowStartX; 8853 } 8854 // track last end of a word 8855 if (ptype == NVGcodepointType.Char && type == NVGcodepointType.Space) { 8856 breakEnd = cast(int)(iter.stringp-str.ptr); 8857 breakWidth = rowWidth; 8858 breakMaxX = rowMaxX; 8859 } 8860 // track last beginning of a word 8861 if (ptype == NVGcodepointType.Space && type == NVGcodepointType.Char) { 8862 wordStart = cast(int)(iter.stringp-str.ptr); 8863 wordStartX = iter.x; 8864 wordMinX = q.x0-rowStartX; 8865 } 8866 // break to new line when a character is beyond break width 8867 if (type == NVGcodepointType.Char && nextWidth > breakRowWidth) { 8868 // the run length is too long, need to break to new line 8869 NVGTextRow!T row; 8870 row.string = str; 8871 if (breakEnd == rowStart) { 8872 // the current word is longer than the row length, just break it from here 8873 row.start = rowStart; 8874 row.end = cast(int)(iter.stringp-str.ptr); 8875 row.width = rowWidth*invscale; 8876 row.minx = rowMinX*invscale; 8877 row.maxx = rowMaxX*invscale; 8878 ++nrows; 8879 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8880 rowStartX = iter.x; 8881 rowStart = cast(int)(iter.stringp-str.ptr); 8882 rowEnd = cast(int)(iter.nextp-str.ptr); 8883 rowWidth = iter.nextx-rowStartX; 8884 rowMinX = q.x0-rowStartX; 8885 rowMaxX = q.x1-rowStartX; 8886 wordStart = rowStart; 8887 wordStartX = iter.x; 8888 wordMinX = q.x0-rowStartX; 8889 } else { 8890 // break the line from the end of the last word, and start new line from the beginning of the new 8891 //{ import core.stdc.stdio : printf; printf("rowStart=%u; rowEnd=%u; breakEnd=%u; len=%u\n", rowStart, rowEnd, breakEnd, cast(uint)str.length); } 8892 row.start = rowStart; 8893 row.end = breakEnd; 8894 row.width = breakWidth*invscale; 8895 row.minx = rowMinX*invscale; 8896 row.maxx = breakMaxX*invscale; 8897 ++nrows; 8898 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8899 rowStartX = wordStartX; 8900 rowStart = wordStart; 8901 rowEnd = cast(int)(iter.nextp-str.ptr); 8902 rowWidth = iter.nextx-rowStartX; 8903 rowMinX = wordMinX; 8904 rowMaxX = q.x1-rowStartX; 8905 // no change to the word start 8906 } 8907 // set null break point 8908 breakEnd = rowStart; 8909 breakWidth = 0.0; 8910 breakMaxX = 0.0; 8911 } 8912 } 8913 8914 pcodepoint = iter.codepoint; 8915 ptype = type; 8916 } 8917 8918 // break the line from the end of the last word, and start new line from the beginning of the new 8919 if (phase != Phase.SkipBlanks && rowStart < str.length) { 8920 //{ import core.stdc.stdio : printf; printf(" rowStart=%u; len=%u\n", rowStart, cast(uint)str.length); } 8921 NVGTextRow!T row; 8922 row.string = str; 8923 row.start = rowStart; 8924 row.end = cast(int)str.length; 8925 row.width = rowWidth*invscale; 8926 row.minx = rowMinX*invscale; 8927 row.maxx = rowMaxX*invscale; 8928 ++nrows; 8929 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8930 } 8931 8932 return nrows; 8933 } 8934 8935 /** Returns iterator which you can use to calculate text bounds and advancement. 8936 * This is usable when you need to do some text layouting with wrapping, to avoid 8937 * guesswork ("will advancement for this space stay the same?"), and Schlemiel's 8938 * algorithm. Note that you can copy the returned struct to save iterator state. 8939 * 8940 * You can check if iterator is valid with [valid] property, put new chars with 8941 * [put] method, get current advance with [advance] property, and current 8942 * bounds with `getBounds(ref float[4] bounds)` method. 8943 * 8944 * $(WARNING Don't change font parameters while iterating! Or use [restoreFont] method.) 8945 * 8946 * Group: text_api 8947 */ 8948 public struct TextBoundsIterator { 8949 private: 8950 NVGContext ctx; 8951 FONSTextBoundsIterator fsiter; // fontstash iterator 8952 float scale, invscale, xscaled, yscaled; 8953 // font settings 8954 float fsSize, fsSpacing, fsBlur; 8955 int fsFontId; 8956 NVGTextAlign fsAlign; 8957 8958 public: 8959 /// Setups iteration. Takes current font parameters from the given NanoVega context. 8960 this (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { reset(actx, ax, ay); } 8961 8962 /// Resets iteration. Takes current font parameters from the given NanoVega context. 8963 void reset (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { 8964 fsiter = fsiter.init; 8965 this = this.init; 8966 if (actx is null) return; 8967 NVGstate* state = nvg__getState(actx); 8968 if (state is null) return; 8969 if (state.fontId == FONS_INVALID) { ctx = null; return; } 8970 8971 ctx = actx; 8972 scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8973 invscale = 1.0f/scale; 8974 8975 fsSize = state.fontSize*scale; 8976 fsSpacing = state.letterSpacing*scale; 8977 fsBlur = state.fontBlur*scale; 8978 fsAlign = state.textAlign; 8979 fsFontId = state.fontId; 8980 restoreFont(); 8981 8982 xscaled = ax*scale; 8983 yscaled = ay*scale; 8984 fsiter.reset(ctx.fs, xscaled, yscaled); 8985 } 8986 8987 /// Restart iteration. Will not restore font. 8988 void restart () nothrow @trusted @nogc { 8989 if (ctx !is null) fsiter.reset(ctx.fs, xscaled, yscaled); 8990 } 8991 8992 /// Restore font settings for the context. 8993 void restoreFont () nothrow @trusted @nogc { 8994 if (ctx !is null) { 8995 ctx.fs.size = fsSize; 8996 ctx.fs.spacing = fsSpacing; 8997 ctx.fs.blur = fsBlur; 8998 ctx.fs.textAlign = fsAlign; 8999 ctx.fs.fontId = fsFontId; 9000 } 9001 } 9002 9003 /// Is this iterator valid? 9004 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null); } 9005 9006 /// Add chars. 9007 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { pragma(inline, true); if (ctx !is null) fsiter.put(str[]); } 9008 9009 /// Returns current advance 9010 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null ? fsiter.advance*invscale : 0); } 9011 9012 /// Returns current text bounds. 9013 void getBounds (ref float[4] bounds) nothrow @trusted @nogc { 9014 if (ctx !is null) { 9015 fsiter.getBounds(bounds); 9016 ctx.fs.getLineBounds(yscaled, &bounds[1], &bounds[3]); 9017 bounds[0] *= invscale; 9018 bounds[1] *= invscale; 9019 bounds[2] *= invscale; 9020 bounds[3] *= invscale; 9021 } else { 9022 bounds[] = 0; 9023 } 9024 } 9025 9026 /// Returns current horizontal text bounds. 9027 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc { 9028 if (ctx !is null) { 9029 fsiter.getHBounds(xmin, xmax); 9030 xmin *= invscale; 9031 xmax *= invscale; 9032 } 9033 } 9034 9035 /// Returns current vertical text bounds. 9036 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc { 9037 if (ctx !is null) { 9038 //fsiter.getVBounds(ymin, ymax); 9039 ctx.fs.getLineBounds(yscaled, &ymin, &ymax); 9040 ymin *= invscale; 9041 ymax *= invscale; 9042 } 9043 } 9044 } 9045 9046 /// Returns font line height (without line spacing), measured in local coordinate space. 9047 /// Group: text_api 9048 public float textFontHeight (NVGContext ctx) nothrow @trusted @nogc { 9049 float res = void; 9050 ctx.textMetrics(null, null, &res); 9051 return res; 9052 } 9053 9054 /// Returns font ascender (positive), measured in local coordinate space. 9055 /// Group: text_api 9056 public float textFontAscender (NVGContext ctx) nothrow @trusted @nogc { 9057 float res = void; 9058 ctx.textMetrics(&res, null, null); 9059 return res; 9060 } 9061 9062 /// Returns font descender (negative), measured in local coordinate space. 9063 /// Group: text_api 9064 public float textFontDescender (NVGContext ctx) nothrow @trusted @nogc { 9065 float res = void; 9066 ctx.textMetrics(null, &res, null); 9067 return res; 9068 } 9069 9070 /** Measures the specified text string. Returns horizontal and vertical sizes of the measured text. 9071 * Measured values are returned in local coordinate space. 9072 * 9073 * Group: text_api 9074 */ 9075 public void textExtents(T) (NVGContext ctx, const(T)[] str, float *w, float *h) nothrow @trusted @nogc if (isAnyCharType!T) { 9076 float[4] bnd = void; 9077 ctx.textBounds(0, 0, str, bnd[]); 9078 if (!ctx.fs.getFontAA(nvg__getState(ctx).fontId)) { 9079 if (w !is null) *w = nvg__lrintf(bnd.ptr[2]-bnd.ptr[0]); 9080 if (h !is null) *h = nvg__lrintf(bnd.ptr[3]-bnd.ptr[1]); 9081 } else { 9082 if (w !is null) *w = bnd.ptr[2]-bnd.ptr[0]; 9083 if (h !is null) *h = bnd.ptr[3]-bnd.ptr[1]; 9084 } 9085 } 9086 9087 /** Measures the specified text string. Returns horizontal size of the measured text. 9088 * Measured values are returned in local coordinate space. 9089 * 9090 * Group: text_api 9091 */ 9092 public float textWidth(T) (NVGContext ctx, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 9093 float w = void; 9094 ctx.textExtents(str, &w, null); 9095 return w; 9096 } 9097 9098 /** Measures the specified text string. Parameter bounds should be a float[4], 9099 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax] 9100 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn). 9101 * Measured values are returned in local coordinate space. 9102 * 9103 * Group: text_api 9104 */ 9105 public float textBounds(T) (NVGContext ctx, float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc 9106 if (isAnyCharType!T) 9107 { 9108 NVGstate* state = nvg__getState(ctx); 9109 9110 if (state.fontId == FONS_INVALID) { 9111 bounds[] = 0; 9112 return 0; 9113 } 9114 9115 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9116 ctx.fs.size = state.fontSize*scale; 9117 ctx.fs.spacing = state.letterSpacing*scale; 9118 ctx.fs.blur = state.fontBlur*scale; 9119 ctx.fs.textAlign = state.textAlign; 9120 ctx.fs.fontId = state.fontId; 9121 9122 float[4] b = void; 9123 immutable float width = ctx.fs.getTextBounds(x*scale, y*scale, str, b[]); 9124 immutable float invscale = 1.0f/scale; 9125 if (bounds.length) { 9126 // use line bounds for height 9127 ctx.fs.getLineBounds(y*scale, b.ptr+1, b.ptr+3); 9128 if (bounds.length > 0) bounds.ptr[0] = b.ptr[0]*invscale; 9129 if (bounds.length > 1) bounds.ptr[1] = b.ptr[1]*invscale; 9130 if (bounds.length > 2) bounds.ptr[2] = b.ptr[2]*invscale; 9131 if (bounds.length > 3) bounds.ptr[3] = b.ptr[3]*invscale; 9132 } 9133 return width*invscale; 9134 } 9135 9136 /// Ditto. 9137 public void textBoxBounds(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str, float[] bounds) if (isAnyCharType!T) { 9138 NVGstate* state = nvg__getState(ctx); 9139 NVGTextRow!T[2] rows; 9140 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9141 float invscale = 1.0f/scale; 9142 float lineh = 0, rminy = 0, rmaxy = 0; 9143 float minx, miny, maxx, maxy; 9144 9145 if (state.fontId == FONS_INVALID) { 9146 bounds[] = 0; 9147 return; 9148 } 9149 9150 auto oldAlign = state.textAlign; 9151 scope(exit) state.textAlign = oldAlign; 9152 auto halign = state.textAlign.horizontal; 9153 9154 ctx.textMetrics(null, null, &lineh); 9155 state.textAlign.horizontal = NVGTextAlign.H.Left; 9156 9157 minx = maxx = x; 9158 miny = maxy = y; 9159 9160 ctx.fs.size = state.fontSize*scale; 9161 ctx.fs.spacing = state.letterSpacing*scale; 9162 ctx.fs.blur = state.fontBlur*scale; 9163 ctx.fs.textAlign = state.textAlign; 9164 ctx.fs.fontId = state.fontId; 9165 ctx.fs.getLineBounds(0, &rminy, &rmaxy); 9166 rminy *= invscale; 9167 rmaxy *= invscale; 9168 9169 for (;;) { 9170 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]); 9171 if (rres.length == 0) break; 9172 foreach (ref row; rres) { 9173 float rminx, rmaxx, dx = 0; 9174 // horizontal bounds 9175 final switch (halign) { 9176 case NVGTextAlign.H.Left: dx = 0; break; 9177 case NVGTextAlign.H.Center: dx = breakRowWidth*0.5f-row.width*0.5f; break; 9178 case NVGTextAlign.H.Right: dx = breakRowWidth-row.width; break; 9179 } 9180 rminx = x+row.minx+dx; 9181 rmaxx = x+row.maxx+dx; 9182 minx = nvg__min(minx, rminx); 9183 maxx = nvg__max(maxx, rmaxx); 9184 // vertical bounds 9185 miny = nvg__min(miny, y+rminy); 9186 maxy = nvg__max(maxy, y+rmaxy); 9187 y += lineh*state.lineHeight; 9188 } 9189 str = rres[$-1].rest; 9190 } 9191 9192 if (bounds.length) { 9193 if (bounds.length > 0) bounds.ptr[0] = minx; 9194 if (bounds.length > 1) bounds.ptr[1] = miny; 9195 if (bounds.length > 2) bounds.ptr[2] = maxx; 9196 if (bounds.length > 3) bounds.ptr[3] = maxy; 9197 } 9198 } 9199 9200 /// Returns the vertical metrics based on the current text style. Measured values are returned in local coordinate space. 9201 /// Group: text_api 9202 public void textMetrics (NVGContext ctx, float* ascender, float* descender, float* lineh) nothrow @trusted @nogc { 9203 NVGstate* state = nvg__getState(ctx); 9204 9205 if (state.fontId == FONS_INVALID) { 9206 if (ascender !is null) *ascender *= 0; 9207 if (descender !is null) *descender *= 0; 9208 if (lineh !is null) *lineh *= 0; 9209 return; 9210 } 9211 9212 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9213 immutable float invscale = 1.0f/scale; 9214 9215 ctx.fs.size = state.fontSize*scale; 9216 ctx.fs.spacing = state.letterSpacing*scale; 9217 ctx.fs.blur = state.fontBlur*scale; 9218 ctx.fs.textAlign = state.textAlign; 9219 ctx.fs.fontId = state.fontId; 9220 9221 ctx.fs.getVertMetrics(ascender, descender, lineh); 9222 if (ascender !is null) *ascender *= invscale; 9223 if (descender !is null) *descender *= invscale; 9224 if (lineh !is null) *lineh *= invscale; 9225 } 9226 9227 9228 // ////////////////////////////////////////////////////////////////////////// // 9229 // fontstash 9230 // ////////////////////////////////////////////////////////////////////////// // 9231 import core.stdc.stdlib : malloc, realloc, free; 9232 import core.stdc.string : memset, memcpy, strncpy, strcmp, strlen; 9233 import core.stdc.stdio : FILE, fopen, fclose, fseek, ftell, fread, SEEK_END, SEEK_SET; 9234 9235 public: 9236 // welcome to version hell! 9237 version(nanovg_force_stb_ttf) { 9238 } else { 9239 version(nanovg_force_detect) {} else version(nanovg_use_freetype) { version = nanovg_use_freetype_ii; } 9240 } 9241 version(nanovg_ignore_iv_stb_ttf) enum nanovg_ignore_iv_stb_ttf = true; else enum nanovg_ignore_iv_stb_ttf = false; 9242 //version(nanovg_ignore_mono); 9243 9244 version(nanovg_force_stb_ttf) { 9245 private enum NanoVegaForceFreeType = false; 9246 } else { 9247 version (nanovg_builtin_freetype_bindings) { 9248 version(Posix) { 9249 private enum NanoVegaForceFreeType = true; 9250 } else { 9251 private enum NanoVegaForceFreeType = false; 9252 } 9253 } else { 9254 version(Posix) { 9255 private enum NanoVegaForceFreeType = true; 9256 } else { 9257 private enum NanoVegaForceFreeType = false; 9258 } 9259 } 9260 } 9261 9262 version(nanovg_use_freetype_ii) { 9263 enum NanoVegaIsUsingSTBTTF = false; 9264 //pragma(msg, "iv.freetype: forced"); 9265 } else { 9266 static if (NanoVegaForceFreeType) { 9267 enum NanoVegaIsUsingSTBTTF = false; 9268 } else { 9269 static if (!nanovg_ignore_iv_stb_ttf && __traits(compiles, { import iv.stb.ttf; })) { 9270 import iv.stb.ttf; 9271 enum NanoVegaIsUsingSTBTTF = true; 9272 version(nanovg_report_stb_ttf) pragma(msg, "iv.stb.ttf"); 9273 } else static if (__traits(compiles, { import arsd.ttf; })) { 9274 import arsd.ttf; 9275 enum NanoVegaIsUsingSTBTTF = true; 9276 version(nanovg_report_stb_ttf) pragma(msg, "arsd.ttf"); 9277 } else static if (__traits(compiles, { import stb_truetype; })) { 9278 import stb_truetype; 9279 enum NanoVegaIsUsingSTBTTF = true; 9280 version(nanovg_report_stb_ttf) pragma(msg, "stb_truetype"); 9281 } else static if (__traits(compiles, { import iv.freetype; })) { 9282 version (nanovg_builtin_freetype_bindings) { 9283 enum NanoVegaIsUsingSTBTTF = false; 9284 version = nanovg_builtin_freetype_bindings; 9285 } else { 9286 import iv.freetype; 9287 enum NanoVegaIsUsingSTBTTF = false; 9288 } 9289 version(nanovg_report_stb_ttf) pragma(msg, "freetype"); 9290 } else { 9291 static assert(0, "no stb_ttf/iv.freetype found!"); 9292 } 9293 } 9294 } 9295 9296 9297 // ////////////////////////////////////////////////////////////////////////// // 9298 //version = nanovg_ft_mono; 9299 9300 /// Invald font id. 9301 /// Group: font_stash 9302 public enum FONS_INVALID = -1; 9303 9304 public enum FONSBitmapFlag : uint { 9305 Required = 0, 9306 Optional = 1, 9307 } 9308 9309 public enum FONSError : int { 9310 NoError = 0, 9311 AtlasFull = 1, // Font atlas is full. 9312 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. 9313 StatesOverflow = 3, // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES. 9314 StatesUnderflow = 4, // Trying to pop too many states fonsPopState(). 9315 } 9316 9317 /// Initial parameters for new FontStash. 9318 /// Group: font_stash 9319 public struct FONSParams { 9320 enum Flag : uint { 9321 ZeroTopLeft = 0U, // default 9322 ZeroBottomLeft = 1U, 9323 } 9324 int width, height; 9325 Flag flags = Flag.ZeroTopLeft; 9326 void* userPtr; 9327 bool function (void* uptr, int width, int height) nothrow @trusted @nogc renderCreate; 9328 int function (void* uptr, int width, int height) nothrow @trusted @nogc renderResize; 9329 void function (void* uptr, int* rect, const(ubyte)* data) nothrow @trusted @nogc renderUpdate; 9330 void function (void* uptr) nothrow @trusted @nogc renderDelete; 9331 @property bool isZeroTopLeft () const pure nothrow @trusted @nogc { pragma(inline, true); return ((flags&Flag.ZeroBottomLeft) == 0); } 9332 } 9333 9334 //TODO: document this 9335 public struct FONSQuad { 9336 float x0=0, y0=0, s0=0, t0=0; 9337 float x1=0, y1=0, s1=0, t1=0; 9338 } 9339 9340 //TODO: document this 9341 public struct FONSTextIter(CT) if (isAnyCharType!CT) { 9342 alias CharType = CT; 9343 float x=0, y=0, nextx=0, nexty=0, scale=0, spacing=0; 9344 uint codepoint; 9345 short isize, iblur; 9346 FONSContext stash; 9347 FONSfont* font; 9348 int prevGlyphIndex; 9349 const(CT)* s; // string 9350 const(CT)* n; // next 9351 const(CT)* e; // end 9352 FONSBitmapFlag bitmapOption; 9353 static if (is(CT == char)) { 9354 uint utf8state; 9355 } 9356 9357 this (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { setup(astash, ax, ay, astr, abitmapOption); } 9358 ~this () nothrow @trusted @nogc { pragma(inline, true); static if (is(CT == char)) utf8state = 0; s = n = e = null; } 9359 9360 @property const(CT)* stringp () const pure nothrow @trusted @nogc { pragma(inline, true); return s; } 9361 @property const(CT)* nextp () const pure nothrow @trusted @nogc { pragma(inline, true); return n; } 9362 @property const(CT)* endp () const pure nothrow @trusted @nogc { pragma(inline, true); return e; } 9363 9364 bool setup (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { 9365 import core.stdc.string : memset; 9366 9367 memset(&this, 0, this.sizeof); 9368 if (astash is null) return false; 9369 9370 FONSstate* state = astash.getState; 9371 9372 if (state.font < 0 || state.font >= astash.nfonts) return false; 9373 font = astash.fonts[state.font]; 9374 if (font is null || font.fdata is null) return false; 9375 9376 isize = cast(short)(state.size*10.0f); 9377 iblur = cast(short)state.blur; 9378 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 9379 9380 // align horizontally 9381 if (state.talign.left) { 9382 // empty 9383 } else if (state.talign.right) { 9384 immutable float width = astash.getTextBounds(ax, ay, astr, null); 9385 ax -= width; 9386 } else if (state.talign.center) { 9387 immutable float width = astash.getTextBounds(ax, ay, astr, null); 9388 ax -= width*0.5f; 9389 } 9390 9391 // align vertically 9392 ay += astash.getVertAlign(font, state.talign, isize); 9393 9394 x = nextx = ax; 9395 y = nexty = ay; 9396 spacing = state.spacing; 9397 9398 if (astr.ptr is null) { 9399 static if (is(CharType == char)) astr = ""; 9400 else static if (is(CharType == wchar)) astr = ""w; 9401 else static if (is(CharType == dchar)) astr = ""d; 9402 else static assert(0, "wtf?!"); 9403 } 9404 s = astr.ptr; 9405 n = astr.ptr; 9406 e = astr.ptr+astr.length; 9407 9408 codepoint = 0; 9409 prevGlyphIndex = -1; 9410 bitmapOption = abitmapOption; 9411 stash = astash; 9412 9413 return true; 9414 } 9415 9416 bool getDummyChar (ref FONSQuad quad) nothrow @trusted @nogc { 9417 if (stash is null || font is null) return false; 9418 // get glyph and quad 9419 x = nextx; 9420 y = nexty; 9421 FONSglyph* glyph = stash.getGlyph(font, 0xFFFD, isize, iblur, bitmapOption); 9422 if (glyph !is null) { 9423 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9424 prevGlyphIndex = glyph.index; 9425 return true; 9426 } else { 9427 prevGlyphIndex = -1; 9428 return false; 9429 } 9430 } 9431 9432 bool next (ref FONSQuad quad) nothrow @trusted @nogc { 9433 if (stash is null || font is null) return false; 9434 FONSglyph* glyph = null; 9435 static if (is(CharType == char)) { 9436 const(char)* str = this.n; 9437 this.s = this.n; 9438 if (str is this.e) return false; 9439 const(char)* e = this.e; 9440 for (; str !is e; ++str) { 9441 /*if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;*/ 9442 mixin(DecUtfMixin!("this.utf8state", "this.codepoint", "*cast(const(ubyte)*)str")); 9443 if (utf8state) continue; 9444 ++str; // 'cause we'll break anyway 9445 // get glyph and quad 9446 x = nextx; 9447 y = nexty; 9448 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption); 9449 if (glyph !is null) { 9450 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9451 prevGlyphIndex = glyph.index; 9452 } else { 9453 prevGlyphIndex = -1; 9454 } 9455 break; 9456 } 9457 this.n = str; 9458 } else { 9459 const(CharType)* str = this.n; 9460 this.s = this.n; 9461 if (str is this.e) return false; 9462 codepoint = cast(uint)(*str++); 9463 if (codepoint > dchar.max) codepoint = 0xFFFD; 9464 // get glyph and quad 9465 x = nextx; 9466 y = nexty; 9467 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption); 9468 if (glyph !is null) { 9469 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9470 prevGlyphIndex = glyph.index; 9471 } else { 9472 prevGlyphIndex = -1; 9473 } 9474 this.n = str; 9475 } 9476 return true; 9477 } 9478 } 9479 9480 9481 // ////////////////////////////////////////////////////////////////////////// // 9482 //static if (!HasAST) version = nanovg_use_freetype_ii_x; 9483 9484 /*version(nanovg_use_freetype_ii_x)*/ static if (!NanoVegaIsUsingSTBTTF) { 9485 version(nanovg_builtin_freetype_bindings) { 9486 pragma(lib, "freetype"); 9487 private extern(C) nothrow @trusted @nogc { 9488 private import core.stdc.config : c_long, c_ulong; 9489 alias FT_Pos = c_long; 9490 // config/ftconfig.h 9491 alias FT_Int16 = short; 9492 alias FT_UInt16 = ushort; 9493 alias FT_Int32 = int; 9494 alias FT_UInt32 = uint; 9495 alias FT_Fast = int; 9496 alias FT_UFast = uint; 9497 alias FT_Int64 = long; 9498 alias FT_Uint64 = ulong; 9499 // fttypes.h 9500 alias FT_Bool = ubyte; 9501 alias FT_FWord = short; 9502 alias FT_UFWord = ushort; 9503 alias FT_Char = char; 9504 alias FT_Byte = ubyte; 9505 alias FT_Bytes = FT_Byte*; 9506 alias FT_Tag = FT_UInt32; 9507 alias FT_String = char; 9508 alias FT_Short = short; 9509 alias FT_UShort = ushort; 9510 alias FT_Int = int; 9511 alias FT_UInt = uint; 9512 alias FT_Long = c_long; 9513 alias FT_ULong = c_ulong; 9514 alias FT_F2Dot14 = short; 9515 alias FT_F26Dot6 = c_long; 9516 alias FT_Fixed = c_long; 9517 alias FT_Error = int; 9518 alias FT_Pointer = void*; 9519 alias FT_Offset = usize; 9520 alias FT_PtrDist = ptrdiff_t; 9521 9522 struct FT_UnitVector { 9523 FT_F2Dot14 x; 9524 FT_F2Dot14 y; 9525 } 9526 9527 struct FT_Matrix { 9528 FT_Fixed xx, xy; 9529 FT_Fixed yx, yy; 9530 } 9531 9532 struct FT_Data { 9533 const(FT_Byte)* pointer; 9534 FT_Int length; 9535 } 9536 alias FT_Face = FT_FaceRec*; 9537 struct FT_FaceRec { 9538 FT_Long num_faces; 9539 FT_Long face_index; 9540 FT_Long face_flags; 9541 FT_Long style_flags; 9542 FT_Long num_glyphs; 9543 FT_String* family_name; 9544 FT_String* style_name; 9545 FT_Int num_fixed_sizes; 9546 FT_Bitmap_Size* available_sizes; 9547 FT_Int num_charmaps; 9548 FT_CharMap* charmaps; 9549 FT_Generic generic; 9550 FT_BBox bbox; 9551 FT_UShort units_per_EM; 9552 FT_Short ascender; 9553 FT_Short descender; 9554 FT_Short height; 9555 FT_Short max_advance_width; 9556 FT_Short max_advance_height; 9557 FT_Short underline_position; 9558 FT_Short underline_thickness; 9559 FT_GlyphSlot glyph; 9560 FT_Size size; 9561 FT_CharMap charmap; 9562 FT_Driver driver; 9563 FT_Memory memory; 9564 FT_Stream stream; 9565 FT_ListRec sizes_list; 9566 FT_Generic autohint; 9567 void* extensions; 9568 FT_Face_Internal internal; 9569 } 9570 struct FT_Bitmap_Size { 9571 FT_Short height; 9572 FT_Short width; 9573 FT_Pos size; 9574 FT_Pos x_ppem; 9575 FT_Pos y_ppem; 9576 } 9577 alias FT_CharMap = FT_CharMapRec*; 9578 struct FT_CharMapRec { 9579 FT_Face face; 9580 FT_Encoding encoding; 9581 FT_UShort platform_id; 9582 FT_UShort encoding_id; 9583 } 9584 extern(C) nothrow @nogc { alias FT_Generic_Finalizer = void function (void* object); } 9585 struct FT_Generic { 9586 void* data; 9587 FT_Generic_Finalizer finalizer; 9588 } 9589 struct FT_Vector { 9590 FT_Pos x; 9591 FT_Pos y; 9592 } 9593 struct FT_BBox { 9594 FT_Pos xMin, yMin; 9595 FT_Pos xMax, yMax; 9596 } 9597 alias FT_Pixel_Mode = int; 9598 enum { 9599 FT_PIXEL_MODE_NONE = 0, 9600 FT_PIXEL_MODE_MONO, 9601 FT_PIXEL_MODE_GRAY, 9602 FT_PIXEL_MODE_GRAY2, 9603 FT_PIXEL_MODE_GRAY4, 9604 FT_PIXEL_MODE_LCD, 9605 FT_PIXEL_MODE_LCD_V, 9606 FT_PIXEL_MODE_MAX 9607 } 9608 struct FT_Bitmap { 9609 uint rows; 9610 uint width; 9611 int pitch; 9612 ubyte* buffer; 9613 ushort num_grays; 9614 ubyte pixel_mode; 9615 ubyte palette_mode; 9616 void* palette; 9617 } 9618 struct FT_Outline { 9619 short n_contours; 9620 short n_points; 9621 FT_Vector* points; 9622 byte* tags; 9623 short* contours; 9624 int flags; 9625 } 9626 alias FT_GlyphSlot = FT_GlyphSlotRec*; 9627 struct FT_GlyphSlotRec { 9628 FT_Library library; 9629 FT_Face face; 9630 FT_GlyphSlot next; 9631 FT_UInt reserved; 9632 FT_Generic generic; 9633 FT_Glyph_Metrics metrics; 9634 FT_Fixed linearHoriAdvance; 9635 FT_Fixed linearVertAdvance; 9636 FT_Vector advance; 9637 FT_Glyph_Format format; 9638 FT_Bitmap bitmap; 9639 FT_Int bitmap_left; 9640 FT_Int bitmap_top; 9641 FT_Outline outline; 9642 FT_UInt num_subglyphs; 9643 FT_SubGlyph subglyphs; 9644 void* control_data; 9645 c_long control_len; 9646 FT_Pos lsb_delta; 9647 FT_Pos rsb_delta; 9648 void* other; 9649 FT_Slot_Internal internal; 9650 } 9651 alias FT_Size = FT_SizeRec*; 9652 struct FT_SizeRec { 9653 FT_Face face; 9654 FT_Generic generic; 9655 FT_Size_Metrics metrics; 9656 FT_Size_Internal internal; 9657 } 9658 alias FT_Encoding = FT_Tag; 9659 alias FT_Face_Internal = void*; 9660 alias FT_Driver = void*; 9661 alias FT_Memory = void*; 9662 alias FT_Stream = void*; 9663 alias FT_Library = void*; 9664 alias FT_SubGlyph = void*; 9665 alias FT_Slot_Internal = void*; 9666 alias FT_Size_Internal = void*; 9667 alias FT_ListNode = FT_ListNodeRec*; 9668 alias FT_List = FT_ListRec*; 9669 struct FT_ListNodeRec { 9670 FT_ListNode prev; 9671 FT_ListNode next; 9672 void* data; 9673 } 9674 struct FT_ListRec { 9675 FT_ListNode head; 9676 FT_ListNode tail; 9677 } 9678 struct FT_Glyph_Metrics { 9679 FT_Pos width; 9680 FT_Pos height; 9681 FT_Pos horiBearingX; 9682 FT_Pos horiBearingY; 9683 FT_Pos horiAdvance; 9684 FT_Pos vertBearingX; 9685 FT_Pos vertBearingY; 9686 FT_Pos vertAdvance; 9687 } 9688 alias FT_Glyph_Format = FT_Tag; 9689 FT_Tag FT_MAKE_TAG (char x1, char x2, char x3, char x4) pure nothrow @safe @nogc { 9690 pragma(inline, true); 9691 return cast(FT_UInt32)((x1<<24)|(x2<<16)|(x3<<8)|x4); 9692 } 9693 enum : FT_Tag { 9694 FT_GLYPH_FORMAT_NONE = 0, 9695 FT_GLYPH_FORMAT_COMPOSITE = FT_MAKE_TAG('c','o','m','p'), 9696 FT_GLYPH_FORMAT_BITMAP = FT_MAKE_TAG('b','i','t','s'), 9697 FT_GLYPH_FORMAT_OUTLINE = FT_MAKE_TAG('o','u','t','l'), 9698 FT_GLYPH_FORMAT_PLOTTER = FT_MAKE_TAG('p','l','o','t'), 9699 } 9700 struct FT_Size_Metrics { 9701 FT_UShort x_ppem; 9702 FT_UShort y_ppem; 9703 9704 FT_Fixed x_scale; 9705 FT_Fixed y_scale; 9706 9707 FT_Pos ascender; 9708 FT_Pos descender; 9709 FT_Pos height; 9710 FT_Pos max_advance; 9711 } 9712 enum FT_LOAD_DEFAULT = 0x0U; 9713 enum FT_LOAD_NO_SCALE = 1U<<0; 9714 enum FT_LOAD_NO_HINTING = 1U<<1; 9715 enum FT_LOAD_RENDER = 1U<<2; 9716 enum FT_LOAD_NO_BITMAP = 1U<<3; 9717 enum FT_LOAD_VERTICAL_LAYOUT = 1U<<4; 9718 enum FT_LOAD_FORCE_AUTOHINT = 1U<<5; 9719 enum FT_LOAD_CROP_BITMAP = 1U<<6; 9720 enum FT_LOAD_PEDANTIC = 1U<<7; 9721 enum FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1U<<9; 9722 enum FT_LOAD_NO_RECURSE = 1U<<10; 9723 enum FT_LOAD_IGNORE_TRANSFORM = 1U<<11; 9724 enum FT_LOAD_MONOCHROME = 1U<<12; 9725 enum FT_LOAD_LINEAR_DESIGN = 1U<<13; 9726 enum FT_LOAD_NO_AUTOHINT = 1U<<15; 9727 enum FT_LOAD_COLOR = 1U<<20; 9728 enum FT_LOAD_COMPUTE_METRICS = 1U<<21; 9729 enum FT_FACE_FLAG_KERNING = 1U<<6; 9730 alias FT_Kerning_Mode = int; 9731 enum /*FT_Kerning_Mode*/ { 9732 FT_KERNING_DEFAULT = 0, 9733 FT_KERNING_UNFITTED, 9734 FT_KERNING_UNSCALED 9735 } 9736 extern(C) nothrow @nogc { 9737 alias FT_Outline_MoveToFunc = int function (const(FT_Vector)*, void*); 9738 alias FT_Outline_LineToFunc = int function (const(FT_Vector)*, void*); 9739 alias FT_Outline_ConicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, void*); 9740 alias FT_Outline_CubicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, const(FT_Vector)*, void*); 9741 } 9742 struct FT_Outline_Funcs { 9743 FT_Outline_MoveToFunc move_to; 9744 FT_Outline_LineToFunc line_to; 9745 FT_Outline_ConicToFunc conic_to; 9746 FT_Outline_CubicToFunc cubic_to; 9747 int shift; 9748 FT_Pos delta; 9749 } 9750 9751 FT_Error FT_Init_FreeType (FT_Library*); 9752 FT_Error FT_New_Memory_Face (FT_Library, const(FT_Byte)*, FT_Long, FT_Long, FT_Face*); 9753 FT_UInt FT_Get_Char_Index (FT_Face, FT_ULong); 9754 FT_Error FT_Set_Pixel_Sizes (FT_Face, FT_UInt, FT_UInt); 9755 FT_Error FT_Load_Glyph (FT_Face, FT_UInt, FT_Int32); 9756 FT_Error FT_Get_Advance (FT_Face, FT_UInt, FT_Int32, FT_Fixed*); 9757 FT_Error FT_Get_Kerning (FT_Face, FT_UInt, FT_UInt, FT_UInt, FT_Vector*); 9758 void FT_Outline_Get_CBox (const(FT_Outline)*, FT_BBox*); 9759 FT_Error FT_Outline_Decompose (FT_Outline*, const(FT_Outline_Funcs)*, void*); 9760 } 9761 } else version(bindbc) { 9762 import bindbc.freetype; 9763 alias FT_KERNING_DEFAULT = FT_Kerning_Mode.FT_KERNING_DEFAULT; 9764 alias FT_KERNING_UNFITTED = FT_Kerning_Mode.FT_KERNING_UNFITTED; 9765 alias FT_KERNING_UNSCALED = FT_Kerning_Mode.FT_KERNING_UNSCALED; 9766 } else { 9767 import iv.freetype; 9768 } 9769 9770 struct FONSttFontImpl { 9771 FT_Face font; 9772 bool mono; // no aa? 9773 } 9774 9775 __gshared FT_Library ftLibrary; 9776 9777 int fons__tt_init (FONSContext context) nothrow @trusted @nogc { 9778 FT_Error ftError; 9779 //FONS_NOTUSED(context); 9780 ftError = FT_Init_FreeType(&ftLibrary); 9781 return (ftError == 0); 9782 } 9783 9784 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc { 9785 font.mono = v; 9786 } 9787 9788 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc { 9789 return font.mono; 9790 } 9791 9792 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc { 9793 FT_Error ftError; 9794 //font.font.userdata = stash; 9795 ftError = FT_New_Memory_Face(ftLibrary, cast(const(FT_Byte)*)data, dataSize, 0, &font.font); 9796 return ftError == 0; 9797 } 9798 9799 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc { 9800 *ascent = font.font.ascender; 9801 *descent = font.font.descender; 9802 *lineGap = font.font.height-(*ascent - *descent); 9803 } 9804 9805 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc { 9806 return size/(font.font.ascender-font.font.descender); 9807 } 9808 9809 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc { 9810 return FT_Get_Char_Index(font.font, codepoint); 9811 } 9812 9813 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 { 9814 FT_Error ftError; 9815 FT_GlyphSlot ftGlyph; 9816 //version(nanovg_ignore_mono) enum exflags = 0; 9817 //else version(nanovg_ft_mono) enum exflags = FT_LOAD_MONOCHROME; else enum exflags = 0; 9818 uint exflags = (font.mono ? FT_LOAD_MONOCHROME : 0); 9819 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))); 9820 if (ftError) return 0; 9821 ftError = FT_Load_Glyph(font.font, glyph, FT_LOAD_RENDER|/*FT_LOAD_NO_AUTOHINT|*/exflags); 9822 if (ftError) return 0; 9823 ftError = FT_Get_Advance(font.font, glyph, FT_LOAD_NO_SCALE|/*FT_LOAD_NO_AUTOHINT|*/exflags, cast(FT_Fixed*)advance); 9824 if (ftError) return 0; 9825 ftGlyph = font.font.glyph; 9826 *lsb = cast(int)ftGlyph.metrics.horiBearingX; 9827 *x0 = ftGlyph.bitmap_left; 9828 *x1 = *x0+ftGlyph.bitmap.width; 9829 *y0 = -ftGlyph.bitmap_top; 9830 *y1 = *y0+ftGlyph.bitmap.rows; 9831 return 1; 9832 } 9833 9834 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc { 9835 FT_GlyphSlot ftGlyph = font.font.glyph; 9836 //FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap 9837 //version(nanovg_ignore_mono) enum RenderAA = true; 9838 //else version(nanovg_ft_mono) enum RenderAA = false; 9839 //else enum RenderAA = true; 9840 if (font.mono) { 9841 auto src = ftGlyph.bitmap.buffer; 9842 auto dst = output; 9843 auto spt = ftGlyph.bitmap.pitch; 9844 if (spt < 0) spt = -spt; 9845 foreach (int y; 0..ftGlyph.bitmap.rows) { 9846 ubyte count = 0, b = 0; 9847 auto s = src; 9848 auto d = dst; 9849 foreach (int x; 0..ftGlyph.bitmap.width) { 9850 if (count-- == 0) { count = 7; b = *s++; } else b <<= 1; 9851 *d++ = (b&0x80 ? 255 : 0); 9852 } 9853 src += spt; 9854 dst += outStride; 9855 } 9856 } else { 9857 auto src = ftGlyph.bitmap.buffer; 9858 auto dst = output; 9859 auto spt = ftGlyph.bitmap.pitch; 9860 if (spt < 0) spt = -spt; 9861 foreach (int y; 0..ftGlyph.bitmap.rows) { 9862 import core.stdc.string : memcpy; 9863 //dst[0..ftGlyph.bitmap.width] = src[0..ftGlyph.bitmap.width]; 9864 memcpy(dst, src, ftGlyph.bitmap.width); 9865 src += spt; 9866 dst += outStride; 9867 } 9868 } 9869 } 9870 9871 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc { 9872 FT_Vector ftKerning; 9873 version(none) { 9874 // fitted kerning 9875 FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning); 9876 //{ import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d\n", glyph1, glyph2, ftKerning.x, ftKerning.y); } 9877 return cast(int)ftKerning.x; // round up and convert to integer 9878 } else { 9879 // unfitted kerning 9880 //FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNFITTED, &ftKerning); 9881 if (glyph1 <= 0 || glyph2 <= 0 || (font.font.face_flags&FT_FACE_FLAG_KERNING) == 0) return 0; 9882 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; 9883 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning)) return 0; 9884 version(none) { 9885 if (ftKerning.x) { 9886 //{ import core.stdc.stdio : printf; printf("has kerning: %u\n", cast(uint)(font.font.face_flags&FT_FACE_FLAG_KERNING)); } 9887 { import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d (size=%g)\n", glyph1, glyph2, ftKerning.x, ftKerning.y, cast(double)size); } 9888 } 9889 } 9890 version(none) { 9891 FT_Vector kk; 9892 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNSCALED, &kk)) assert(0, "wtf?!"); 9893 auto kadvfrac = FT_MulFix(kk.x, font.font.size.metrics.x_scale); // 1/64 of pixel 9894 //return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6); 9895 //assert(ftKerning.x == kadvfrac); 9896 if (ftKerning.x || kadvfrac) { 9897 { 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); } 9898 } 9899 //return cast(int)(kadvfrac+(kadvfrac < 0 ? -31 : 32)>>6); // round up and convert to integer 9900 return kadvfrac/64.0f; 9901 } 9902 //return cast(int)(ftKerning.x+(ftKerning.x < 0 ? -31 : 32)>>6); // round up and convert to integer 9903 return ftKerning.x/64.0f; 9904 } 9905 } 9906 9907 extern(C) nothrow @trusted @nogc { 9908 static struct OutlinerData { 9909 @disable this (this); 9910 void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); } 9911 NVGContext vg; 9912 NVGPathOutline.DataStore* ol; 9913 FT_BBox outlineBBox; 9914 nothrow @trusted @nogc: 9915 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; } 9916 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 9917 } 9918 9919 int fons__nvg__moveto_cb (const(FT_Vector)* to, void* user) { 9920 auto odata = cast(OutlinerData*)user; 9921 if (odata.vg !is null) odata.vg.moveTo(odata.transx(to.x), odata.transy(to.y)); 9922 if (odata.ol !is null) { 9923 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo); 9924 odata.ol.putArgs(odata.transx(to.x)); 9925 odata.ol.putArgs(odata.transy(to.y)); 9926 } 9927 return 0; 9928 } 9929 9930 int fons__nvg__lineto_cb (const(FT_Vector)* to, void* user) { 9931 auto odata = cast(OutlinerData*)user; 9932 if (odata.vg !is null) odata.vg.lineTo(odata.transx(to.x), odata.transy(to.y)); 9933 if (odata.ol !is null) { 9934 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo); 9935 odata.ol.putArgs(odata.transx(to.x)); 9936 odata.ol.putArgs(odata.transy(to.y)); 9937 } 9938 return 0; 9939 } 9940 9941 int fons__nvg__quadto_cb (const(FT_Vector)* c1, const(FT_Vector)* to, void* user) { 9942 auto odata = cast(OutlinerData*)user; 9943 if (odata.vg !is null) odata.vg.quadTo(odata.transx(c1.x), odata.transy(c1.y), odata.transx(to.x), odata.transy(to.y)); 9944 if (odata.ol !is null) { 9945 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo); 9946 odata.ol.putArgs(odata.transx(c1.x)); 9947 odata.ol.putArgs(odata.transy(c1.y)); 9948 odata.ol.putArgs(odata.transx(to.x)); 9949 odata.ol.putArgs(odata.transy(to.y)); 9950 } 9951 return 0; 9952 } 9953 9954 int fons__nvg__cubicto_cb (const(FT_Vector)* c1, const(FT_Vector)* c2, const(FT_Vector)* to, void* user) { 9955 auto odata = cast(OutlinerData*)user; 9956 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)); 9957 if (odata.ol !is null) { 9958 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo); 9959 odata.ol.putArgs(odata.transx(c1.x)); 9960 odata.ol.putArgs(odata.transy(c1.y)); 9961 odata.ol.putArgs(odata.transx(c2.x)); 9962 odata.ol.putArgs(odata.transy(c2.y)); 9963 odata.ol.putArgs(odata.transx(to.x)); 9964 odata.ol.putArgs(odata.transy(to.y)); 9965 } 9966 return 0; 9967 } 9968 } 9969 9970 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc { 9971 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 9972 9973 FT_Outline_Funcs funcs; 9974 funcs.move_to = &fons__nvg__moveto_cb; 9975 funcs.line_to = &fons__nvg__lineto_cb; 9976 funcs.conic_to = &fons__nvg__quadto_cb; 9977 funcs.cubic_to = &fons__nvg__cubicto_cb; 9978 9979 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 9980 if (err) { bounds[] = 0; return false; } 9981 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; } 9982 9983 FT_Outline outline = font.font.glyph.outline; 9984 9985 OutlinerData odata; 9986 odata.vg = vg; 9987 FT_Outline_Get_CBox(&outline, &odata.outlineBBox); 9988 9989 err = FT_Outline_Decompose(&outline, &funcs, &odata); 9990 if (err) { bounds[] = 0; return false; } 9991 if (bounds.length > 0) bounds.ptr[0] = odata.outlineBBox.xMin; 9992 if (bounds.length > 1) bounds.ptr[1] = -odata.outlineBBox.yMax; 9993 if (bounds.length > 2) bounds.ptr[2] = odata.outlineBBox.xMax; 9994 if (bounds.length > 3) bounds.ptr[3] = -odata.outlineBBox.yMin; 9995 return true; 9996 } 9997 9998 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 9999 FT_Outline_Funcs funcs; 10000 funcs.move_to = &fons__nvg__moveto_cb; 10001 funcs.line_to = &fons__nvg__lineto_cb; 10002 funcs.conic_to = &fons__nvg__quadto_cb; 10003 funcs.cubic_to = &fons__nvg__cubicto_cb; 10004 10005 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 10006 if (err) return false; 10007 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) return false; 10008 10009 FT_Outline outline = font.font.glyph.outline; 10010 10011 OutlinerData odata; 10012 odata.ol = ol; 10013 FT_Outline_Get_CBox(&outline, &odata.outlineBBox); 10014 10015 err = FT_Outline_Decompose(&outline, &funcs, &odata); 10016 if (err) return false; 10017 ol.bounds.ptr[0] = odata.outlineBBox.xMin; 10018 ol.bounds.ptr[1] = -odata.outlineBBox.yMax; 10019 ol.bounds.ptr[2] = odata.outlineBBox.xMax; 10020 ol.bounds.ptr[3] = -odata.outlineBBox.yMin; 10021 return true; 10022 } 10023 10024 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc { 10025 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10026 10027 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 10028 if (err) return false; 10029 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; } 10030 10031 FT_Outline outline = font.font.glyph.outline; 10032 FT_BBox outlineBBox; 10033 FT_Outline_Get_CBox(&outline, &outlineBBox); 10034 if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin; 10035 if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax; 10036 if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax; 10037 if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin; 10038 return true; 10039 } 10040 10041 10042 } else { 10043 // ////////////////////////////////////////////////////////////////////////// // 10044 // sorry 10045 import std.traits : isFunctionPointer, isDelegate; 10046 private auto assumeNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) { 10047 import std.traits; 10048 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_; 10049 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 10050 } 10051 10052 private auto forceNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) { 10053 try { 10054 return assumeNoThrowNoGC(t)(); 10055 } catch (Exception e) { 10056 assert(0, "OOPS!"); 10057 } 10058 } 10059 10060 struct FONSttFontImpl { 10061 stbtt_fontinfo font; 10062 bool mono; // no aa? 10063 } 10064 10065 int fons__tt_init (FONSContext context) nothrow @trusted @nogc { 10066 return 1; 10067 } 10068 10069 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc { 10070 font.mono = v; 10071 } 10072 10073 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc { 10074 return font.mono; 10075 } 10076 10077 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc { 10078 int stbError; 10079 font.font.userdata = context; 10080 forceNoThrowNoGC({ stbError = stbtt_InitFont(&font.font, data, 0); }); 10081 return stbError; 10082 } 10083 10084 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc { 10085 forceNoThrowNoGC({ stbtt_GetFontVMetrics(&font.font, ascent, descent, lineGap); }); 10086 } 10087 10088 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc { 10089 float res = void; 10090 forceNoThrowNoGC({ res = stbtt_ScaleForPixelHeight(&font.font, size); }); 10091 return res; 10092 } 10093 10094 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc { 10095 int res; 10096 forceNoThrowNoGC({ res = stbtt_FindGlyphIndex(&font.font, codepoint); }); 10097 return res; 10098 } 10099 10100 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 { 10101 forceNoThrowNoGC({ stbtt_GetGlyphHMetrics(&font.font, glyph, advance, lsb); }); 10102 forceNoThrowNoGC({ stbtt_GetGlyphBitmapBox(&font.font, glyph, scale, scale, x0, y0, x1, y1); }); 10103 return 1; 10104 } 10105 10106 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc { 10107 forceNoThrowNoGC({ stbtt_MakeGlyphBitmap(&font.font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); }); 10108 } 10109 10110 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc { 10111 // FUnits -> pixels: pointSize * resolution / (72 points per inch * units_per_em) 10112 // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#converting 10113 float res = void; 10114 forceNoThrowNoGC({ 10115 res = stbtt_GetGlyphKernAdvance(&font.font, glyph1, glyph2); 10116 res *= stbtt_ScaleForPixelHeight(&font.font, size); 10117 }); 10118 /* 10119 if (res != 0) { 10120 { 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)); } 10121 } 10122 */ 10123 //k8: dunno if this is right; i guess it isn't but... 10124 return res; 10125 } 10126 10127 // old arsd.ttf sux! ;-) 10128 static if (is(typeof(STBTT_vcubic))) { 10129 10130 static struct OutlinerData { 10131 @disable this (this); 10132 void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); } 10133 NVGPathOutline.DataStore* ol; 10134 nothrow @trusted @nogc: 10135 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; } 10136 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 10137 } 10138 10139 10140 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc { 10141 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10142 10143 bool okflag = false; 10144 10145 forceNoThrowNoGC({ 10146 int x0, y0, x1, y1; 10147 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10148 bounds[] = 0; 10149 return; 10150 } 10151 10152 if (bounds.length > 0) bounds.ptr[0] = x0; 10153 if (bounds.length > 1) bounds.ptr[1] = -y1; 10154 if (bounds.length > 2) bounds.ptr[2] = x1; 10155 if (bounds.length > 3) bounds.ptr[3] = -y0; 10156 10157 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 10158 10159 stbtt_vertex* verts = null; 10160 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); } 10161 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts); 10162 if (vcount < 1) return; 10163 10164 foreach (const ref vt; verts[0..vcount]) { 10165 switch (vt.type) { 10166 case STBTT_vmove: vg.moveTo(vt.x, transy(vt.y)); break; 10167 case STBTT_vline: vg.lineTo(vt.x, transy(vt.y)); break; 10168 case STBTT_vcurve: vg.quadTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy)); break; 10169 case STBTT_vcubic: vg.bezierTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy), vt.cx1, transy(vt.cy1)); break; 10170 default: 10171 } 10172 } 10173 10174 okflag = true; 10175 }); 10176 10177 return okflag; 10178 } 10179 10180 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 10181 bool okflag = false; 10182 10183 forceNoThrowNoGC({ 10184 int x0, y0, x1, y1; 10185 10186 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10187 ol.bounds[] = 0; 10188 return; 10189 } 10190 10191 ol.bounds.ptr[0] = x0; 10192 ol.bounds.ptr[1] = -y1; 10193 ol.bounds.ptr[2] = x1; 10194 ol.bounds.ptr[3] = -y0; 10195 10196 stbtt_vertex* verts = null; 10197 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); } 10198 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts); 10199 if (vcount < 1) return; 10200 10201 OutlinerData odata; 10202 odata.ol = ol; 10203 10204 foreach (const ref vt; verts[0..vcount]) { 10205 switch (vt.type) { 10206 case STBTT_vmove: 10207 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo); 10208 odata.ol.putArgs(odata.transx(vt.x)); 10209 odata.ol.putArgs(odata.transy(vt.y)); 10210 break; 10211 case STBTT_vline: 10212 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo); 10213 odata.ol.putArgs(odata.transx(vt.x)); 10214 odata.ol.putArgs(odata.transy(vt.y)); 10215 break; 10216 case STBTT_vcurve: 10217 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo); 10218 odata.ol.putArgs(odata.transx(vt.x)); 10219 odata.ol.putArgs(odata.transy(vt.y)); 10220 odata.ol.putArgs(odata.transx(vt.cx)); 10221 odata.ol.putArgs(odata.transy(vt.cy)); 10222 break; 10223 case STBTT_vcubic: 10224 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo); 10225 odata.ol.putArgs(odata.transx(vt.x)); 10226 odata.ol.putArgs(odata.transy(vt.y)); 10227 odata.ol.putArgs(odata.transx(vt.cx)); 10228 odata.ol.putArgs(odata.transy(vt.cy)); 10229 odata.ol.putArgs(odata.transx(vt.cx1)); 10230 odata.ol.putArgs(odata.transy(vt.cy1)); 10231 break; 10232 default: 10233 } 10234 } 10235 10236 okflag = true; 10237 }); 10238 10239 return okflag; 10240 } 10241 10242 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc { 10243 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10244 10245 bool okflag = false; 10246 10247 forceNoThrowNoGC({ 10248 int x0, y0, x1, y1; 10249 if (stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10250 if (bounds.length > 0) bounds.ptr[0] = x0; 10251 if (bounds.length > 1) bounds.ptr[1] = -y1; 10252 if (bounds.length > 2) bounds.ptr[2] = x1; 10253 if (bounds.length > 3) bounds.ptr[3] = -y0; 10254 okflag = true; 10255 } else { 10256 bounds[] = 0; 10257 } 10258 }); 10259 10260 return okflag; 10261 } 10262 10263 } // check for old stb_ttf 10264 10265 10266 } // version 10267 10268 10269 // ////////////////////////////////////////////////////////////////////////// // 10270 private: 10271 enum FONS_SCRATCH_BUF_SIZE = 64000; 10272 enum FONS_HASH_LUT_SIZE = 256; 10273 enum FONS_INIT_FONTS = 4; 10274 enum FONS_INIT_GLYPHS = 256; 10275 enum FONS_INIT_ATLAS_NODES = 256; 10276 enum FONS_VERTEX_COUNT = 1024; 10277 enum FONS_MAX_STATES = 20; 10278 enum FONS_MAX_FALLBACKS = 20; 10279 10280 10281 struct FONSglyph { 10282 uint codepoint; 10283 int index; 10284 int next; 10285 short size, blur; 10286 short x0, y0, x1, y1; 10287 short xadv, xoff, yoff; 10288 } 10289 10290 // refcounted 10291 struct FONSfontData { 10292 ubyte* data; 10293 int dataSize; 10294 bool freeData; 10295 int rc; 10296 10297 @disable this (this); // no copies 10298 void opAssign() (in auto ref FONSfontData a) { static assert(0, "no copies!"); } 10299 } 10300 10301 // won't set rc to 1 10302 FONSfontData* fons__createFontData (ubyte* adata, int asize, bool afree) nothrow @trusted @nogc { 10303 import core.stdc.stdlib : malloc; 10304 assert(adata !is null); 10305 assert(asize > 0); 10306 auto res = cast(FONSfontData*)malloc(FONSfontData.sizeof); 10307 if (res is null) assert(0, "FONS: out of memory"); 10308 res.data = adata; 10309 res.dataSize = asize; 10310 res.freeData = afree; 10311 res.rc = 0; 10312 return res; 10313 } 10314 10315 void incref (FONSfontData* fd) pure nothrow @trusted @nogc { 10316 pragma(inline, true); 10317 if (fd !is null) ++fd.rc; 10318 } 10319 10320 void decref (ref FONSfontData* fd) nothrow @trusted @nogc { 10321 if (fd !is null) { 10322 if (--fd.rc == 0) { 10323 import core.stdc.stdlib : free; 10324 if (fd.freeData && fd.data !is null) { 10325 free(fd.data); 10326 fd.data = null; 10327 } 10328 free(fd); 10329 fd = null; 10330 } 10331 } 10332 } 10333 10334 // as creating and destroying fonts is a rare operation, malloc some data 10335 struct FONSfont { 10336 FONSttFontImpl font; 10337 char* name; // malloced, strz, always lowercase 10338 uint namelen; 10339 uint namehash; 10340 char* path; // malloced, strz 10341 FONSfontData* fdata; 10342 float ascender; 10343 float descender; 10344 float lineh; 10345 FONSglyph* glyphs; 10346 int cglyphs; 10347 int nglyphs; 10348 int[FONS_HASH_LUT_SIZE] lut; 10349 int[FONS_MAX_FALLBACKS] fallbacks; 10350 int nfallbacks; 10351 10352 @disable this (this); 10353 void opAssign() (in auto ref FONSfont a) { static assert(0, "no copies"); } 10354 10355 static uint djbhash (const(void)[] s) pure nothrow @safe @nogc { 10356 uint hash = 5381; 10357 foreach (ubyte b; cast(const(ubyte)[])s) { 10358 if (b >= 'A' && b <= 'Z') b += 32; // poor man's tolower 10359 hash = ((hash<<5)+hash)+b; 10360 } 10361 return hash; 10362 } 10363 10364 // except glyphs 10365 void freeMemory () nothrow @trusted @nogc { 10366 import core.stdc.stdlib : free; 10367 if (name !is null) { free(name); name = null; } 10368 namelen = namehash = 0; 10369 if (path !is null) { free(path); path = null; } 10370 fdata.decref(); 10371 } 10372 10373 // this also calcs name hash 10374 void setName (const(char)[] aname) nothrow @trusted @nogc { 10375 //{ import core.stdc.stdio; printf("setname: [%.*s]\n", cast(uint)aname.length, aname.ptr); } 10376 import core.stdc.stdlib : realloc; 10377 if (aname.length > int.max/32) assert(0, "FONS: invalid font name"); 10378 namelen = cast(uint)aname.length; 10379 name = cast(char*)realloc(name, namelen+1); 10380 if (name is null) assert(0, "FONS: out of memory"); 10381 if (aname.length) name[0..aname.length] = aname[]; 10382 name[namelen] = 0; 10383 // lowercase it 10384 foreach (ref char ch; name[0..namelen]) if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower 10385 namehash = djbhash(name[0..namelen]); 10386 //{ import core.stdc.stdio; printf(" [%s] [%.*s] [0x%08x]\n", name, namelen, name, namehash); } 10387 } 10388 10389 void setPath (const(char)[] apath) nothrow @trusted @nogc { 10390 import core.stdc.stdlib : realloc; 10391 if (apath.length > int.max/32) assert(0, "FONS: invalid font path"); 10392 path = cast(char*)realloc(path, apath.length+1); 10393 if (path is null) assert(0, "FONS: out of memory"); 10394 if (apath.length) path[0..apath.length] = apath[]; 10395 path[apath.length] = 0; 10396 } 10397 10398 // this won't check hash 10399 bool nameEqu (const(char)[] aname) const pure nothrow @trusted @nogc { 10400 //{ import core.stdc.stdio; printf("nameEqu: aname=[%.*s]; namelen=%u; aslen=%u\n", cast(uint)aname.length, aname.ptr, namelen, cast(uint)aname.length); } 10401 if (namelen != aname.length) return false; 10402 const(char)* ns = name; 10403 // name part 10404 foreach (char ch; aname) { 10405 if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower 10406 if (ch != *ns++) return false; 10407 } 10408 // done (length was checked earlier) 10409 return true; 10410 } 10411 10412 void clear () nothrow @trusted @nogc { 10413 import core.stdc.stdlib : free; 10414 import core.stdc.string : memset; 10415 if (glyphs !is null) free(glyphs); 10416 freeMemory(); 10417 memset(&this, 0, this.sizeof); 10418 } 10419 10420 FONSglyph* allocGlyph () nothrow @trusted @nogc { 10421 if (nglyphs+1 > cglyphs) { 10422 import core.stdc.stdlib : realloc; 10423 cglyphs = (cglyphs == 0 ? 8 : cglyphs*2); 10424 glyphs = cast(FONSglyph*)realloc(glyphs, FONSglyph.sizeof*cglyphs); 10425 if (glyphs is null) assert(0, "FontStash: out of memory"); 10426 } 10427 ++nglyphs; 10428 return &glyphs[nglyphs-1]; 10429 } 10430 } 10431 10432 void kill (ref FONSfont* font) nothrow @trusted @nogc { 10433 if (font !is null) { 10434 import core.stdc.stdlib : free; 10435 font.clear(); 10436 free(font); 10437 font = null; 10438 } 10439 } 10440 10441 10442 // ////////////////////////////////////////////////////////////////////////// // 10443 struct FONSstate { 10444 int font; 10445 NVGTextAlign talign; 10446 float size = 0; 10447 float blur = 0; 10448 float spacing = 0; 10449 } 10450 10451 10452 // ////////////////////////////////////////////////////////////////////////// // 10453 // atlas based on Skyline Bin Packer by Jukka Jylänki 10454 alias FONSAtlas = FONSatlasInternal*; 10455 10456 struct FONSatlasInternal { 10457 static struct Node { 10458 short x, y, width; 10459 } 10460 10461 int width, height; 10462 Node* nodes; 10463 int nnodes; 10464 int cnodes; 10465 10466 @disable this (this); 10467 void opAssign() (in auto ref FONSatlasInternal a) { static assert(0, "no copies"); } 10468 10469 nothrow @trusted @nogc: 10470 static FONSAtlas create (int w, int h, int nnodes) { 10471 import core.stdc.stdlib : malloc; 10472 import core.stdc.string : memset; 10473 10474 FONSAtlas atlas = cast(FONSAtlas)malloc(FONSatlasInternal.sizeof); 10475 if (atlas is null) assert(0, "FontStash: out of memory"); 10476 memset(atlas, 0, FONSatlasInternal.sizeof); 10477 10478 atlas.width = w; 10479 atlas.height = h; 10480 10481 // allocate space for skyline nodes 10482 atlas.nodes = cast(Node*)malloc(Node.sizeof*nnodes); 10483 if (atlas.nodes is null) assert(0, "FontStash: out of memory"); 10484 memset(atlas.nodes, 0, Node.sizeof*nnodes); 10485 atlas.nnodes = 0; 10486 atlas.cnodes = nnodes; 10487 10488 // init root node 10489 atlas.nodes[0].x = 0; 10490 atlas.nodes[0].y = 0; 10491 atlas.nodes[0].width = cast(short)w; 10492 ++atlas.nnodes; 10493 10494 return atlas; 10495 } 10496 10497 void clear () { 10498 import core.stdc.stdlib : free; 10499 import core.stdc.string : memset; 10500 10501 if (nodes !is null) free(nodes); 10502 memset(&this, 0, this.sizeof); 10503 } 10504 10505 void insertNode (int idx, int x, int y, int w) { 10506 if (nnodes+1 > cnodes) { 10507 import core.stdc.stdlib : realloc; 10508 cnodes = (cnodes == 0 ? 8 : cnodes*2); 10509 nodes = cast(Node*)realloc(nodes, Node.sizeof*cnodes); 10510 if (nodes is null) assert(0, "FontStash: out of memory"); 10511 } 10512 for (int i = nnodes; i > idx; --i) nodes[i] = nodes[i-1]; 10513 nodes[idx].x = cast(short)x; 10514 nodes[idx].y = cast(short)y; 10515 nodes[idx].width = cast(short)w; 10516 ++nnodes; 10517 } 10518 10519 void removeNode (int idx) { 10520 if (nnodes == 0) return; 10521 foreach (immutable int i; idx+1..nnodes) nodes[i-1] = nodes[i]; 10522 --nnodes; 10523 } 10524 10525 // insert node for empty space 10526 void expand (int w, int h) { 10527 if (w > width) insertNode(nnodes, width, 0, w-width); 10528 width = w; 10529 height = h; 10530 } 10531 10532 void reset (int w, int h) { 10533 width = w; 10534 height = h; 10535 nnodes = 0; 10536 // init root node 10537 nodes[0].x = 0; 10538 nodes[0].y = 0; 10539 nodes[0].width = cast(short)w; 10540 ++nnodes; 10541 } 10542 10543 void addSkylineLevel (int idx, int x, int y, int w, int h) { 10544 insertNode(idx, x, y+h, w); 10545 10546 // delete skyline segments that fall under the shadow of the new segment 10547 for (int i = idx+1; i < nnodes; ++i) { 10548 if (nodes[i].x < nodes[i-1].x+nodes[i-1].width) { 10549 int shrink = nodes[i-1].x+nodes[i-1].width-nodes[i].x; 10550 nodes[i].x += cast(short)shrink; 10551 nodes[i].width -= cast(short)shrink; 10552 if (nodes[i].width <= 0) { 10553 removeNode(i); 10554 --i; 10555 } else { 10556 break; 10557 } 10558 } else { 10559 break; 10560 } 10561 } 10562 10563 // Merge same height skyline segments that are next to each other 10564 for (int i = 0; i < nnodes-1; ++i) { 10565 if (nodes[i].y == nodes[i+1].y) { 10566 nodes[i].width += nodes[i+1].width; 10567 removeNode(i+1); 10568 --i; 10569 } 10570 } 10571 } 10572 10573 // checks if there is enough space at the location of skyline span 'i', 10574 // and return the max height of all skyline spans under that at that location, 10575 // (think tetris block being dropped at that position); or -1 if no space found 10576 int rectFits (int i, int w, int h) { 10577 int x = nodes[i].x; 10578 int y = nodes[i].y; 10579 if (x+w > width) return -1; 10580 int spaceLeft = w; 10581 while (spaceLeft > 0) { 10582 if (i == nnodes) return -1; 10583 y = nvg__max(y, nodes[i].y); 10584 if (y+h > height) return -1; 10585 spaceLeft -= nodes[i].width; 10586 ++i; 10587 } 10588 return y; 10589 } 10590 10591 bool addRect (int rw, int rh, int* rx, int* ry) { 10592 int besth = height, bestw = width, besti = -1; 10593 int bestx = -1, besty = -1; 10594 10595 // Bottom left fit heuristic. 10596 for (int i = 0; i < nnodes; ++i) { 10597 int y = rectFits(i, rw, rh); 10598 if (y != -1) { 10599 if (y+rh < besth || (y+rh == besth && nodes[i].width < bestw)) { 10600 besti = i; 10601 bestw = nodes[i].width; 10602 besth = y+rh; 10603 bestx = nodes[i].x; 10604 besty = y; 10605 } 10606 } 10607 } 10608 10609 if (besti == -1) return false; 10610 10611 // perform the actual packing 10612 addSkylineLevel(besti, bestx, besty, rw, rh); 10613 10614 *rx = bestx; 10615 *ry = besty; 10616 10617 return true; 10618 } 10619 } 10620 10621 void kill (ref FONSAtlas atlas) nothrow @trusted @nogc { 10622 if (atlas !is null) { 10623 import core.stdc.stdlib : free; 10624 atlas.clear(); 10625 free(atlas); 10626 atlas = null; 10627 } 10628 } 10629 10630 10631 // ////////////////////////////////////////////////////////////////////////// // 10632 /// FontStash context (internal definition). Don't use it derectly, it was made public only to generate documentation. 10633 /// Group: font_stash 10634 public struct FONScontextInternal { 10635 private: 10636 FONSParams params; 10637 float itw, ith; 10638 ubyte* texData; 10639 int[4] dirtyRect; 10640 FONSfont** fonts; // actually, a simple hash table; can't grow yet 10641 int cfonts; // allocated 10642 int nfonts; // used (so we can track hash table stats) 10643 int* hashidx; // [hsize] items; holds indicies in [fonts] array 10644 int hused, hsize;// used items and total items in [hashidx] 10645 FONSAtlas atlas; 10646 ubyte* scratch; 10647 int nscratch; 10648 FONSstate[FONS_MAX_STATES] states; 10649 int nstates; 10650 10651 void delegate (FONSError error, int val) nothrow @trusted @nogc handleError; 10652 10653 @disable this (this); 10654 void opAssign() (in auto ref FONScontextInternal ctx) { static assert(0, "FONS copying is not allowed"); } 10655 10656 private: 10657 static bool strequci (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 10658 if (s0.length != s1.length) return false; 10659 const(char)* sp0 = s0.ptr; 10660 const(char)* sp1 = s1.ptr; 10661 foreach (immutable _; 0..s0.length) { 10662 char c0 = *sp0++; 10663 char c1 = *sp1++; 10664 if (c0 != c1) { 10665 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man tolower 10666 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man tolower 10667 if (c0 != c1) return false; 10668 } 10669 } 10670 return true; 10671 } 10672 10673 inout(FONSstate)* getState () inout pure nothrow @trusted @nogc { 10674 pragma(inline, true); 10675 return cast(inout)(&states[(nstates > 0 ? nstates-1 : 0)]); 10676 } 10677 10678 // simple linear probing; returns [FONS_INVALID] if not found 10679 int findNameInHash (const(char)[] name) const pure nothrow @trusted @nogc { 10680 if (nfonts == 0) return FONS_INVALID; 10681 auto nhash = FONSfont.djbhash(name); 10682 //{ import core.stdc.stdio; printf("findinhash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10683 auto res = nhash%hsize; 10684 // hash will never be 100% full, so this loop is safe 10685 for (;;) { 10686 int idx = hashidx[res]; 10687 if (idx == -1) break; 10688 auto font = fonts[idx]; 10689 if (font is null) assert(0, "FONS internal error"); 10690 if (font.namehash == nhash && font.nameEqu(name)) return idx; 10691 //{ import core.stdc.stdio; printf("findinhash chained: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10692 res = (res+1)%hsize; 10693 } 10694 return FONS_INVALID; 10695 } 10696 10697 // should be called $(B before) freeing `fonts[fidx]` 10698 void removeIndexFromHash (int fidx) nothrow @trusted @nogc { 10699 if (fidx < 0 || fidx >= nfonts) assert(0, "FONS internal error"); 10700 if (fonts[fidx] is null) assert(0, "FONS internal error"); 10701 if (hused != nfonts) assert(0, "FONS internal error"); 10702 auto nhash = fonts[fidx].namehash; 10703 auto res = nhash%hsize; 10704 // hash will never be 100% full, so this loop is safe 10705 for (;;) { 10706 int idx = hashidx[res]; 10707 if (idx == -1) assert(0, "FONS INTERNAL ERROR"); 10708 if (idx == fidx) { 10709 // i found her! copy rest here 10710 int nidx = (res+1)%hsize; 10711 for (;;) { 10712 if ((hashidx[res] = hashidx[nidx]) == -1) break; // so it will copy `-1` too 10713 res = nidx; 10714 nidx = (nidx+1)%hsize; 10715 } 10716 return; 10717 } 10718 res = (res+1)%hsize; 10719 } 10720 } 10721 10722 // add font with the given index to hash 10723 // prerequisite: font should not exists in hash 10724 void addIndexToHash (int idx) nothrow @trusted @nogc { 10725 if (idx < 0 || idx >= nfonts) assert(0, "FONS internal error"); 10726 if (fonts[idx] is null) assert(0, "FONS internal error"); 10727 import core.stdc.stdlib : realloc; 10728 auto nhash = fonts[idx].namehash; 10729 //{ import core.stdc.stdio; printf("addtohash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10730 // allocate new hash table if there was none 10731 if (hsize == 0) { 10732 enum InitSize = 256; 10733 auto newlist = cast(int*)realloc(null, InitSize*hashidx[0].sizeof); 10734 if (newlist is null) assert(0, "FONS: out of memory"); 10735 newlist[0..InitSize] = -1; 10736 hsize = InitSize; 10737 hused = 0; 10738 hashidx = newlist; 10739 } 10740 int res = cast(int)(nhash%hsize); 10741 // need to rehash? we want our hash table 50% full at max 10742 if (hashidx[res] != -1 && hused >= hsize/2) { 10743 uint nsz = hsize*2; 10744 if (nsz > 1024*1024) assert(0, "FONS: out of memory for fonts"); 10745 auto newlist = cast(int*)realloc(fonts, nsz*hashidx[0].sizeof); 10746 if (newlist is null) assert(0, "FONS: out of memory"); 10747 newlist[0..nsz] = -1; 10748 hused = 0; 10749 // rehash 10750 foreach (immutable fidx, FONSfont* ff; fonts[0..nfonts]) { 10751 if (ff is null) continue; 10752 // find slot for this font (guaranteed to have one) 10753 uint newslot = ff.namehash%nsz; 10754 while (newlist[newslot] != -1) newslot = (newslot+1)%nsz; 10755 newlist[newslot] = cast(int)fidx; 10756 ++hused; 10757 } 10758 hsize = nsz; 10759 hashidx = newlist; 10760 // we added everything, including [idx], so nothing more to do here 10761 } else { 10762 // find slot (guaranteed to have one) 10763 while (hashidx[res] != -1) res = (res+1)%hsize; 10764 // i found her! 10765 hashidx[res] = idx; 10766 ++hused; 10767 } 10768 } 10769 10770 void addWhiteRect (int w, int h) nothrow @trusted @nogc { 10771 int gx, gy; 10772 ubyte* dst; 10773 10774 if (!atlas.addRect(w, h, &gx, &gy)) return; 10775 10776 // Rasterize 10777 dst = &texData[gx+gy*params.width]; 10778 foreach (int y; 0..h) { 10779 foreach (int x; 0..w) { 10780 dst[x] = 0xff; 10781 } 10782 dst += params.width; 10783 } 10784 10785 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], gx); 10786 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], gy); 10787 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], gx+w); 10788 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], gy+h); 10789 } 10790 10791 // returns fid, not hash slot 10792 int allocFontAt (int atidx) nothrow @trusted @nogc { 10793 if (atidx >= 0 && atidx >= nfonts) assert(0, "internal NanoVega fontstash error"); 10794 10795 if (atidx < 0) { 10796 if (nfonts >= cfonts) { 10797 import core.stdc.stdlib : realloc; 10798 import core.stdc.string : memset; 10799 assert(nfonts == cfonts); 10800 int newsz = cfonts+64; 10801 if (newsz > 65535) assert(0, "FONS: too many fonts"); 10802 auto newlist = cast(FONSfont**)realloc(fonts, newsz*(FONSfont*).sizeof); 10803 if (newlist is null) assert(0, "FONS: out of memory"); 10804 memset(newlist+cfonts, 0, (newsz-cfonts)*(FONSfont*).sizeof); 10805 fonts = newlist; 10806 cfonts = newsz; 10807 } 10808 assert(nfonts < cfonts); 10809 } 10810 10811 FONSfont* font = cast(FONSfont*)malloc(FONSfont.sizeof); 10812 if (font is null) assert(0, "FONS: out of memory"); 10813 memset(font, 0, FONSfont.sizeof); 10814 10815 font.glyphs = cast(FONSglyph*)malloc(FONSglyph.sizeof*FONS_INIT_GLYPHS); 10816 if (font.glyphs is null) assert(0, "FONS: out of memory"); 10817 font.cglyphs = FONS_INIT_GLYPHS; 10818 font.nglyphs = 0; 10819 10820 if (atidx < 0) { 10821 fonts[nfonts] = font; 10822 return nfonts++; 10823 } else { 10824 fonts[atidx] = font; 10825 return atidx; 10826 } 10827 } 10828 10829 // 0: ooops 10830 int findGlyphForCP (FONSfont *font, dchar dch, FONSfont** renderfont) nothrow @trusted @nogc { 10831 if (renderfont !is null) *renderfont = font; 10832 if (font is null || font.fdata is null) return 0; 10833 auto g = fons__tt_getGlyphIndex(&font.font, cast(uint)dch); 10834 // try to find the glyph in fallback fonts 10835 if (g == 0) { 10836 foreach (immutable i; 0..font.nfallbacks) { 10837 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]]; 10838 if (fallbackFont !is null) { 10839 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, cast(uint)dch); 10840 if (fallbackIndex != 0) { 10841 if (renderfont !is null) *renderfont = fallbackFont; 10842 return g; 10843 } 10844 } 10845 } 10846 // no char, try to find replacement one 10847 if (dch != 0xFFFD) { 10848 g = fons__tt_getGlyphIndex(&font.font, 0xFFFD); 10849 if (g == 0) { 10850 foreach (immutable i; 0..font.nfallbacks) { 10851 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]]; 10852 if (fallbackFont !is null) { 10853 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, 0xFFFD); 10854 if (fallbackIndex != 0) { 10855 if (renderfont !is null) *renderfont = fallbackFont; 10856 return g; 10857 } 10858 } 10859 } 10860 } 10861 } 10862 } 10863 return g; 10864 } 10865 10866 void clear () nothrow @trusted @nogc { 10867 import core.stdc.stdlib : free; 10868 10869 if (params.renderDelete !is null) params.renderDelete(params.userPtr); 10870 foreach (immutable int i; 0..nfonts) fonts[i].kill(); 10871 10872 if (atlas !is null) atlas.kill(); 10873 if (fonts !is null) free(fonts); 10874 if (texData !is null) free(texData); 10875 if (scratch !is null) free(scratch); 10876 if (hashidx !is null) free(hashidx); 10877 } 10878 10879 // add font from another fontstash 10880 int addCookedFont (FONSfont* font) nothrow @trusted @nogc { 10881 if (font is null || font.fdata is null) return FONS_INVALID; 10882 font.fdata.incref(); 10883 auto res = addFontWithData(font.name[0..font.namelen], font.fdata, !font.font.mono); 10884 if (res == FONS_INVALID) font.fdata.decref(); // oops 10885 return res; 10886 } 10887 10888 // fdata refcount must be already increased; it won't be changed 10889 int addFontWithData (const(char)[] name, FONSfontData* fdata, bool defAA) nothrow @trusted @nogc { 10890 int i, ascent, descent, fh, lineGap; 10891 10892 if (name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID; 10893 if (name.length > 32767) return FONS_INVALID; 10894 if (fdata is null) return FONS_INVALID; 10895 10896 // find a font with the given name 10897 int newidx; 10898 FONSfont* oldfont = null; 10899 int oldidx = findNameInHash(name); 10900 if (oldidx != FONS_INVALID) { 10901 // replacement font 10902 oldfont = fonts[oldidx]; 10903 newidx = oldidx; 10904 } else { 10905 // new font, allocate new bucket 10906 newidx = -1; 10907 } 10908 10909 newidx = allocFontAt(newidx); 10910 FONSfont* font = fonts[newidx]; 10911 font.setName(name); 10912 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; // init hash lookup 10913 font.fdata = fdata; // set the font data (don't change reference count) 10914 fons__tt_setMono(&this, &font.font, !defAA); 10915 10916 // init font 10917 nscratch = 0; 10918 if (!fons__tt_loadFont(&this, &font.font, fdata.data, fdata.dataSize)) { 10919 // we promised to not free data on error, so just clear the data store (it will be freed by the caller) 10920 font.fdata = null; 10921 font.kill(); 10922 if (oldidx != FONS_INVALID) { 10923 assert(oldidx == newidx); 10924 fonts[oldidx] = oldfont; 10925 } else { 10926 assert(newidx == nfonts-1); 10927 fonts[newidx] = null; 10928 --nfonts; 10929 } 10930 return FONS_INVALID; 10931 } else { 10932 // free old font data, if any 10933 if (oldfont !is null) oldfont.kill(); 10934 } 10935 10936 // add font to name hash 10937 if (oldidx == FONS_INVALID) addIndexToHash(newidx); 10938 10939 // store normalized line height 10940 // the real line height is got by multiplying the lineh by font size 10941 fons__tt_getFontVMetrics(&font.font, &ascent, &descent, &lineGap); 10942 fh = ascent-descent; 10943 font.ascender = cast(float)ascent/cast(float)fh; 10944 font.descender = cast(float)descent/cast(float)fh; 10945 font.lineh = cast(float)(fh+lineGap)/cast(float)fh; 10946 10947 //{ import core.stdc.stdio; printf("created font [%.*s] (idx=%d)...\n", cast(uint)name.length, name.ptr, idx); } 10948 return newidx; 10949 } 10950 10951 // isize: size*10 10952 float getVertAlign (FONSfont* font, NVGTextAlign talign, short isize) pure nothrow @trusted @nogc { 10953 if (params.isZeroTopLeft) { 10954 final switch (talign.vertical) { 10955 case NVGTextAlign.V.Top: return font.ascender*cast(float)isize/10.0f; 10956 case NVGTextAlign.V.Middle: return (font.ascender+font.descender)/2.0f*cast(float)isize/10.0f; 10957 case NVGTextAlign.V.Baseline: return 0.0f; 10958 case NVGTextAlign.V.Bottom: return font.descender*cast(float)isize/10.0f; 10959 } 10960 } else { 10961 final switch (talign.vertical) { 10962 case NVGTextAlign.V.Top: return -font.ascender*cast(float)isize/10.0f; 10963 case NVGTextAlign.V.Middle: return -(font.ascender+font.descender)/2.0f*cast(float)isize/10.0f; 10964 case NVGTextAlign.V.Baseline: return 0.0f; 10965 case NVGTextAlign.V.Bottom: return -font.descender*cast(float)isize/10.0f; 10966 } 10967 } 10968 assert(0); 10969 } 10970 10971 public: 10972 /** Create new FontStash context. It can be destroyed with `fs.kill()` later. 10973 * 10974 * Note that if you don't plan to rasterize glyphs (i.e. you will use created 10975 * FontStash only to measure text), you can simply pass `FONSParams.init`). 10976 */ 10977 static FONSContext create() (in auto ref FONSParams params) nothrow @trusted @nogc { 10978 import core.stdc.string : memcpy; 10979 10980 FONSContext stash = null; 10981 10982 // allocate memory for the font stash 10983 stash = cast(FONSContext)malloc(FONScontextInternal.sizeof); 10984 if (stash is null) goto error; 10985 memset(stash, 0, FONScontextInternal.sizeof); 10986 10987 memcpy(&stash.params, ¶ms, params.sizeof); 10988 if (stash.params.width < 1) stash.params.width = 32; 10989 if (stash.params.height < 1) stash.params.height = 32; 10990 10991 // allocate scratch buffer 10992 stash.scratch = cast(ubyte*)malloc(FONS_SCRATCH_BUF_SIZE); 10993 if (stash.scratch is null) goto error; 10994 10995 // initialize implementation library 10996 if (!fons__tt_init(stash)) goto error; 10997 10998 if (stash.params.renderCreate !is null) { 10999 if (!stash.params.renderCreate(stash.params.userPtr, stash.params.width, stash.params.height)) goto error; 11000 } 11001 11002 stash.atlas = FONSAtlas.create(stash.params.width, stash.params.height, FONS_INIT_ATLAS_NODES); 11003 if (stash.atlas is null) goto error; 11004 11005 // don't allocate space for fonts: hash manager will do that for us later 11006 //stash.cfonts = 0; 11007 //stash.nfonts = 0; 11008 11009 // create texture for the cache 11010 stash.itw = 1.0f/stash.params.width; 11011 stash.ith = 1.0f/stash.params.height; 11012 stash.texData = cast(ubyte*)malloc(stash.params.width*stash.params.height); 11013 if (stash.texData is null) goto error; 11014 memset(stash.texData, 0, stash.params.width*stash.params.height); 11015 11016 stash.dirtyRect.ptr[0] = stash.params.width; 11017 stash.dirtyRect.ptr[1] = stash.params.height; 11018 stash.dirtyRect.ptr[2] = 0; 11019 stash.dirtyRect.ptr[3] = 0; 11020 11021 // add white rect at 0, 0 for debug drawing 11022 stash.addWhiteRect(2, 2); 11023 11024 stash.pushState(); 11025 stash.clearState(); 11026 11027 return stash; 11028 11029 error: 11030 stash.kill(); 11031 return null; 11032 } 11033 11034 public: 11035 /// Add fallback font (FontStash will try to find missing glyph in all fallback fonts before giving up). 11036 bool addFallbackFont (int base, int fallback) nothrow @trusted @nogc { 11037 FONSfont* baseFont = fonts[base]; 11038 if (baseFont !is null && baseFont.nfallbacks < FONS_MAX_FALLBACKS) { 11039 baseFont.fallbacks.ptr[baseFont.nfallbacks++] = fallback; 11040 return true; 11041 } 11042 return false; 11043 } 11044 11045 @property void size (float size) nothrow @trusted @nogc { pragma(inline, true); getState.size = size; } /// Set current font size. 11046 @property float size () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.size; } /// Get current font size. 11047 11048 @property void spacing (float spacing) nothrow @trusted @nogc { pragma(inline, true); getState.spacing = spacing; } /// Set current letter spacing. 11049 @property float spacing () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.spacing; } /// Get current letter spacing. 11050 11051 @property void blur (float blur) nothrow @trusted @nogc { pragma(inline, true); getState.blur = blur; } /// Set current letter blur. 11052 @property float blur () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.blur; } /// Get current letter blur. 11053 11054 @property void textAlign (NVGTextAlign talign) nothrow @trusted @nogc { pragma(inline, true); getState.talign = talign; } /// Set current text align. 11055 @property NVGTextAlign textAlign () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.talign; } /// Get current text align. 11056 11057 @property void fontId (int font) nothrow @trusted @nogc { pragma(inline, true); getState.font = font; } /// Set current font id. 11058 @property int fontId () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.font; } /// Get current font id. 11059 11060 @property void fontId (const(char)[] name) nothrow @trusted @nogc { pragma(inline, true); getState.font = getFontByName(name); } /// Set current font using its name. 11061 11062 /// Check if FontStash has a font with the given name loaded. 11063 bool hasFont (const(char)[] name) const pure nothrow @trusted @nogc { pragma(inline, true); return (getFontByName(name) >= 0); } 11064 11065 /// Get AA for the current font, or for the specified font. 11066 bool getFontAA (int font=-1) nothrow @trusted @nogc { 11067 FONSstate* state = getState; 11068 if (font < 0) font = state.font; 11069 if (font < 0 || font >= nfonts) return false; 11070 FONSfont* f = fonts[font]; 11071 return (f !is null ? !f.font.mono : false); 11072 } 11073 11074 /// Push current state. Returns `false` if state stack overflowed. 11075 bool pushState () nothrow @trusted @nogc { 11076 if (nstates >= FONS_MAX_STATES) { 11077 if (handleError !is null) handleError(FONSError.StatesOverflow, 0); 11078 return false; 11079 } 11080 if (nstates > 0) { 11081 import core.stdc.string : memcpy; 11082 memcpy(&states[nstates], &states[nstates-1], FONSstate.sizeof); 11083 } 11084 ++nstates; 11085 return true; 11086 } 11087 11088 /// Pop current state. Returns `false` if state stack underflowed. 11089 bool popState () nothrow @trusted @nogc { 11090 if (nstates <= 1) { 11091 if (handleError !is null) handleError(FONSError.StatesUnderflow, 0); 11092 return false; 11093 } 11094 --nstates; 11095 return true; 11096 } 11097 11098 /// Clear current state (i.e. set it to some sane defaults). 11099 void clearState () nothrow @trusted @nogc { 11100 FONSstate* state = getState; 11101 state.size = 12.0f; 11102 state.font = 0; 11103 state.blur = 0; 11104 state.spacing = 0; 11105 state.talign.reset; 11106 } 11107 11108 private enum NoAlias = ":noaa"; 11109 11110 /** Add font to FontStash. 11111 * 11112 * Load scalable font from disk, and add it to FontStash. If you will try to load a font 11113 * with same name and path several times, FontStash will load it only once. Also, you can 11114 * load new disk font for any existing logical font. 11115 * 11116 * Params: 11117 * name = logical font name, that will be used to select this font later. 11118 * path = path to disk file with your font. 11119 * defAA = should FontStash use antialiased font rasterizer? 11120 * 11121 * Returns: 11122 * font id or [FONS_INVALID]. 11123 */ 11124 int addFont (const(char)[] name, const(char)[] path, bool defAA=false) nothrow @trusted { 11125 if (path.length == 0 || name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID; 11126 if (path.length > 32768) return FONS_INVALID; // arbitrary limit 11127 11128 // if font path ends with ":noaa", turn off antialiasing 11129 if (path.length >= NoAlias.length && strequci(path[$-NoAlias.length..$], NoAlias)) { 11130 path = path[0..$-NoAlias.length]; 11131 if (path.length == 0) return FONS_INVALID; 11132 defAA = false; 11133 } 11134 11135 // if font name ends with ":noaa", turn off antialiasing 11136 if (name.length > NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) { 11137 name = name[0..$-NoAlias.length]; 11138 defAA = false; 11139 } 11140 11141 // find a font with the given name 11142 int fidx = findNameInHash(name); 11143 //{ import core.stdc.stdio; printf("loading font '%.*s' [%s] (fidx=%d)...\n", cast(uint)path.length, path.ptr, fontnamebuf.ptr, fidx); } 11144 11145 int loadFontFile (const(char)[] path) { 11146 // check if existing font (if any) has the same path 11147 if (fidx >= 0) { 11148 import core.stdc.string : strlen; 11149 auto plen = (fonts[fidx].path !is null ? strlen(fonts[fidx].path) : 0); 11150 version(Posix) { 11151 //{ 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); } 11152 if (plen == path.length && fonts[fidx].path[0..plen] == path) { 11153 //{ import core.stdc.stdio; printf("*** font [%.*s] already loaded from [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)plen, path.ptr); } 11154 // i found her! 11155 return fidx; 11156 } 11157 } else { 11158 if (plen == path.length && strequci(fonts[fidx].path[0..plen], path)) { 11159 // i found her! 11160 return fidx; 11161 } 11162 } 11163 } 11164 version(Windows) { 11165 // special shitdows check: this will reject fontconfig font names (but still allow things like "c:myfont") 11166 foreach (immutable char ch; path[(path.length >= 2 && path[1] == ':' ? 2 : 0)..$]) if (ch == ':') return FONS_INVALID; 11167 } 11168 // either no such font, or different path 11169 //{ import core.stdc.stdio; printf("trying font [%.*s] from file [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)path.length, path.ptr); } 11170 int xres = FONS_INVALID; 11171 try { 11172 import core.stdc.stdlib : free, malloc; 11173 static if (NanoVegaHasIVVFS) { 11174 auto fl = VFile(path); 11175 auto dataSize = fl.size; 11176 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID; 11177 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize); 11178 if (data is null) assert(0, "out of memory in NanoVega fontstash"); 11179 scope(failure) free(data); // oops 11180 fl.rawReadExact(data[0..cast(uint)dataSize]); 11181 fl.close(); 11182 } else { 11183 import core.stdc.stdio : FILE, fopen, fclose, fread, ftell, fseek; 11184 import std.internal.cstring : tempCString; 11185 auto fl = fopen(path.tempCString, "rb"); 11186 if (fl is null) return FONS_INVALID; 11187 scope(exit) fclose(fl); 11188 if (fseek(fl, 0, 2/*SEEK_END*/) != 0) return FONS_INVALID; 11189 auto dataSize = ftell(fl); 11190 if (fseek(fl, 0, 0/*SEEK_SET*/) != 0) return FONS_INVALID; 11191 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID; 11192 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize); 11193 if (data is null) assert(0, "out of memory in NanoVega fontstash"); 11194 scope(failure) free(data); // oops 11195 ubyte* dptr = data; 11196 auto left = cast(uint)dataSize; 11197 while (left > 0) { 11198 auto rd = fread(dptr, 1, left, fl); 11199 if (rd == 0) { free(data); return FONS_INVALID; } // unexpected EOF or reading error, it doesn't matter 11200 dptr += rd; 11201 left -= rd; 11202 } 11203 } 11204 scope(failure) free(data); // oops 11205 // create font data 11206 FONSfontData* fdata = fons__createFontData(data, cast(int)dataSize, true); // free data 11207 fdata.incref(); 11208 xres = addFontWithData(name, fdata, defAA); 11209 if (xres == FONS_INVALID) { 11210 fdata.decref(); // this will free [data] and [fdata] 11211 } else { 11212 // remember path 11213 fonts[xres].setPath(path); 11214 } 11215 } catch (Exception e) { 11216 // oops; sorry 11217 } 11218 return xres; 11219 } 11220 11221 // first try direct path 11222 auto res = loadFontFile(path); 11223 // if loading failed, try fontconfig (if fontconfig is available) 11224 static if (NanoVegaHasFontConfig) { 11225 if (res == FONS_INVALID && fontconfigAvailable) { 11226 // idiotic fontconfig NEVER fails; let's skip it if `path` looks like a path 11227 bool ok = true; 11228 if (path.length > 4 && (path[$-4..$] == ".ttf" || path[$-4..$] == ".ttc")) ok = false; 11229 if (ok) { foreach (immutable char ch; path) if (ch == '/') { ok = false; break; } } 11230 if (ok) { 11231 import std.internal.cstring : tempCString; 11232 FcPattern* pat = FcNameParse(path.tempCString); 11233 if (pat !is null) { 11234 scope(exit) FcPatternDestroy(pat); 11235 if (FcConfigSubstitute(null, pat, FcMatchPattern)) { 11236 FcDefaultSubstitute(pat); 11237 // find the font 11238 FcResult result; 11239 FcPattern* font = FcFontMatch(null, pat, &result); 11240 if (font !is null) { 11241 scope(exit) FcPatternDestroy(font); 11242 char* file = null; 11243 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) { 11244 if (file !is null && file[0]) { 11245 import core.stdc.string : strlen; 11246 res = loadFontFile(file[0..strlen(file)]); 11247 } 11248 } 11249 } 11250 } 11251 } 11252 } 11253 } 11254 } 11255 return res; 11256 } 11257 11258 /** Add font to FontStash, using data from memory. 11259 * 11260 * And already loaded font to FontStash. You can replace existing logical fonts. 11261 * But note that you can't remove logical font by passing "empty" data. 11262 * 11263 * $(WARNING If [FONS_INVALID] returned, `data` won't be freed even if `freeData` is `true`.) 11264 * 11265 * Params: 11266 * name = logical font name, that will be used to select this font later. 11267 * data = font data. 11268 * dataSize = font data size. 11269 * freeData = should FontStash take ownership of the font data? 11270 * defAA = should FontStash use antialiased font rasterizer? 11271 * 11272 * Returns: 11273 * font id or [FONS_INVALID]. 11274 */ 11275 int addFontMem (const(char)[] name, ubyte* data, int dataSize, bool freeData, bool defAA=false) nothrow @trusted @nogc { 11276 if (data is null || dataSize < 16) return FONS_INVALID; 11277 FONSfontData* fdata = fons__createFontData(data, dataSize, freeData); 11278 fdata.incref(); 11279 auto res = addFontWithData(name, fdata, defAA); 11280 if (res == FONS_INVALID) { 11281 // we promised to not free data on error 11282 fdata.freeData = false; 11283 fdata.decref(); // this will free [fdata] 11284 } 11285 return res; 11286 } 11287 11288 /** Add fonts from another FontStash. 11289 * 11290 * This is more effective (and faster) than reloading fonts, because internally font data 11291 * is reference counted. 11292 */ 11293 void addFontsFrom (FONSContext source) nothrow @trusted @nogc { 11294 if (source is null) return; 11295 foreach (FONSfont* font; source.fonts[0..source.nfonts]) { 11296 if (font !is null) { 11297 auto newidx = addCookedFont(font); 11298 FONSfont* newfont = fonts[newidx]; 11299 assert(newfont !is null); 11300 assert(newfont.path is null); 11301 // copy path 11302 if (font.path !is null && font.path[0]) { 11303 import core.stdc.stdlib : malloc; 11304 import core.stdc.string : strcpy, strlen; 11305 newfont.path = cast(char*)malloc(strlen(font.path)+1); 11306 if (newfont.path is null) assert(0, "FONS: out of memory"); 11307 strcpy(newfont.path, font.path); 11308 } 11309 } 11310 } 11311 } 11312 11313 /// Returns logical font name corresponding to the given font id, or `null`. 11314 /// $(WARNING Copy returned name, as name buffer can be invalidated by next FontStash API call!) 11315 const(char)[] getNameById (int idx) const pure nothrow @trusted @nogc { 11316 if (idx < 0 || idx >= nfonts || fonts[idx] is null) return null; 11317 return fonts[idx].name[0..fonts[idx].namelen]; 11318 } 11319 11320 /// Returns font id corresponding to the given logical font name, or [FONS_INVALID]. 11321 int getFontByName (const(char)[] name) const pure nothrow @trusted @nogc { 11322 //{ import core.stdc.stdio; printf("fonsGetFontByName: [%.*s]\n", cast(uint)name.length, name.ptr); } 11323 // remove ":noaa" suffix 11324 if (name.length >= NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) { 11325 name = name[0..$-NoAlias.length]; 11326 } 11327 if (name.length == 0) return FONS_INVALID; 11328 return findNameInHash(name); 11329 } 11330 11331 /** Measures the specified text string. Parameter bounds should be a float[4], 11332 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax] 11333 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn). 11334 */ 11335 float getTextBounds(T) (float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc if (isAnyCharType!T) { 11336 FONSstate* state = getState; 11337 uint codepoint; 11338 uint utf8state = 0; 11339 FONSQuad q; 11340 FONSglyph* glyph = null; 11341 int prevGlyphIndex = -1; 11342 short isize = cast(short)(state.size*10.0f); 11343 short iblur = cast(short)state.blur; 11344 FONSfont* font; 11345 11346 if (state.font < 0 || state.font >= nfonts) return 0; 11347 font = fonts[state.font]; 11348 if (font is null || font.fdata is null) return 0; 11349 11350 float scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 11351 11352 // Align vertically. 11353 y += getVertAlign(font, state.talign, isize); 11354 11355 float minx = x, maxx = x; 11356 float miny = y, maxy = y; 11357 float startx = x; 11358 11359 foreach (T ch; str) { 11360 static if (T.sizeof == 1) { 11361 //if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue; 11362 mixin(DecUtfMixin!("utf8state", "codepoint", "(cast(ubyte)ch)")); 11363 if (utf8state) continue; 11364 } else { 11365 static if (T.sizeof == 4) { 11366 if (ch > dchar.max) ch = 0xFFFD; 11367 } 11368 codepoint = cast(uint)ch; 11369 } 11370 glyph = getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional); 11371 if (glyph !is null) { 11372 getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q); 11373 if (q.x0 < minx) minx = q.x0; 11374 if (q.x1 > maxx) maxx = q.x1; 11375 if (params.isZeroTopLeft) { 11376 if (q.y0 < miny) miny = q.y0; 11377 if (q.y1 > maxy) maxy = q.y1; 11378 } else { 11379 if (q.y1 < miny) miny = q.y1; 11380 if (q.y0 > maxy) maxy = q.y0; 11381 } 11382 prevGlyphIndex = glyph.index; 11383 } else { 11384 //{ import core.stdc.stdio; printf("NO GLYPH FOR 0x%04x\n", cast(uint)codepoint); } 11385 prevGlyphIndex = -1; 11386 } 11387 } 11388 11389 float advance = x-startx; 11390 //{ import core.stdc.stdio; printf("***: x=%g; startx=%g; advance=%g\n", cast(double)x, cast(double)startx, cast(double)advance); } 11391 11392 // Align horizontally 11393 if (state.talign.left) { 11394 // empty 11395 } else if (state.talign.right) { 11396 minx -= advance; 11397 maxx -= advance; 11398 } else if (state.talign.center) { 11399 minx -= advance*0.5f; 11400 maxx -= advance*0.5f; 11401 } 11402 11403 if (bounds.length) { 11404 if (bounds.length > 0) bounds.ptr[0] = minx; 11405 if (bounds.length > 1) bounds.ptr[1] = miny; 11406 if (bounds.length > 2) bounds.ptr[2] = maxx; 11407 if (bounds.length > 3) bounds.ptr[3] = maxy; 11408 } 11409 11410 return advance; 11411 } 11412 11413 /// Returns various font metrics. Any argument can be `null` if you aren't interested in its value. 11414 void getVertMetrics (float* ascender, float* descender, float* lineh) nothrow @trusted @nogc { 11415 FONSstate* state = getState; 11416 if (state.font < 0 || state.font >= nfonts) { 11417 if (ascender !is null) *ascender = 0; 11418 if (descender !is null) *descender = 0; 11419 if (lineh !is null) *lineh = 0; 11420 } else { 11421 FONSfont* font = fonts[state.font]; 11422 if (font is null || font.fdata is null) { 11423 if (ascender !is null) *ascender = 0; 11424 if (descender !is null) *descender = 0; 11425 if (lineh !is null) *lineh = 0; 11426 } else { 11427 short isize = cast(short)(state.size*10.0f); 11428 if (ascender !is null) *ascender = font.ascender*isize/10.0f; 11429 if (descender !is null) *descender = font.descender*isize/10.0f; 11430 if (lineh !is null) *lineh = font.lineh*isize/10.0f; 11431 } 11432 } 11433 } 11434 11435 /// Returns line bounds. Any argument can be `null` if you aren't interested in its value. 11436 void getLineBounds (float y, float* minyp, float* maxyp) nothrow @trusted @nogc { 11437 FONSfont* font; 11438 FONSstate* state = getState; 11439 short isize; 11440 11441 if (minyp !is null) *minyp = 0; 11442 if (maxyp !is null) *maxyp = 0; 11443 11444 if (state.font < 0 || state.font >= nfonts) return; 11445 font = fonts[state.font]; 11446 isize = cast(short)(state.size*10.0f); 11447 if (font is null || font.fdata is null) return; 11448 11449 y += getVertAlign(font, state.talign, isize); 11450 11451 if (params.isZeroTopLeft) { 11452 immutable float miny = y-font.ascender*cast(float)isize/10.0f; 11453 immutable float maxy = miny+font.lineh*isize/10.0f; 11454 if (minyp !is null) *minyp = miny; 11455 if (maxyp !is null) *maxyp = maxy; 11456 } else { 11457 immutable float maxy = y+font.descender*cast(float)isize/10.0f; 11458 immutable float miny = maxy-font.lineh*isize/10.0f; 11459 if (minyp !is null) *minyp = miny; 11460 if (maxyp !is null) *maxyp = maxy; 11461 } 11462 } 11463 11464 /// Returns font line height. 11465 float fontHeight () nothrow @trusted @nogc { 11466 float res = void; 11467 getVertMetrics(null, null, &res); 11468 return res; 11469 } 11470 11471 /// Returns font ascender (positive). 11472 float fontAscender () nothrow @trusted @nogc { 11473 float res = void; 11474 getVertMetrics(&res, null, null); 11475 return res; 11476 } 11477 11478 /// Returns font descender (negative). 11479 float fontDescender () nothrow @trusted @nogc { 11480 float res = void; 11481 getVertMetrics(null, &res, null); 11482 return res; 11483 } 11484 11485 //TODO: document this 11486 const(ubyte)* getTextureData (int* width, int* height) nothrow @trusted @nogc { 11487 if (width !is null) *width = params.width; 11488 if (height !is null) *height = params.height; 11489 return texData; 11490 } 11491 11492 //TODO: document this 11493 bool validateTexture (int* dirty) nothrow @trusted @nogc { 11494 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) { 11495 dirty[0] = dirtyRect.ptr[0]; 11496 dirty[1] = dirtyRect.ptr[1]; 11497 dirty[2] = dirtyRect.ptr[2]; 11498 dirty[3] = dirtyRect.ptr[3]; 11499 // reset dirty rect 11500 dirtyRect.ptr[0] = params.width; 11501 dirtyRect.ptr[1] = params.height; 11502 dirtyRect.ptr[2] = 0; 11503 dirtyRect.ptr[3] = 0; 11504 return true; 11505 } 11506 return false; 11507 } 11508 11509 //TODO: document this 11510 void errorCallback (void delegate (FONSError error, int val) nothrow @trusted @nogc callback) nothrow @trusted @nogc { 11511 handleError = callback; 11512 } 11513 11514 //TODO: document this 11515 void getAtlasSize (int* width, int* height) const pure nothrow @trusted @nogc { 11516 if (width !is null) *width = params.width; 11517 if (height !is null) *height = params.height; 11518 } 11519 11520 //TODO: document this 11521 bool expandAtlas (int width, int height) nothrow @trusted @nogc { 11522 import core.stdc.stdlib : free; 11523 import core.stdc.string : memcpy, memset; 11524 11525 int maxy = 0; 11526 ubyte* data = null; 11527 11528 width = nvg__max(width, params.width); 11529 height = nvg__max(height, params.height); 11530 11531 if (width == params.width && height == params.height) return true; 11532 11533 // Flush pending glyphs. 11534 flush(); 11535 11536 // Create new texture 11537 if (params.renderResize !is null) { 11538 if (params.renderResize(params.userPtr, width, height) == 0) return false; 11539 } 11540 // Copy old texture data over. 11541 data = cast(ubyte*)malloc(width*height); 11542 if (data is null) return 0; 11543 foreach (immutable int i; 0..params.height) { 11544 ubyte* dst = &data[i*width]; 11545 ubyte* src = &texData[i*params.width]; 11546 memcpy(dst, src, params.width); 11547 if (width > params.width) memset(dst+params.width, 0, width-params.width); 11548 } 11549 if (height > params.height) memset(&data[params.height*width], 0, (height-params.height)*width); 11550 11551 free(texData); 11552 texData = data; 11553 11554 // Increase atlas size 11555 atlas.expand(width, height); 11556 11557 // Add existing data as dirty. 11558 foreach (immutable int i; 0..atlas.nnodes) maxy = nvg__max(maxy, atlas.nodes[i].y); 11559 dirtyRect.ptr[0] = 0; 11560 dirtyRect.ptr[1] = 0; 11561 dirtyRect.ptr[2] = params.width; 11562 dirtyRect.ptr[3] = maxy; 11563 11564 params.width = width; 11565 params.height = height; 11566 itw = 1.0f/params.width; 11567 ith = 1.0f/params.height; 11568 11569 return true; 11570 } 11571 11572 //TODO: document this 11573 bool resetAtlas (int width, int height) nothrow @trusted @nogc { 11574 import core.stdc.stdlib : realloc; 11575 import core.stdc.string : memcpy, memset; 11576 11577 // flush pending glyphs 11578 flush(); 11579 11580 // create new texture 11581 if (params.renderResize !is null) { 11582 if (params.renderResize(params.userPtr, width, height) == 0) return false; 11583 } 11584 11585 // reset atlas 11586 atlas.reset(width, height); 11587 11588 // clear texture data 11589 texData = cast(ubyte*)realloc(texData, width*height); 11590 if (texData is null) assert(0, "FONS: out of memory"); 11591 memset(texData, 0, width*height); 11592 11593 // reset dirty rect 11594 dirtyRect.ptr[0] = width; 11595 dirtyRect.ptr[1] = height; 11596 dirtyRect.ptr[2] = 0; 11597 dirtyRect.ptr[3] = 0; 11598 11599 // Reset cached glyphs 11600 foreach (FONSfont* font; fonts[0..nfonts]) { 11601 if (font !is null) { 11602 font.nglyphs = 0; 11603 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; 11604 } 11605 } 11606 11607 params.width = width; 11608 params.height = height; 11609 itw = 1.0f/params.width; 11610 ith = 1.0f/params.height; 11611 11612 // Add white rect at 0, 0 for debug drawing. 11613 addWhiteRect(2, 2); 11614 11615 return true; 11616 } 11617 11618 //TODO: document this 11619 bool getPathBounds (dchar dch, float[] bounds) nothrow @trusted @nogc { 11620 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 11621 static if (is(typeof(&fons__nvg__bounds))) { 11622 FONSstate* state = getState; 11623 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; } 11624 FONSfont* font; 11625 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11626 if (g == 0) { bounds[] = 0; return false; } 11627 assert(font !is null); 11628 return fons__nvg__bounds(&font.font, g, bounds); 11629 } else { 11630 bounds[] = 0; 11631 return false; 11632 } 11633 } 11634 11635 //TODO: document this 11636 bool toPath() (NVGContext vg, dchar dch, float[] bounds=null) nothrow @trusted @nogc { 11637 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 11638 static if (is(typeof(&fons__nvg__toPath))) { 11639 if (vg is null) { bounds[] = 0; return false; } 11640 FONSstate* state = getState; 11641 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; } 11642 FONSfont* font; 11643 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11644 if (g == 0) { bounds[] = 0; return false; } 11645 assert(font !is null); 11646 return fons__nvg__toPath(vg, &font.font, g, bounds); 11647 } else { 11648 bounds[] = 0; 11649 return false; 11650 } 11651 } 11652 11653 //TODO: document this 11654 bool toOutline (dchar dch, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 11655 if (ol is null) return false; 11656 static if (is(typeof(&fons__nvg__toOutline))) { 11657 FONSstate* state = getState; 11658 if (state.font < 0 || state.font >= nfonts) return false; 11659 FONSfont* font; 11660 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11661 if (g == 0) return false; 11662 assert(font !is null); 11663 return fons__nvg__toOutline(&font.font, g, ol); 11664 } else { 11665 return false; 11666 } 11667 } 11668 11669 //TODO: document this 11670 FONSglyph* getGlyph (FONSfont* font, uint codepoint, short isize, short iblur, FONSBitmapFlag bitmapOption) nothrow @trusted @nogc { 11671 static uint fons__hashint() (uint a) pure nothrow @safe @nogc { 11672 pragma(inline, true); 11673 a += ~(a<<15); 11674 a ^= (a>>10); 11675 a += (a<<3); 11676 a ^= (a>>6); 11677 a += ~(a<<11); 11678 a ^= (a>>16); 11679 return a; 11680 } 11681 11682 // based on Exponential blur, Jani Huhtanen, 2006 11683 enum APREC = 16; 11684 enum ZPREC = 7; 11685 11686 static void fons__blurCols (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc { 11687 foreach (immutable int y; 0..h) { 11688 int z = 0; // force zero border 11689 foreach (int x; 1..w) { 11690 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC; 11691 dst[x] = cast(ubyte)(z>>ZPREC); 11692 } 11693 dst[w-1] = 0; // force zero border 11694 z = 0; 11695 for (int x = w-2; x >= 0; --x) { 11696 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC; 11697 dst[x] = cast(ubyte)(z>>ZPREC); 11698 } 11699 dst[0] = 0; // force zero border 11700 dst += dstStride; 11701 } 11702 } 11703 11704 static void fons__blurRows (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc { 11705 foreach (immutable int x; 0..w) { 11706 int z = 0; // force zero border 11707 for (int y = dstStride; y < h*dstStride; y += dstStride) { 11708 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC; 11709 dst[y] = cast(ubyte)(z>>ZPREC); 11710 } 11711 dst[(h-1)*dstStride] = 0; // force zero border 11712 z = 0; 11713 for (int y = (h-2)*dstStride; y >= 0; y -= dstStride) { 11714 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC; 11715 dst[y] = cast(ubyte)(z>>ZPREC); 11716 } 11717 dst[0] = 0; // force zero border 11718 ++dst; 11719 } 11720 } 11721 11722 static void fons__blur (ubyte* dst, int w, int h, int dstStride, int blur) nothrow @trusted @nogc { 11723 import std.math : expf = exp; 11724 if (blur < 1) return; 11725 // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity) 11726 immutable float sigma = cast(float)blur*0.57735f; // 1/sqrt(3) 11727 int alpha = cast(int)((1<<APREC)*(1.0f-expf(-2.3f/(sigma+1.0f)))); 11728 fons__blurRows(dst, w, h, dstStride, alpha); 11729 fons__blurCols(dst, w, h, dstStride, alpha); 11730 fons__blurRows(dst, w, h, dstStride, alpha); 11731 fons__blurCols(dst, w, h, dstStride, alpha); 11732 //fons__blurrows(dst, w, h, dstStride, alpha); 11733 //fons__blurcols(dst, w, h, dstStride, alpha); 11734 } 11735 11736 int advance, lsb, x0, y0, x1, y1, gx, gy; 11737 FONSglyph* glyph = null; 11738 float size = isize/10.0f; 11739 FONSfont* renderFont = font; 11740 11741 if (isize < 2) return null; 11742 if (iblur > 20) iblur = 20; 11743 int pad = iblur+2; 11744 11745 // Reset allocator. 11746 nscratch = 0; 11747 11748 // Find code point and size. 11749 uint h = fons__hashint(codepoint)&(FONS_HASH_LUT_SIZE-1); 11750 int i = font.lut.ptr[h]; 11751 while (i != -1) { 11752 //if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) return &font.glyphs[i]; 11753 if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) { 11754 glyph = &font.glyphs[i]; 11755 // Negative coordinate indicates there is no bitmap data created. 11756 if (bitmapOption == FONSBitmapFlag.Optional || (glyph.x0 >= 0 && glyph.y0 >= 0)) return glyph; 11757 // At this point, glyph exists but the bitmap data is not yet created. 11758 break; 11759 } 11760 i = font.glyphs[i].next; 11761 } 11762 11763 // Create a new glyph or rasterize bitmap data for a cached glyph. 11764 //scale = fons__tt_getPixelHeightScale(&font.font, size); 11765 int g = findGlyphForCP(font, cast(dchar)codepoint, &renderFont); 11766 // It is possible that we did not find a fallback glyph. 11767 // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph. 11768 11769 float scale = fons__tt_getPixelHeightScale(&renderFont.font, size); 11770 fons__tt_buildGlyphBitmap(&renderFont.font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1); 11771 int gw = x1-x0+pad*2; 11772 int gh = y1-y0+pad*2; 11773 11774 // Determines the spot to draw glyph in the atlas. 11775 if (bitmapOption == FONSBitmapFlag.Required) { 11776 // Find free spot for the rect in the atlas. 11777 bool added = atlas.addRect(gw, gh, &gx, &gy); 11778 if (!added && handleError !is null) { 11779 // Atlas is full, let the user to resize the atlas (or not), and try again. 11780 handleError(FONSError.AtlasFull, 0); 11781 added = atlas.addRect(gw, gh, &gx, &gy); 11782 } 11783 if (!added) return null; 11784 } else { 11785 // Negative coordinate indicates there is no bitmap data created. 11786 gx = -1; 11787 gy = -1; 11788 } 11789 11790 // Init glyph. 11791 if (glyph is null) { 11792 glyph = font.allocGlyph(); 11793 glyph.codepoint = codepoint; 11794 glyph.size = isize; 11795 glyph.blur = iblur; 11796 glyph.next = 0; 11797 11798 // Insert char to hash lookup. 11799 glyph.next = font.lut.ptr[h]; 11800 font.lut.ptr[h] = font.nglyphs-1; 11801 } 11802 glyph.index = g; 11803 glyph.x0 = cast(short)gx; 11804 glyph.y0 = cast(short)gy; 11805 glyph.x1 = cast(short)(glyph.x0+gw); 11806 glyph.y1 = cast(short)(glyph.y0+gh); 11807 glyph.xadv = cast(short)(scale*advance*10.0f); 11808 glyph.xoff = cast(short)(x0-pad); 11809 glyph.yoff = cast(short)(y0-pad); 11810 11811 if (bitmapOption == FONSBitmapFlag.Optional) return glyph; 11812 11813 // Rasterize 11814 ubyte* dst = &texData[(glyph.x0+pad)+(glyph.y0+pad)*params.width]; 11815 fons__tt_renderGlyphBitmap(&font.font, dst, gw-pad*2, gh-pad*2, params.width, scale, scale, g); 11816 11817 // Make sure there is one pixel empty border. 11818 dst = &texData[glyph.x0+glyph.y0*params.width]; 11819 foreach (immutable int y; 0..gh) { 11820 dst[y*params.width] = 0; 11821 dst[gw-1+y*params.width] = 0; 11822 } 11823 foreach (immutable int x; 0..gw) { 11824 dst[x] = 0; 11825 dst[x+(gh-1)*params.width] = 0; 11826 } 11827 11828 // Debug code to color the glyph background 11829 version(none) { 11830 foreach (immutable yy; 0..gh) { 11831 foreach (immutable xx; 0..gw) { 11832 int a = cast(int)dst[xx+yy*params.width]+42; 11833 if (a > 255) a = 255; 11834 dst[xx+yy*params.width] = cast(ubyte)a; 11835 } 11836 } 11837 } 11838 11839 // Blur 11840 if (iblur > 0) { 11841 nscratch = 0; 11842 ubyte* bdst = &texData[glyph.x0+glyph.y0*params.width]; 11843 fons__blur(bdst, gw, gh, params.width, iblur); 11844 } 11845 11846 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], glyph.x0); 11847 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], glyph.y0); 11848 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], glyph.x1); 11849 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], glyph.y1); 11850 11851 return glyph; 11852 } 11853 11854 //TODO: document this 11855 void getQuad (FONSfont* font, int prevGlyphIndex, FONSglyph* glyph, float size, float scale, float spacing, float* x, float* y, FONSQuad* q) nothrow @trusted @nogc { 11856 if (prevGlyphIndex >= 0) { 11857 immutable float adv = fons__tt_getGlyphKernAdvance(&font.font, size, prevGlyphIndex, glyph.index)/**scale*/; //k8: do we really need scale here? 11858 //if (adv != 0) { import core.stdc.stdio; printf("adv=%g (scale=%g; spacing=%g)\n", cast(double)adv, cast(double)scale, cast(double)spacing); } 11859 *x += cast(int)(adv+spacing /*+0.5f*/); //k8: for me, it looks better this way (with non-aa fonts) 11860 } 11861 11862 // Each glyph has 2px border to allow good interpolation, 11863 // one pixel to prevent leaking, and one to allow good interpolation for rendering. 11864 // Inset the texture region by one pixel for correct interpolation. 11865 immutable float xoff = cast(short)(glyph.xoff+1); 11866 immutable float yoff = cast(short)(glyph.yoff+1); 11867 immutable float x0 = cast(float)(glyph.x0+1); 11868 immutable float y0 = cast(float)(glyph.y0+1); 11869 immutable float x1 = cast(float)(glyph.x1-1); 11870 immutable float y1 = cast(float)(glyph.y1-1); 11871 11872 if (params.isZeroTopLeft) { 11873 immutable float rx = cast(float)cast(int)(*x+xoff); 11874 immutable float ry = cast(float)cast(int)(*y+yoff); 11875 11876 q.x0 = rx; 11877 q.y0 = ry; 11878 q.x1 = rx+x1-x0; 11879 q.y1 = ry+y1-y0; 11880 11881 q.s0 = x0*itw; 11882 q.t0 = y0*ith; 11883 q.s1 = x1*itw; 11884 q.t1 = y1*ith; 11885 } else { 11886 immutable float rx = cast(float)cast(int)(*x+xoff); 11887 immutable float ry = cast(float)cast(int)(*y-yoff); 11888 11889 q.x0 = rx; 11890 q.y0 = ry; 11891 q.x1 = rx+x1-x0; 11892 q.y1 = ry-y1+y0; 11893 11894 q.s0 = x0*itw; 11895 q.t0 = y0*ith; 11896 q.s1 = x1*itw; 11897 q.t1 = y1*ith; 11898 } 11899 11900 *x += cast(int)(glyph.xadv/10.0f+0.5f); 11901 } 11902 11903 void flush () nothrow @trusted @nogc { 11904 // flush texture 11905 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) { 11906 if (params.renderUpdate !is null) params.renderUpdate(params.userPtr, dirtyRect.ptr, texData); 11907 // reset dirty rect 11908 dirtyRect.ptr[0] = params.width; 11909 dirtyRect.ptr[1] = params.height; 11910 dirtyRect.ptr[2] = 0; 11911 dirtyRect.ptr[3] = 0; 11912 } 11913 } 11914 } 11915 11916 /// Free all resources used by the `stash`, and `stash` itself. 11917 /// Group: font_stash 11918 public void kill (ref FONSContext stash) nothrow @trusted @nogc { 11919 import core.stdc.stdlib : free; 11920 if (stash is null) return; 11921 stash.clear(); 11922 free(stash); 11923 stash = null; 11924 } 11925 11926 11927 // ////////////////////////////////////////////////////////////////////////// // 11928 void* fons__tmpalloc (usize size, void* up) nothrow @trusted @nogc { 11929 ubyte* ptr; 11930 FONSContext stash = cast(FONSContext)up; 11931 // 16-byte align the returned pointer 11932 size = (size+0xf)&~0xf; 11933 if (stash.nscratch+cast(int)size > FONS_SCRATCH_BUF_SIZE) { 11934 if (stash.handleError !is null) stash.handleError(FONSError.ScratchFull, stash.nscratch+cast(int)size); 11935 return null; 11936 } 11937 ptr = stash.scratch+stash.nscratch; 11938 stash.nscratch += cast(int)size; 11939 return ptr; 11940 } 11941 11942 void fons__tmpfree (void* ptr, void* up) nothrow @trusted @nogc { 11943 // empty 11944 } 11945 11946 11947 // ////////////////////////////////////////////////////////////////////////// // 11948 // Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de> 11949 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. 11950 11951 enum FONS_UTF8_ACCEPT = 0; 11952 enum FONS_UTF8_REJECT = 12; 11953 11954 static immutable ubyte[364] utf8d = [ 11955 // The first part of the table maps bytes to character classes that 11956 // to reduce the size of the transition table and create bitmasks. 11957 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, 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 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, 11962 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, 11963 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, 11964 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, 11965 11966 // The second part is a transition table that maps a combination 11967 // of a state of the automaton and a character class to a state. 11968 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 11969 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, 11970 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 11971 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 11972 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 11973 ]; 11974 11975 private enum DecUtfMixin(string state, string codep, string byte_) = 11976 `{ 11977 uint type_ = utf8d.ptr[`~byte_~`]; 11978 `~codep~` = (`~state~` != FONS_UTF8_ACCEPT ? (`~byte_~`&0x3fu)|(`~codep~`<<6) : (0xff>>type_)&`~byte_~`); 11979 if ((`~state~` = utf8d.ptr[256+`~state~`+type_]) == FONS_UTF8_REJECT) { 11980 `~state~` = FONS_UTF8_ACCEPT; 11981 `~codep~` = 0xFFFD; 11982 } 11983 }`; 11984 11985 /* 11986 uint fons__decutf8 (uint* state, uint* codep, uint byte_) { 11987 pragma(inline, true); 11988 uint type = utf8d.ptr[byte_]; 11989 *codep = (*state != FONS_UTF8_ACCEPT ? (byte_&0x3fu)|(*codep<<6) : (0xff>>type)&byte_); 11990 *state = utf8d.ptr[256 + *state+type]; 11991 return *state; 11992 } 11993 */ 11994 11995 11996 // ////////////////////////////////////////////////////////////////////////// // 11997 /// This iterator can be used to do text measurement. 11998 /// $(WARNING Don't add new fonts to stash while you are iterating, or you WILL get segfault!) 11999 /// Group: font_stash 12000 public struct FONSTextBoundsIterator { 12001 private: 12002 FONSContext stash; 12003 FONSstate state; 12004 uint codepoint = 0xFFFD; 12005 uint utf8state = 0; 12006 int prevGlyphIndex = -1; 12007 short isize, iblur; 12008 float scale = 0; 12009 FONSfont* font; 12010 float startx = 0, x = 0, y = 0; 12011 float minx = 0, miny = 0, maxx = 0, maxy = 0; 12012 12013 private: 12014 void clear () nothrow @trusted @nogc { 12015 import core.stdc.string : memset; 12016 memset(&this, 0, this.sizeof); 12017 this.prevGlyphIndex = -1; 12018 this.codepoint = 0xFFFD; 12019 } 12020 12021 public: 12022 /// Initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator. 12023 this (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { reset(astash, ax, ay); } 12024 12025 /// (Re)initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator. 12026 void reset (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { 12027 clear(); 12028 12029 if (astash is null || astash.nstates == 0) return; 12030 12031 stash = astash; 12032 state = *stash.getState; 12033 12034 if (state.font < 0 || state.font >= stash.nfonts) { clear(); return; } 12035 font = stash.fonts[state.font]; 12036 if (font is null || font.fdata is null) { clear(); return; } 12037 12038 x = ax; 12039 y = ay; 12040 isize = cast(short)(state.size*10.0f); 12041 iblur = cast(short)state.blur; 12042 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 12043 12044 // align vertically 12045 y += astash.getVertAlign(font, state.talign, isize); 12046 12047 minx = maxx = x; 12048 miny = maxy = y; 12049 startx = x; 12050 } 12051 12052 /// Can this iterator be used? 12053 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null); } 12054 12055 /// Put some text into iterator, calculate new values. 12056 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { 12057 enum DoCodePointMixin = q{ 12058 glyph = stash.getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional); 12059 if (glyph !is null) { 12060 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q); 12061 if (q.x0 < minx) minx = q.x0; 12062 if (q.x1 > maxx) maxx = q.x1; 12063 if (stash.params.isZeroTopLeft) { 12064 if (q.y0 < miny) miny = q.y0; 12065 if (q.y1 > maxy) maxy = q.y1; 12066 } else { 12067 if (q.y1 < miny) miny = q.y1; 12068 if (q.y0 > maxy) maxy = q.y0; 12069 } 12070 prevGlyphIndex = glyph.index; 12071 } else { 12072 prevGlyphIndex = -1; 12073 } 12074 }; 12075 12076 if (stash is null || str.length == 0) return; // alas 12077 12078 FONSQuad q; 12079 FONSglyph* glyph; 12080 12081 static if (is(T == char)) { 12082 foreach (char ch; str) { 12083 mixin(DecUtfMixin!("utf8state", "codepoint", "cast(ubyte)ch")); 12084 if (utf8state) continue; // full char is not collected yet 12085 mixin(DoCodePointMixin); 12086 } 12087 } else { 12088 if (utf8state) { 12089 utf8state = 0; 12090 codepoint = 0xFFFD; 12091 mixin(DoCodePointMixin); 12092 } 12093 foreach (T dch; str) { 12094 static if (is(T == dchar)) { 12095 if (dch > dchar.max) dch = 0xFFFD; 12096 } 12097 codepoint = cast(uint)dch; 12098 mixin(DoCodePointMixin); 12099 } 12100 } 12101 } 12102 12103 /// Returns current advance. 12104 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null ? x-startx : 0); } 12105 12106 /// Returns current text bounds. 12107 void getBounds (ref float[4] bounds) const pure nothrow @safe @nogc { 12108 if (stash is null) { bounds[] = 0; return; } 12109 float lminx = minx, lmaxx = maxx; 12110 // align horizontally 12111 if (state.talign.left) { 12112 // empty 12113 } else if (state.talign.right) { 12114 float ca = advance; 12115 lminx -= ca; 12116 lmaxx -= ca; 12117 } else if (state.talign.center) { 12118 float ca = advance*0.5f; 12119 lminx -= ca; 12120 lmaxx -= ca; 12121 } 12122 bounds[0] = lminx; 12123 bounds[1] = miny; 12124 bounds[2] = lmaxx; 12125 bounds[3] = maxy; 12126 } 12127 12128 /// Returns current horizontal text bounds. 12129 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc { 12130 if (stash !is null) { 12131 float lminx = minx, lmaxx = maxx; 12132 // align horizontally 12133 if (state.talign.left) { 12134 // empty 12135 } else if (state.talign.right) { 12136 float ca = advance; 12137 lminx -= ca; 12138 lmaxx -= ca; 12139 } else if (state.talign.center) { 12140 float ca = advance*0.5f; 12141 lminx -= ca; 12142 lmaxx -= ca; 12143 } 12144 xmin = lminx; 12145 xmax = lmaxx; 12146 } else { 12147 xmin = xmax = 0; 12148 } 12149 } 12150 12151 /// Returns current vertical text bounds. 12152 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc { 12153 pragma(inline, true); 12154 if (stash !is null) { 12155 ymin = miny; 12156 ymax = maxy; 12157 } else { 12158 ymin = ymax = 0; 12159 } 12160 } 12161 12162 /// Returns font line height. 12163 float lineHeight () nothrow @trusted @nogc { 12164 pragma(inline, true); 12165 return (stash !is null ? stash.fonts[state.font].lineh*cast(short)(state.size*10.0f)/10.0f : 0); 12166 } 12167 12168 /// Returns font ascender (positive). 12169 float ascender () nothrow @trusted @nogc { 12170 pragma(inline, true); 12171 return (stash !is null ? stash.fonts[state.font].ascender*cast(short)(state.size*10.0f)/10.0f : 0); 12172 } 12173 12174 /// Returns font descender (negative). 12175 float descender () nothrow @trusted @nogc { 12176 pragma(inline, true); 12177 return (stash !is null ? stash.fonts[state.font].descender*cast(short)(state.size*10.0f)/10.0f : 0); 12178 } 12179 } 12180 12181 12182 // ////////////////////////////////////////////////////////////////////////// // 12183 // backgl 12184 // ////////////////////////////////////////////////////////////////////////// // 12185 import core.stdc.stdlib : malloc, realloc, free; 12186 import core.stdc.string : memcpy, memset; 12187 12188 static if (__VERSION__ < 2076) { 12189 private auto DGNoThrowNoGC(T) (scope T t) /*if (isFunctionPointer!T || isDelegate!T)*/ { 12190 import std.traits; 12191 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_; 12192 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 12193 } 12194 } 12195 12196 12197 //import arsd.simpledisplay; 12198 version(nanovg_bindbc_opengl_bindings) { 12199 import bindbc.opengl; 12200 } else version(nanovg_builtin_opengl_bindings) { 12201 import arsd.simpledisplay; 12202 12203 /++ 12204 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. 12205 12206 History: 12207 Added January 22, 2021 (version 9.2 release) 12208 +/ 12209 public class NVGWindow : SimpleWindow { 12210 NVGContext nvg; 12211 12212 /++ 12213 12214 +/ 12215 this(int width, int height, string title) { 12216 setOpenGLContextVersion(3, 0); 12217 super(width, height, title, OpenGlOptions.yes, Resizability.allowResizing); 12218 12219 this.onClosing = delegate() { 12220 nvg.kill(); 12221 }; 12222 12223 this.visibleForTheFirstTime = delegate() { 12224 nvg = nvgCreateContext(); 12225 if(nvg is null) throw new Exception("cannot initialize NanoVega"); 12226 }; 12227 12228 this.redrawOpenGlScene = delegate() { 12229 if(redrawNVGScene is null) 12230 return; 12231 glViewport(0, 0, this.width, this.height); 12232 if(clearOnEachFrame) { 12233 glClearColor(0, 0, 0, 0); 12234 glClear(glNVGClearFlags); 12235 } 12236 12237 nvg.beginFrame(this.width, this.height); 12238 scope(exit) nvg.endFrame(); 12239 12240 redrawNVGScene(nvg); 12241 }; 12242 12243 this.setEventHandlers( 12244 &redrawOpenGlSceneNow, 12245 (KeyEvent ke) { 12246 if(ke.key == Key.Escape || ke.key == Key.Q) 12247 this.close(); 12248 } 12249 ); 12250 } 12251 12252 /++ 12253 12254 +/ 12255 bool clearOnEachFrame = true; 12256 12257 /++ 12258 12259 +/ 12260 void delegate(NVGContext nvg) redrawNVGScene; 12261 12262 /++ 12263 12264 +/ 12265 void redrawNVGSceneNow() { 12266 redrawOpenGlSceneNow(); 12267 } 12268 } 12269 12270 } else { 12271 import iv.glbinds; 12272 } 12273 12274 private: 12275 // sdpy is missing that yet 12276 static if (!is(typeof(GL_STENCIL_BUFFER_BIT))) enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 12277 12278 12279 12280 version(bindbc){ 12281 private extern(System) nothrow @nogc: 12282 // this definition doesn't exist in regular OpenGL (?) 12283 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 12284 private void nanovgInitOpenGL () { 12285 // i'm not aware of calling the load multiple times having negative side effects, so i don't do an initialization check 12286 GLSupport support = loadOpenGL(); 12287 if (support == GLSupport.noLibrary) 12288 assert(0, "OpenGL initialization failed: shared library failed to load"); 12289 else if (support == GLSupport.badLibrary) 12290 assert(0, "OpenGL initialization failed: a context-independent symbol failed to load"); 12291 else if (support == GLSupport.noContext) 12292 assert(0, "OpenGL initialization failed: a context needs to be created prior to initialization"); 12293 } 12294 } else { // OpenGL API missing from simpledisplay 12295 private void nanovgInitOpenGL () @nogc nothrow { 12296 __gshared bool initialized = false; 12297 if (initialized) return; 12298 12299 try 12300 gl3.loadDynamicLibrary(); 12301 catch(Exception) 12302 assert(0, "GL 3 failed to load"); 12303 12304 initialized = true; 12305 } 12306 } 12307 12308 12309 12310 /// Context creation flags. 12311 /// Group: context_management 12312 public enum NVGContextFlag : int { 12313 /// Nothing special, i.e. empty flag. 12314 None = 0, 12315 /// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA). 12316 Antialias = 1U<<0, 12317 /** Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little 12318 * slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. */ 12319 StencilStrokes = 1U<<1, 12320 /// Flag indicating that additional debug checks are done. 12321 Debug = 1U<<2, 12322 /// Filter (antialias) fonts 12323 FontAA = 1U<<7, 12324 /// Don't filter (antialias) fonts 12325 FontNoAA = 1U<<8, 12326 /// You can use this as a substitute for default flags, for cases like this: `nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);`. 12327 Default = 1U<<31, 12328 } 12329 12330 public enum NANOVG_GL_USE_STATE_FILTER = true; 12331 12332 /// Returns flags for glClear(). 12333 /// Group: context_management 12334 public uint glNVGClearFlags () pure nothrow @safe @nogc { 12335 pragma(inline, true); 12336 return (GL_COLOR_BUFFER_BIT|/*GL_DEPTH_BUFFER_BIT|*/GL_STENCIL_BUFFER_BIT); 12337 } 12338 12339 12340 // ////////////////////////////////////////////////////////////////////////// // 12341 private: 12342 12343 version = nanovega_shared_stencil; 12344 //version = nanovega_debug_clipping; 12345 12346 enum GLNVGuniformLoc { 12347 ViewSize, 12348 Tex, 12349 Frag, 12350 TMat, 12351 TTr, 12352 ClipTex, 12353 } 12354 12355 alias GLNVGshaderType = int; 12356 enum /*GLNVGshaderType*/ { 12357 NSVG_SHADER_FILLCOLOR, 12358 NSVG_SHADER_FILLGRAD, 12359 NSVG_SHADER_FILLIMG, 12360 NSVG_SHADER_SIMPLE, // also used for clipfill 12361 NSVG_SHADER_IMG, 12362 } 12363 12364 struct GLNVGshader { 12365 GLuint prog; 12366 GLuint frag; 12367 GLuint vert; 12368 GLint[GLNVGuniformLoc.max+1] loc; 12369 } 12370 12371 struct GLNVGtexture { 12372 int id; 12373 GLuint tex; 12374 int width, height; 12375 NVGtexture type; 12376 int flags; 12377 shared int rc; // this can be 0 with tex != 0 -- postponed deletion 12378 int nextfree; 12379 } 12380 12381 struct GLNVGblend { 12382 bool simple; 12383 GLenum srcRGB; 12384 GLenum dstRGB; 12385 GLenum srcAlpha; 12386 GLenum dstAlpha; 12387 } 12388 12389 alias GLNVGcallType = int; 12390 enum /*GLNVGcallType*/ { 12391 GLNVG_NONE = 0, 12392 GLNVG_FILL, 12393 GLNVG_CONVEXFILL, 12394 GLNVG_STROKE, 12395 GLNVG_TRIANGLES, 12396 GLNVG_AFFINE, // change affine transformation matrix 12397 GLNVG_PUSHCLIP, 12398 GLNVG_POPCLIP, 12399 GLNVG_RESETCLIP, 12400 GLNVG_CLIP_DDUMP_ON, 12401 GLNVG_CLIP_DDUMP_OFF, 12402 } 12403 12404 struct GLNVGcall { 12405 int type; 12406 int evenOdd; // for fill 12407 int image; 12408 int pathOffset; 12409 int pathCount; 12410 int triangleOffset; 12411 int triangleCount; 12412 int uniformOffset; 12413 NVGMatrix affine; 12414 GLNVGblend blendFunc; 12415 NVGClipMode clipmode; 12416 } 12417 12418 struct GLNVGpath { 12419 int fillOffset; 12420 int fillCount; 12421 int strokeOffset; 12422 int strokeCount; 12423 } 12424 12425 align(1) struct GLNVGfragUniforms { 12426 align(1): 12427 enum UNIFORM_ARRAY_SIZE = 13; 12428 // note: after modifying layout or size of uniform array, 12429 // don't forget to also update the fragment shader source! 12430 align(1) union { 12431 align(1): 12432 align(1) struct { 12433 align(1): 12434 float[12] scissorMat; // matrices are actually 3 vec4s 12435 float[12] paintMat; 12436 NVGColor innerCol; 12437 NVGColor middleCol; 12438 NVGColor outerCol; 12439 float[2] scissorExt; 12440 float[2] scissorScale; 12441 float[2] extent; 12442 float radius; 12443 float feather; 12444 float strokeMult; 12445 float strokeThr; 12446 float texType; 12447 float type; 12448 float doclip; 12449 float midp; // for gradients 12450 float unused2, unused3; 12451 } 12452 float[4][UNIFORM_ARRAY_SIZE] uniformArray; 12453 } 12454 } 12455 12456 enum GLMaskState { 12457 DontMask = -1, 12458 Uninitialized = 0, 12459 Initialized = 1, 12460 JustCleared = 2, 12461 } 12462 12463 final class GLNVGTextureLocker {} 12464 12465 struct GLNVGcontext { 12466 private import core.thread : ThreadID; 12467 12468 GLNVGshader shader; 12469 GLNVGtexture* textures; 12470 float[2] view; 12471 int freetexid; // -1: none 12472 int ntextures; 12473 int ctextures; 12474 GLuint vertBuf; 12475 int fragSize; 12476 int flags; 12477 // FBOs for masks 12478 GLuint[NVG_MAX_STATES] fbo; 12479 GLuint[2][NVG_MAX_STATES] fboTex; // FBO textures: [0] is color, [1] is stencil 12480 int fboWidth, fboHeight; 12481 GLMaskState[NVG_MAX_STATES] maskStack; 12482 int msp; // mask stack pointer; starts from `0`; points to next free item; see below for logic description 12483 int lastClipFBO; // -666: cache invalidated; -1: don't mask 12484 int lastClipUniOfs; 12485 bool doClipUnion; // specal mode 12486 GLNVGshader shaderFillFBO; 12487 GLNVGshader shaderCopyFBO; 12488 12489 bool inFrame; // will be `true` if we can perform OpenGL operations (used in texture deletion) 12490 shared bool mustCleanTextures; // will be `true` if we should delete some textures 12491 ThreadID mainTID; 12492 uint mainFBO; 12493 12494 // Per frame buffers 12495 GLNVGcall* calls; 12496 int ccalls; 12497 int ncalls; 12498 GLNVGpath* paths; 12499 int cpaths; 12500 int npaths; 12501 NVGVertex* verts; 12502 int cverts; 12503 int nverts; 12504 ubyte* uniforms; 12505 int cuniforms; 12506 int nuniforms; 12507 NVGMatrix lastAffine; 12508 12509 // cached state 12510 static if (NANOVG_GL_USE_STATE_FILTER) { 12511 GLuint boundTexture; 12512 GLuint stencilMask; 12513 GLenum stencilFunc; 12514 GLint stencilFuncRef; 12515 GLuint stencilFuncMask; 12516 GLNVGblend blendFunc; 12517 } 12518 } 12519 12520 int glnvg__maxi() (int a, int b) { pragma(inline, true); return (a > b ? a : b); } 12521 12522 void glnvg__bindTexture (GLNVGcontext* gl, GLuint tex) nothrow @trusted @nogc { 12523 static if (NANOVG_GL_USE_STATE_FILTER) { 12524 if (gl.boundTexture != tex) { 12525 gl.boundTexture = tex; 12526 glBindTexture(GL_TEXTURE_2D, tex); 12527 } 12528 } else { 12529 glBindTexture(GL_TEXTURE_2D, tex); 12530 } 12531 } 12532 12533 void glnvg__stencilMask (GLNVGcontext* gl, GLuint mask) nothrow @trusted @nogc { 12534 static if (NANOVG_GL_USE_STATE_FILTER) { 12535 if (gl.stencilMask != mask) { 12536 gl.stencilMask = mask; 12537 glStencilMask(mask); 12538 } 12539 } else { 12540 glStencilMask(mask); 12541 } 12542 } 12543 12544 void glnvg__stencilFunc (GLNVGcontext* gl, GLenum func, GLint ref_, GLuint mask) nothrow @trusted @nogc { 12545 static if (NANOVG_GL_USE_STATE_FILTER) { 12546 if (gl.stencilFunc != func || gl.stencilFuncRef != ref_ || gl.stencilFuncMask != mask) { 12547 gl.stencilFunc = func; 12548 gl.stencilFuncRef = ref_; 12549 gl.stencilFuncMask = mask; 12550 glStencilFunc(func, ref_, mask); 12551 } 12552 } else { 12553 glStencilFunc(func, ref_, mask); 12554 } 12555 } 12556 12557 // texture id is never zero 12558 // sets refcount to one 12559 GLNVGtexture* glnvg__allocTexture (GLNVGcontext* gl) nothrow @trusted @nogc { 12560 GLNVGtexture* tex = null; 12561 12562 int tid = gl.freetexid; 12563 if (tid == -1) { 12564 if (gl.ntextures >= gl.ctextures) { 12565 assert(gl.ntextures == gl.ctextures); 12566 //pragma(msg, GLNVGtexture.sizeof*32); 12567 int ctextures = (gl.ctextures == 0 ? 32 : gl.ctextures+gl.ctextures/2); // 1.5x overallocate 12568 GLNVGtexture* textures = cast(GLNVGtexture*)realloc(gl.textures, GLNVGtexture.sizeof*ctextures); 12569 if (textures is null) assert(0, "NanoVega: out of memory for textures"); 12570 memset(&textures[gl.ctextures], 0, (ctextures-gl.ctextures)*GLNVGtexture.sizeof); 12571 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated more textures (n=%d; c=%d; nc=%d)\n", gl.ntextures, gl.ctextures, ctextures); }} 12572 gl.textures = textures; 12573 gl.ctextures = ctextures; 12574 } 12575 assert(gl.ntextures+1 <= gl.ctextures); 12576 tid = gl.ntextures++; 12577 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf(" got next free texture id %d, ntextures=%d\n", tid+1, gl.ntextures); }} 12578 } else { 12579 gl.freetexid = gl.textures[tid].nextfree; 12580 } 12581 assert(tid <= gl.ntextures); 12582 12583 assert(gl.textures[tid].id == 0); 12584 tex = &gl.textures[tid]; 12585 memset(tex, 0, (*tex).sizeof); 12586 tex.id = tid+1; 12587 tex.rc = 1; 12588 tex.nextfree = -1; 12589 12590 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated texture with id %d (%d)\n", tex.id, tid+1); }} 12591 12592 return tex; 12593 } 12594 12595 GLNVGtexture* glnvg__findTexture (GLNVGcontext* gl, int id) nothrow @trusted @nogc { 12596 if (id <= 0 || id > gl.ntextures) return null; 12597 if (gl.textures[id-1].id == 0) return null; // free one 12598 assert(gl.textures[id-1].id == id); 12599 return &gl.textures[id-1]; 12600 } 12601 12602 bool glnvg__deleteTexture (GLNVGcontext* gl, ref int id) nothrow @trusted @nogc { 12603 if (id <= 0 || id > gl.ntextures) return false; 12604 auto tx = &gl.textures[id-1]; 12605 if (tx.id == 0) { id = 0; return false; } // free one 12606 assert(tx.id == id); 12607 assert(tx.tex != 0); 12608 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("decrefing texture with id %d (%d)\n", tx.id, id); }} 12609 import core.atomic : atomicOp; 12610 if (atomicOp!"-="(tx.rc, 1) == 0) { 12611 import core.thread : ThreadID; 12612 ThreadID mytid; 12613 static if (__VERSION__ < 2076) { 12614 DGNoThrowNoGC(() { 12615 import core.thread; mytid = Thread.getThis.id; 12616 })(); 12617 } else { 12618 try { import core.thread; mytid = Thread.getThis.id; } catch (Exception e) {} 12619 } 12620 if (gl.mainTID == mytid && gl.inFrame) { 12621 // can delete it right now 12622 if ((tx.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tx.tex); 12623 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** deleted texture with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }} 12624 memset(tx, 0, (*tx).sizeof); 12625 //{ import core.stdc.stdio; printf("deleting texture with id %d\n", id); } 12626 tx.nextfree = gl.freetexid; 12627 gl.freetexid = id-1; 12628 } else { 12629 // alas, we aren't doing frame business, so we should postpone deletion 12630 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** POSTPONED texture deletion with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }} 12631 version(aliced) { 12632 synchronized(GLNVGTextureLocker.classinfo) { 12633 tx.id = 0; // mark it as dead 12634 gl.mustCleanTextures = true; // set "need cleanup" flag 12635 } 12636 } else { 12637 try { 12638 synchronized(GLNVGTextureLocker.classinfo) { 12639 tx.id = 0; // mark it as dead 12640 gl.mustCleanTextures = true; // set "need cleanup" flag 12641 } 12642 } catch (Exception e) {} 12643 } 12644 } 12645 } 12646 id = 0; 12647 return true; 12648 } 12649 12650 void glnvg__dumpShaderError (GLuint shader, const(char)* name, const(char)* type) nothrow @trusted @nogc { 12651 import core.stdc.stdio : fprintf, stderr; 12652 GLchar[512+1] str = 0; 12653 GLsizei len = 0; 12654 glGetShaderInfoLog(shader, 512, &len, str.ptr); 12655 if (len > 512) len = 512; 12656 str[len] = '\0'; 12657 fprintf(stderr, "Shader %s/%s error:\n%s\n", name, type, str.ptr); 12658 } 12659 12660 void glnvg__dumpProgramError (GLuint prog, const(char)* name) nothrow @trusted @nogc { 12661 import core.stdc.stdio : fprintf, stderr; 12662 GLchar[512+1] str = 0; 12663 GLsizei len = 0; 12664 glGetProgramInfoLog(prog, 512, &len, str.ptr); 12665 if (len > 512) len = 512; 12666 str[len] = '\0'; 12667 fprintf(stderr, "Program %s error:\n%s\n", name, str.ptr); 12668 } 12669 12670 void glnvg__resetError(bool force=false) (GLNVGcontext* gl) nothrow @trusted @nogc { 12671 static if (!force) { 12672 if ((gl.flags&NVGContextFlag.Debug) == 0) return; 12673 } 12674 glGetError(); 12675 } 12676 12677 void glnvg__checkError(bool force=false) (GLNVGcontext* gl, const(char)* str) nothrow @trusted @nogc { 12678 GLenum err; 12679 static if (!force) { 12680 if ((gl.flags&NVGContextFlag.Debug) == 0) return; 12681 } 12682 err = glGetError(); 12683 if (err != GL_NO_ERROR) { 12684 import core.stdc.stdio : fprintf, stderr; 12685 fprintf(stderr, "Error %08x after %s\n", err, str); 12686 return; 12687 } 12688 } 12689 12690 bool glnvg__createShader (GLNVGshader* shader, const(char)* name, const(char)* header, const(char)* opts, const(char)* vshader, const(char)* fshader) nothrow @trusted @nogc { 12691 GLint status; 12692 GLuint prog, vert, frag; 12693 const(char)*[3] str; 12694 12695 memset(shader, 0, (*shader).sizeof); 12696 12697 prog = glCreateProgram(); 12698 vert = glCreateShader(GL_VERTEX_SHADER); 12699 frag = glCreateShader(GL_FRAGMENT_SHADER); 12700 str[0] = header; 12701 str[1] = (opts !is null ? opts : ""); 12702 str[2] = vshader; 12703 glShaderSource(vert, 3, cast(const(char)**)str.ptr, null); 12704 12705 glCompileShader(vert); 12706 glGetShaderiv(vert, GL_COMPILE_STATUS, &status); 12707 if (status != GL_TRUE) { 12708 glnvg__dumpShaderError(vert, name, "vert"); 12709 return false; 12710 } 12711 12712 str[0] = header; 12713 str[1] = (opts !is null ? opts : ""); 12714 str[2] = fshader; 12715 glShaderSource(frag, 3, cast(const(char)**)str.ptr, null); 12716 12717 glCompileShader(frag); 12718 glGetShaderiv(frag, GL_COMPILE_STATUS, &status); 12719 if (status != GL_TRUE) { 12720 glnvg__dumpShaderError(frag, name, "frag"); 12721 return false; 12722 } 12723 12724 glAttachShader(prog, vert); 12725 glAttachShader(prog, frag); 12726 12727 glBindAttribLocation(prog, 0, "vertex"); 12728 glBindAttribLocation(prog, 1, "tcoord"); 12729 12730 glLinkProgram(prog); 12731 glGetProgramiv(prog, GL_LINK_STATUS, &status); 12732 if (status != GL_TRUE) { 12733 glnvg__dumpProgramError(prog, name); 12734 return false; 12735 } 12736 12737 shader.prog = prog; 12738 shader.vert = vert; 12739 shader.frag = frag; 12740 12741 return true; 12742 } 12743 12744 void glnvg__deleteShader (GLNVGshader* shader) nothrow @trusted @nogc { 12745 if (shader.prog != 0) glDeleteProgram(shader.prog); 12746 if (shader.vert != 0) glDeleteShader(shader.vert); 12747 if (shader.frag != 0) glDeleteShader(shader.frag); 12748 } 12749 12750 void glnvg__getUniforms (GLNVGshader* shader) nothrow @trusted @nogc { 12751 shader.loc[GLNVGuniformLoc.ViewSize] = glGetUniformLocation(shader.prog, "viewSize"); 12752 shader.loc[GLNVGuniformLoc.Tex] = glGetUniformLocation(shader.prog, "tex"); 12753 shader.loc[GLNVGuniformLoc.Frag] = glGetUniformLocation(shader.prog, "frag"); 12754 shader.loc[GLNVGuniformLoc.TMat] = glGetUniformLocation(shader.prog, "tmat"); 12755 shader.loc[GLNVGuniformLoc.TTr] = glGetUniformLocation(shader.prog, "ttr"); 12756 shader.loc[GLNVGuniformLoc.ClipTex] = glGetUniformLocation(shader.prog, "clipTex"); 12757 } 12758 12759 void glnvg__killFBOs (GLNVGcontext* gl) nothrow @trusted @nogc { 12760 foreach (immutable fidx, ref GLuint fbo; gl.fbo[]) { 12761 if (fbo != 0) { 12762 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); 12763 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); 12764 foreach (ref GLuint tid; gl.fboTex.ptr[fidx][]) if (tid != 0) { glDeleteTextures(1, &tid); tid = 0; } 12765 glDeleteFramebuffers(1, &fbo); 12766 fbo = 0; 12767 } 12768 } 12769 gl.fboWidth = gl.fboHeight = 0; 12770 } 12771 12772 // returns `true` is new FBO was created 12773 // will not unbind buffer, if it was created 12774 bool glnvg__allocFBO (GLNVGcontext* gl, int fidx, bool doclear=true) nothrow @trusted @nogc { 12775 assert(fidx >= 0 && fidx < gl.fbo.length); 12776 assert(gl.fboWidth > 0); 12777 assert(gl.fboHeight > 0); 12778 12779 if (gl.fbo.ptr[fidx] != 0) return false; // nothing to do, this FBO is already initialized 12780 12781 glnvg__resetError(gl); 12782 12783 // allocate FBO object 12784 GLuint fbo = 0; 12785 glGenFramebuffers(1, &fbo); 12786 if (fbo == 0) assert(0, "NanoVega: cannot create FBO"); 12787 glnvg__checkError(gl, "glnvg__allocFBO: glGenFramebuffers"); 12788 glBindFramebuffer(GL_FRAMEBUFFER, fbo); 12789 //scope(exit) glBindFramebuffer(GL_FRAMEBUFFER, 0); 12790 12791 // attach 2D texture to this FBO 12792 GLuint tidColor = 0; 12793 glGenTextures(1, &tidColor); 12794 if (tidColor == 0) assert(0, "NanoVega: cannot create RGBA texture for FBO"); 12795 glBindTexture(GL_TEXTURE_2D, tidColor); 12796 //scope(exit) glBindTexture(GL_TEXTURE_2D, 0); 12797 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 12798 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_S"); 12799 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 12800 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_T"); 12801 //FIXME: linear or nearest? 12802 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 12803 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 12804 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MIN_FILTER"); 12805 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 12806 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 12807 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MAG_FILTER"); 12808 // empty texture 12809 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 12810 // create texture with only one color channel 12811 glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, gl.fboWidth, gl.fboHeight, 0, GL_RED, GL_UNSIGNED_BYTE, null); 12812 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 12813 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (color)"); 12814 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tidColor, 0); 12815 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (color)"); 12816 12817 // attach stencil texture to this FBO 12818 GLuint tidStencil = 0; 12819 version(nanovega_shared_stencil) { 12820 if (gl.fboTex.ptr[0].ptr[0] == 0) { 12821 glGenTextures(1, &tidStencil); 12822 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO"); 12823 gl.fboTex.ptr[0].ptr[0] = tidStencil; 12824 } else { 12825 tidStencil = gl.fboTex.ptr[0].ptr[0]; 12826 } 12827 if (fidx != 0) gl.fboTex.ptr[fidx].ptr[1] = 0; // stencil texture is shared among FBOs 12828 } else { 12829 glGenTextures(1, &tidStencil); 12830 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO"); 12831 gl.fboTex.ptr[0].ptr[0] = tidStencil; 12832 } 12833 glBindTexture(GL_TEXTURE_2D, tidStencil); 12834 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, gl.fboWidth, gl.fboHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, null); 12835 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (stencil)"); 12836 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tidStencil, 0); 12837 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (stencil)"); 12838 12839 { 12840 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); 12841 if (status != GL_FRAMEBUFFER_COMPLETE) { 12842 version(all) { 12843 import core.stdc.stdio; 12844 if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) printf("fucked attachement\n"); 12845 if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) printf("fucked dimensions\n"); 12846 if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) printf("missing attachement\n"); 12847 if (status == GL_FRAMEBUFFER_UNSUPPORTED) printf("unsupported\n"); 12848 } 12849 assert(0, "NanoVega: framebuffer creation failed"); 12850 } 12851 } 12852 12853 // clear 'em all 12854 if (doclear) { 12855 glClearColor(0, 0, 0, 0); 12856 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 12857 } 12858 12859 // save texture ids 12860 gl.fbo.ptr[fidx] = fbo; 12861 gl.fboTex.ptr[fidx].ptr[0] = tidColor; 12862 version(nanovega_shared_stencil) {} else { 12863 gl.fboTex.ptr[fidx].ptr[1] = tidStencil; 12864 } 12865 12866 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture); 12867 12868 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): created with index %d\n", gl.msp-1, fidx); } 12869 12870 return true; 12871 } 12872 12873 // will not unbind buffer 12874 void glnvg__clearFBO (GLNVGcontext* gl, int fidx) nothrow @trusted @nogc { 12875 assert(fidx >= 0 && fidx < gl.fbo.length); 12876 assert(gl.fboWidth > 0); 12877 assert(gl.fboHeight > 0); 12878 assert(gl.fbo.ptr[fidx] != 0); 12879 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[fidx]); 12880 glClearColor(0, 0, 0, 0); 12881 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 12882 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cleared with index %d\n", gl.msp-1, fidx); } 12883 } 12884 12885 // will not unbind buffer 12886 void glnvg__copyFBOToFrom (GLNVGcontext* gl, int didx, int sidx) nothrow @trusted @nogc { 12887 import core.stdc.string : memset; 12888 assert(didx >= 0 && didx < gl.fbo.length); 12889 assert(sidx >= 0 && sidx < gl.fbo.length); 12890 assert(gl.fboWidth > 0); 12891 assert(gl.fboHeight > 0); 12892 assert(gl.fbo.ptr[didx] != 0); 12893 assert(gl.fbo.ptr[sidx] != 0); 12894 if (didx == sidx) return; 12895 12896 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copy FBO: %d -> %d\n", gl.msp-1, sidx, didx); } 12897 12898 glUseProgram(gl.shaderCopyFBO.prog); 12899 12900 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[didx]); 12901 glDisable(GL_CULL_FACE); 12902 glDisable(GL_BLEND); 12903 glDisable(GL_SCISSOR_TEST); 12904 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[sidx].ptr[0]); 12905 // copy texture by drawing full quad 12906 enum x = 0; 12907 enum y = 0; 12908 immutable int w = gl.fboWidth; 12909 immutable int h = gl.fboHeight; 12910 immutable(NVGVertex[4]) vertices = 12911 [NVGVertex(x, y), // top-left 12912 NVGVertex(w, y), // top-right 12913 NVGVertex(w, h), // bottom-right 12914 NVGVertex(x, h)]; // bottom-left 12915 12916 glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices); 12917 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 12918 12919 // restore state (but don't unbind FBO) 12920 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture); 12921 glEnable(GL_CULL_FACE); 12922 glEnable(GL_BLEND); 12923 glUseProgram(gl.shader.prog); 12924 } 12925 12926 void glnvg__resetFBOClipTextureCache (GLNVGcontext* gl) nothrow @trusted @nogc { 12927 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): texture cache invalidated (%d)\n", gl.msp-1, gl.lastClipFBO); } 12928 /* 12929 if (gl.lastClipFBO >= 0) { 12930 glActiveTexture(GL_TEXTURE1); 12931 glBindTexture(GL_TEXTURE_2D, 0); 12932 glActiveTexture(GL_TEXTURE0); 12933 } 12934 */ 12935 gl.lastClipFBO = -666; 12936 } 12937 12938 void glnvg__setFBOClipTexture (GLNVGcontext* gl, GLNVGfragUniforms* frag) nothrow @trusted @nogc { 12939 //assert(gl.msp > 0 && gl.msp <= gl.maskStack.length); 12940 if (gl.lastClipFBO != -666) { 12941 // cached 12942 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cached (%d)\n", gl.msp-1, gl.lastClipFBO); } 12943 frag.doclip = (gl.lastClipFBO >= 0 ? 1 : 0); 12944 return; 12945 } 12946 12947 // no cache 12948 int fboidx = -1; 12949 mainloop: foreach_reverse (immutable sp, GLMaskState mst; gl.maskStack.ptr[0..gl.msp]/*; reverse*/) { 12950 final switch (mst) { 12951 case GLMaskState.DontMask: fboidx = -1; break mainloop; 12952 case GLMaskState.Uninitialized: break; 12953 case GLMaskState.Initialized: fboidx = cast(int)sp; break mainloop; 12954 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__setFBOClipTexture()` internal error"); 12955 } 12956 } 12957 12958 if (fboidx < 0) { 12959 // don't mask 12960 gl.lastClipFBO = -1; 12961 frag.doclip = 0; 12962 } else { 12963 // do masking 12964 assert(gl.fbo.ptr[fboidx] != 0); 12965 gl.lastClipFBO = fboidx; 12966 frag.doclip = 1; 12967 } 12968 12969 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): new cache (new:%d)\n", gl.msp-1, gl.lastClipFBO); } 12970 12971 if (gl.lastClipFBO >= 0) { 12972 assert(gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 12973 glActiveTexture(GL_TEXTURE1); 12974 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 12975 glActiveTexture(GL_TEXTURE0); 12976 } 12977 } 12978 12979 // returns index in `gl.fbo`, or -1 for "don't mask" 12980 int glnvg__generateFBOClipTexture (GLNVGcontext* gl) nothrow @trusted @nogc { 12981 assert(gl.msp > 0 && gl.msp <= gl.maskStack.length); 12982 // we need initialized FBO, even for "don't mask" case 12983 // for this, look back in stack, and either copy initialized FBO, 12984 // or stop at first uninitialized one, and clear it 12985 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.Initialized) { 12986 // shortcut 12987 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); } 12988 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[gl.msp-1]); 12989 return gl.msp-1; 12990 } 12991 foreach_reverse (immutable sp; 0..gl.msp/*; reverse*/) { 12992 final switch (gl.maskStack.ptr[sp]) { 12993 case GLMaskState.DontMask: 12994 // clear it 12995 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture\n", gl.msp-1); } 12996 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1); 12997 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared; 12998 return gl.msp-1; 12999 case GLMaskState.Uninitialized: break; // do nothing 13000 case GLMaskState.Initialized: 13001 // i found her! copy to TOS 13002 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copying texture from %d\n", gl.msp-1, cast(int)sp); } 13003 glnvg__allocFBO(gl, gl.msp-1, false); 13004 glnvg__copyFBOToFrom(gl, gl.msp-1, sp); 13005 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized; 13006 return gl.msp-1; 13007 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__generateFBOClipTexture()` internal error"); 13008 } 13009 } 13010 // nothing was initialized, lol 13011 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture (first one)\n", gl.msp-1); } 13012 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1); 13013 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared; 13014 return gl.msp-1; 13015 } 13016 13017 void glnvg__renderPushClip (void* uptr) nothrow @trusted @nogc { 13018 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13019 GLNVGcall* call = glnvg__allocCall(gl); 13020 if (call is null) return; 13021 call.type = GLNVG_PUSHCLIP; 13022 } 13023 13024 void glnvg__renderPopClip (void* uptr) nothrow @trusted @nogc { 13025 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13026 GLNVGcall* call = glnvg__allocCall(gl); 13027 if (call is null) return; 13028 call.type = GLNVG_POPCLIP; 13029 } 13030 13031 void glnvg__renderResetClip (void* uptr) nothrow @trusted @nogc { 13032 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13033 GLNVGcall* call = glnvg__allocCall(gl); 13034 if (call is null) return; 13035 call.type = GLNVG_RESETCLIP; 13036 } 13037 13038 void glnvg__clipDebugDump (void* uptr, bool doit) nothrow @trusted @nogc { 13039 version(nanovega_debug_clipping) { 13040 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13041 GLNVGcall* call = glnvg__allocCall(gl); 13042 call.type = (doit ? GLNVG_CLIP_DDUMP_ON : GLNVG_CLIP_DDUMP_OFF); 13043 } 13044 } 13045 13046 bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc { 13047 import core.stdc.stdio : snprintf; 13048 13049 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13050 enum align_ = 4; 13051 13052 char[64] shaderHeader = void; 13053 //enum shaderHeader = "#define UNIFORM_ARRAY_SIZE 12\n"; 13054 snprintf(shaderHeader.ptr, shaderHeader.length, "#define UNIFORM_ARRAY_SIZE %u\n", cast(uint)GLNVGfragUniforms.UNIFORM_ARRAY_SIZE); 13055 13056 enum fillVertShader = q{ 13057 uniform vec2 viewSize; 13058 attribute vec2 vertex; 13059 attribute vec2 tcoord; 13060 varying vec2 ftcoord; 13061 varying vec2 fpos; 13062 uniform vec4 tmat; /* abcd of affine matrix: xyzw */ 13063 uniform vec2 ttr; /* tx and ty of affine matrix */ 13064 void main (void) { 13065 /* affine transformation */ 13066 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x; 13067 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y; 13068 ftcoord = tcoord; 13069 fpos = vec2(nx, ny); 13070 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1); 13071 } 13072 }; 13073 13074 enum fillFragShader = ` 13075 uniform vec4 frag[UNIFORM_ARRAY_SIZE]; 13076 uniform sampler2D tex; 13077 uniform sampler2D clipTex; 13078 uniform vec2 viewSize; 13079 varying vec2 ftcoord; 13080 varying vec2 fpos; 13081 #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz) 13082 #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz) 13083 #define innerCol frag[6] 13084 #define middleCol frag[7] 13085 #define outerCol frag[7+1] 13086 #define scissorExt frag[8+1].xy 13087 #define scissorScale frag[8+1].zw 13088 #define extent frag[9+1].xy 13089 #define radius frag[9+1].z 13090 #define feather frag[9+1].w 13091 #define strokeMult frag[10+1].x 13092 #define strokeThr frag[10+1].y 13093 #define texType int(frag[10+1].z) 13094 #define type int(frag[10+1].w) 13095 #define doclip int(frag[11+1].x) 13096 #define midp frag[11+1].y 13097 13098 float sdroundrect (in vec2 pt, in vec2 ext, in float rad) { 13099 vec2 ext2 = ext-vec2(rad, rad); 13100 vec2 d = abs(pt)-ext2; 13101 return min(max(d.x, d.y), 0.0)+length(max(d, 0.0))-rad; 13102 } 13103 13104 // Scissoring 13105 float scissorMask (in vec2 p) { 13106 vec2 sc = (abs((scissorMat*vec3(p, 1.0)).xy)-scissorExt); 13107 sc = vec2(0.5, 0.5)-sc*scissorScale; 13108 return clamp(sc.x, 0.0, 1.0)*clamp(sc.y, 0.0, 1.0); 13109 } 13110 13111 #ifdef EDGE_AA 13112 // Stroke - from [0..1] to clipped pyramid, where the slope is 1px. 13113 float strokeMask () { 13114 return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult)*min(1.0, ftcoord.y); 13115 } 13116 #endif 13117 13118 void main (void) { 13119 // clipping 13120 if (doclip != 0) { 13121 /*vec4 clr = texelFetch(clipTex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);*/ 13122 vec4 clr = texture2D(clipTex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y)); 13123 if (clr.r == 0.0) discard; 13124 } 13125 float scissor = scissorMask(fpos); 13126 if (scissor <= 0.0) discard; //k8: is it really faster? 13127 #ifdef EDGE_AA 13128 float strokeAlpha = strokeMask(); 13129 if (strokeAlpha < strokeThr) discard; 13130 #else 13131 float strokeAlpha = 1.0; 13132 #endif 13133 // rendering 13134 vec4 color; 13135 if (type == 0) { /* NSVG_SHADER_FILLCOLOR */ 13136 color = innerCol; 13137 // Combine alpha 13138 color *= strokeAlpha*scissor; 13139 } else if (type == 1) { /* NSVG_SHADER_FILLGRAD */ 13140 // Gradient 13141 // Calculate gradient color using box gradient 13142 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy; 13143 float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0); 13144 if (midp <= 0.0) { 13145 color = mix(innerCol, outerCol, d); 13146 } else { 13147 float gdst = min(midp, 1.0); 13148 if (d < gdst) { 13149 color = mix(innerCol, middleCol, d/gdst); 13150 } else { 13151 color = mix(middleCol, outerCol, (d-gdst)/gdst); 13152 } 13153 } 13154 // Combine alpha 13155 color *= strokeAlpha*scissor; 13156 } else if (type == 2) { /* NSVG_SHADER_FILLIMG */ 13157 // Image 13158 // Calculate color from texture 13159 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy/extent; 13160 color = texture2D(tex, pt); 13161 if (texType == 1) color = vec4(color.xyz*color.w, color.w); 13162 if (texType == 2) color = vec4(color.x); 13163 // Apply color tint and alpha 13164 color *= innerCol; 13165 // Combine alpha 13166 color *= strokeAlpha*scissor; 13167 } else if (type == 3) { /* NSVG_SHADER_SIMPLE */ 13168 // Stencil fill 13169 color = vec4(1, 1, 1, 1); 13170 } else if (type == 4) { /* NSVG_SHADER_IMG */ 13171 // Textured tris 13172 color = texture2D(tex, ftcoord); 13173 if (texType == 1) color = vec4(color.xyz*color.w, color.w); 13174 if (texType == 2) color = vec4(color.x); 13175 color *= scissor; 13176 color *= innerCol; // Apply color tint 13177 } 13178 gl_FragColor = color; 13179 } 13180 `; 13181 13182 enum clipVertShaderFill = q{ 13183 uniform vec2 viewSize; 13184 attribute vec2 vertex; 13185 uniform vec4 tmat; /* abcd of affine matrix: xyzw */ 13186 uniform vec2 ttr; /* tx and ty of affine matrix */ 13187 void main (void) { 13188 /* affine transformation */ 13189 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x; 13190 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y; 13191 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1); 13192 } 13193 }; 13194 13195 enum clipFragShaderFill = q{ 13196 uniform vec2 viewSize; 13197 void main (void) { 13198 gl_FragColor = vec4(1, 1, 1, 1); 13199 } 13200 }; 13201 13202 enum clipVertShaderCopy = q{ 13203 uniform vec2 viewSize; 13204 attribute vec2 vertex; 13205 void main (void) { 13206 gl_Position = vec4(2.0*vertex.x/viewSize.x-1.0, 1.0-2.0*vertex.y/viewSize.y, 0, 1); 13207 } 13208 }; 13209 13210 enum clipFragShaderCopy = q{ 13211 uniform sampler2D tex; 13212 uniform vec2 viewSize; 13213 void main (void) { 13214 //gl_FragColor = texelFetch(tex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0); 13215 gl_FragColor = texture2D(tex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y)); 13216 } 13217 }; 13218 13219 glnvg__checkError(gl, "init"); 13220 13221 string defines = (gl.flags&NVGContextFlag.Antialias ? "#define EDGE_AA 1\n" : null); 13222 if (!glnvg__createShader(&gl.shader, "shader", shaderHeader.ptr, defines.ptr, fillVertShader, fillFragShader)) return false; 13223 if (!glnvg__createShader(&gl.shaderFillFBO, "shaderFillFBO", shaderHeader.ptr, defines.ptr, clipVertShaderFill, clipFragShaderFill)) return false; 13224 if (!glnvg__createShader(&gl.shaderCopyFBO, "shaderCopyFBO", shaderHeader.ptr, defines.ptr, clipVertShaderCopy, clipFragShaderCopy)) return false; 13225 13226 glnvg__checkError(gl, "uniform locations"); 13227 glnvg__getUniforms(&gl.shader); 13228 glnvg__getUniforms(&gl.shaderFillFBO); 13229 glnvg__getUniforms(&gl.shaderCopyFBO); 13230 13231 // Create dynamic vertex array 13232 glGenBuffers(1, &gl.vertBuf); 13233 13234 gl.fragSize = GLNVGfragUniforms.sizeof+align_-GLNVGfragUniforms.sizeof%align_; 13235 13236 glnvg__checkError(gl, "create done"); 13237 13238 glFinish(); 13239 13240 return true; 13241 } 13242 13243 int glnvg__renderCreateTexture (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc { 13244 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13245 GLNVGtexture* tex = glnvg__allocTexture(gl); 13246 13247 if (tex is null) return 0; 13248 13249 glGenTextures(1, &tex.tex); 13250 tex.width = w; 13251 tex.height = h; 13252 tex.type = type; 13253 tex.flags = imageFlags; 13254 glnvg__bindTexture(gl, tex.tex); 13255 13256 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("created texture with id %d; glid=%u\n", tex.id, tex.tex); }} 13257 13258 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 13259 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width); 13260 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13261 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13262 13263 13264 13265 immutable ttype = (type == NVGtexture.RGBA ? GL_RGBA : GL_RED); 13266 glTexImage2D(GL_TEXTURE_2D, 0, ttype, w, h, 0, ttype, GL_UNSIGNED_BYTE, data); 13267 // GL 3.0 and later have support for a dedicated function for generating mipmaps 13268 // it needs to be called after the glTexImage2D call 13269 if (imageFlags & NVGImageFlag.GenerateMipmaps) 13270 glGenerateMipmap(GL_TEXTURE_2D); 13271 13272 immutable tfmin = 13273 (imageFlags & NVGImageFlag.GenerateMipmaps ? GL_LINEAR_MIPMAP_LINEAR : 13274 imageFlags & NVGImageFlag.NoFiltering ? GL_NEAREST : 13275 GL_LINEAR); 13276 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.0); 13277 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tfmin); 13278 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST : GL_LINEAR)); 13279 13280 int flag; 13281 if (imageFlags&NVGImageFlag.RepeatX) 13282 flag = GL_REPEAT; 13283 else if (imageFlags&NVGImageFlag.ClampToBorderX) 13284 flag = GL_CLAMP_TO_BORDER; 13285 else 13286 flag = GL_CLAMP_TO_EDGE; 13287 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, flag); 13288 13289 13290 if (imageFlags&NVGImageFlag.RepeatY) 13291 flag = GL_REPEAT; 13292 else if (imageFlags&NVGImageFlag.ClampToBorderY) 13293 flag = GL_CLAMP_TO_BORDER; 13294 else 13295 flag = GL_CLAMP_TO_EDGE; 13296 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, flag); 13297 13298 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 13299 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 13300 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13301 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13302 13303 glnvg__checkError(gl, "create tex"); 13304 glnvg__bindTexture(gl, 0); 13305 13306 return tex.id; 13307 } 13308 13309 bool glnvg__renderDeleteTexture (void* uptr, int image) nothrow @trusted @nogc { 13310 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13311 return glnvg__deleteTexture(gl, image); 13312 } 13313 13314 bool glnvg__renderTextureIncRef (void* uptr, int image) nothrow @trusted @nogc { 13315 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13316 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13317 if (tex is null) { 13318 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT incref texture with id %d\n", image); }} 13319 return false; 13320 } 13321 import core.atomic : atomicOp; 13322 atomicOp!"+="(tex.rc, 1); 13323 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("texture #%d: incref; newref=%d\n", image, tex.rc); }} 13324 return true; 13325 } 13326 13327 bool glnvg__renderUpdateTexture (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc { 13328 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13329 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13330 13331 if (tex is null) { 13332 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT update texture with id %d\n", image); }} 13333 return false; 13334 } 13335 13336 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("updated texture with id %d; glid=%u\n", tex.id, image, tex.tex); }} 13337 13338 glnvg__bindTexture(gl, tex.tex); 13339 13340 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 13341 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width); 13342 glPixelStorei(GL_UNPACK_SKIP_PIXELS, x); 13343 glPixelStorei(GL_UNPACK_SKIP_ROWS, y); 13344 13345 immutable ttype = (tex.type == NVGtexture.RGBA ? GL_RGBA : GL_RED); 13346 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, ttype, GL_UNSIGNED_BYTE, data); 13347 13348 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 13349 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 13350 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13351 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13352 13353 glnvg__bindTexture(gl, 0); 13354 13355 return true; 13356 } 13357 13358 bool glnvg__renderGetTextureSize (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc { 13359 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13360 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13361 if (tex is null) { 13362 if (w !is null) *w = 0; 13363 if (h !is null) *h = 0; 13364 return false; 13365 } else { 13366 if (w !is null) *w = tex.width; 13367 if (h !is null) *h = tex.height; 13368 return true; 13369 } 13370 } 13371 13372 void glnvg__xformToMat3x4 (float[] m3, const(float)[] t) nothrow @trusted @nogc { 13373 assert(t.length >= 6); 13374 assert(m3.length >= 12); 13375 m3.ptr[0] = t.ptr[0]; 13376 m3.ptr[1] = t.ptr[1]; 13377 m3.ptr[2] = 0.0f; 13378 m3.ptr[3] = 0.0f; 13379 m3.ptr[4] = t.ptr[2]; 13380 m3.ptr[5] = t.ptr[3]; 13381 m3.ptr[6] = 0.0f; 13382 m3.ptr[7] = 0.0f; 13383 m3.ptr[8] = t.ptr[4]; 13384 m3.ptr[9] = t.ptr[5]; 13385 m3.ptr[10] = 1.0f; 13386 m3.ptr[11] = 0.0f; 13387 } 13388 13389 NVGColor glnvg__premulColor() (in auto ref NVGColor c) nothrow @trusted @nogc { 13390 //pragma(inline, true); 13391 NVGColor res = void; 13392 res.r = c.r*c.a; 13393 res.g = c.g*c.a; 13394 res.b = c.b*c.a; 13395 res.a = c.a; 13396 return res; 13397 } 13398 13399 bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* paint, NVGscissor* scissor, float width, float fringe, float strokeThr) nothrow @trusted @nogc { 13400 import core.stdc.math : sqrtf; 13401 GLNVGtexture* tex = null; 13402 NVGMatrix invxform = void; 13403 13404 memset(frag, 0, (*frag).sizeof); 13405 13406 frag.innerCol = glnvg__premulColor(paint.innerColor); 13407 frag.middleCol = glnvg__premulColor(paint.middleColor); 13408 frag.outerCol = glnvg__premulColor(paint.outerColor); 13409 frag.midp = paint.midp; 13410 13411 if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) { 13412 memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof); 13413 frag.scissorExt.ptr[0] = 1.0f; 13414 frag.scissorExt.ptr[1] = 1.0f; 13415 frag.scissorScale.ptr[0] = 1.0f; 13416 frag.scissorScale.ptr[1] = 1.0f; 13417 } else { 13418 //nvgTransformInverse(invxform[], scissor.xform[]); 13419 invxform = scissor.xform.inverted; 13420 glnvg__xformToMat3x4(frag.scissorMat[], invxform.mat[]); 13421 frag.scissorExt.ptr[0] = scissor.extent.ptr[0]; 13422 frag.scissorExt.ptr[1] = scissor.extent.ptr[1]; 13423 frag.scissorScale.ptr[0] = sqrtf(scissor.xform.mat.ptr[0]*scissor.xform.mat.ptr[0]+scissor.xform.mat.ptr[2]*scissor.xform.mat.ptr[2])/fringe; 13424 frag.scissorScale.ptr[1] = sqrtf(scissor.xform.mat.ptr[1]*scissor.xform.mat.ptr[1]+scissor.xform.mat.ptr[3]*scissor.xform.mat.ptr[3])/fringe; 13425 } 13426 13427 memcpy(frag.extent.ptr, paint.extent.ptr, frag.extent.sizeof); 13428 frag.strokeMult = (width*0.5f+fringe*0.5f)/fringe; 13429 frag.strokeThr = strokeThr; 13430 13431 if (paint.image.valid) { 13432 tex = glnvg__findTexture(gl, paint.image.id); 13433 if (tex is null) return false; 13434 if ((tex.flags&NVGImageFlag.FlipY) != 0) { 13435 /* 13436 NVGMatrix flipped; 13437 nvgTransformScale(flipped[], 1.0f, -1.0f); 13438 nvgTransformMultiply(flipped[], paint.xform[]); 13439 nvgTransformInverse(invxform[], flipped[]); 13440 */ 13441 /* 13442 NVGMatrix m1 = void, m2 = void; 13443 nvgTransformTranslate(m1[], 0.0f, frag.extent.ptr[1]*0.5f); 13444 nvgTransformMultiply(m1[], paint.xform[]); 13445 nvgTransformScale(m2[], 1.0f, -1.0f); 13446 nvgTransformMultiply(m2[], m1[]); 13447 nvgTransformTranslate(m1[], 0.0f, -frag.extent.ptr[1]*0.5f); 13448 nvgTransformMultiply(m1[], m2[]); 13449 nvgTransformInverse(invxform[], m1[]); 13450 */ 13451 NVGMatrix m1 = NVGMatrix.Translated(0.0f, frag.extent.ptr[1]*0.5f); 13452 m1.mul(paint.xform); 13453 NVGMatrix m2 = NVGMatrix.Scaled(1.0f, -1.0f); 13454 m2.mul(m1); 13455 m1 = NVGMatrix.Translated(0.0f, -frag.extent.ptr[1]*0.5f); 13456 m1.mul(m2); 13457 invxform = m1.inverted; 13458 } else { 13459 //nvgTransformInverse(invxform[], paint.xform[]); 13460 invxform = paint.xform.inverted; 13461 } 13462 frag.type = NSVG_SHADER_FILLIMG; 13463 13464 if (tex.type == NVGtexture.RGBA) { 13465 frag.texType = (tex.flags&NVGImageFlag.Premultiplied ? 0 : 1); 13466 } else { 13467 frag.texType = 2; 13468 } 13469 //printf("frag.texType = %d\n", frag.texType); 13470 } else { 13471 frag.type = (paint.simpleColor ? NSVG_SHADER_FILLCOLOR : NSVG_SHADER_FILLGRAD); 13472 frag.radius = paint.radius; 13473 frag.feather = paint.feather; 13474 //nvgTransformInverse(invxform[], paint.xform[]); 13475 invxform = paint.xform.inverted; 13476 } 13477 13478 glnvg__xformToMat3x4(frag.paintMat[], invxform.mat[]); 13479 13480 return true; 13481 } 13482 13483 void glnvg__setUniforms (GLNVGcontext* gl, int uniformOffset, int image) nothrow @trusted @nogc { 13484 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); 13485 glnvg__setFBOClipTexture(gl, frag); 13486 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.Frag], frag.UNIFORM_ARRAY_SIZE, &(frag.uniformArray.ptr[0].ptr[0])); 13487 glnvg__checkError(gl, "glnvg__setUniforms"); 13488 if (image != 0) { 13489 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13490 glnvg__bindTexture(gl, (tex !is null ? tex.tex : 0)); 13491 glnvg__checkError(gl, "tex paint tex"); 13492 } else { 13493 glnvg__bindTexture(gl, 0); 13494 } 13495 } 13496 13497 void glnvg__finishClip (GLNVGcontext* gl, NVGClipMode clipmode) nothrow @trusted @nogc { 13498 assert(clipmode != NVGClipMode.None); 13499 13500 // fill FBO, clear stencil buffer 13501 //TODO: optimize with bounds? 13502 version(all) { 13503 //glnvg__resetAffine(gl); 13504 //glUseProgram(gl.shaderFillFBO.prog); 13505 glDisable(GL_CULL_FACE); 13506 glDisable(GL_BLEND); 13507 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13508 glEnable(GL_STENCIL_TEST); 13509 if (gl.doClipUnion) { 13510 // for "and" we should clear everything that is NOT stencil-masked 13511 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13512 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13513 } else { 13514 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x00, 0xff); 13515 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13516 } 13517 13518 immutable(NVGVertex[4]) vertices = 13519 [NVGVertex(0, 0, 0, 0), 13520 NVGVertex(0, gl.fboHeight, 0, 0), 13521 NVGVertex(gl.fboWidth, gl.fboHeight, 0, 0), 13522 NVGVertex(gl.fboWidth, 0, 0, 0)]; 13523 13524 glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices); 13525 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 13526 13527 //glnvg__restoreAffine(gl); 13528 } 13529 13530 glBindFramebuffer(GL_FRAMEBUFFER, gl.mainFBO); 13531 glDisable(GL_COLOR_LOGIC_OP); 13532 //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // done above 13533 glEnable(GL_BLEND); 13534 glDisable(GL_STENCIL_TEST); 13535 glEnable(GL_CULL_FACE); 13536 glUseProgram(gl.shader.prog); 13537 13538 // set current FBO as used one 13539 assert(gl.msp > 0 && gl.fbo.ptr[gl.msp-1] > 0 && gl.fboTex.ptr[gl.msp-1].ptr[0] > 0); 13540 if (gl.lastClipFBO != gl.msp-1) { 13541 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); } 13542 gl.lastClipFBO = gl.msp-1; 13543 glActiveTexture(GL_TEXTURE1); 13544 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 13545 glActiveTexture(GL_TEXTURE0); 13546 } 13547 } 13548 13549 void glnvg__setClipUniforms (GLNVGcontext* gl, int uniformOffset, NVGClipMode clipmode) nothrow @trusted @nogc { 13550 assert(clipmode != NVGClipMode.None); 13551 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); 13552 // save uniform offset for `glnvg__finishClip()` 13553 gl.lastClipUniOfs = uniformOffset; 13554 // get FBO index, bind this FBO 13555 immutable int clipTexId = glnvg__generateFBOClipTexture(gl); 13556 assert(clipTexId >= 0); 13557 glUseProgram(gl.shaderFillFBO.prog); 13558 glnvg__checkError(gl, "use"); 13559 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[clipTexId]); 13560 // set logic op for clip 13561 gl.doClipUnion = false; 13562 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.JustCleared) { 13563 // it is cleared to zero, we can just draw a path 13564 glDisable(GL_COLOR_LOGIC_OP); 13565 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized; 13566 } else { 13567 glEnable(GL_COLOR_LOGIC_OP); 13568 final switch (clipmode) { 13569 case NVGClipMode.None: assert(0, "wtf?!"); 13570 case NVGClipMode.Union: glLogicOp(GL_CLEAR); gl.doClipUnion = true; break; // use `GL_CLEAR` to avoid adding another shader mode 13571 case NVGClipMode.Or: glLogicOp(GL_COPY); break; // GL_OR 13572 case NVGClipMode.Xor: glLogicOp(GL_XOR); break; 13573 case NVGClipMode.Sub: glLogicOp(GL_CLEAR); break; 13574 case NVGClipMode.Replace: glLogicOp(GL_COPY); break; 13575 } 13576 } 13577 // set affine matrix 13578 glUniform4fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TMat], 1, gl.lastAffine.mat.ptr); 13579 glnvg__checkError(gl, "affine 0"); 13580 glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TTr], 1, gl.lastAffine.mat.ptr+4); 13581 glnvg__checkError(gl, "affine 1"); 13582 // setup common OpenGL parameters 13583 glDisable(GL_BLEND); 13584 glDisable(GL_CULL_FACE); 13585 glEnable(GL_STENCIL_TEST); 13586 glnvg__stencilMask(gl, 0xff); 13587 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13588 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 13589 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13590 } 13591 13592 void glnvg__renderViewport (void* uptr, int width, int height) nothrow @trusted @nogc { 13593 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13594 gl.inFrame = true; 13595 gl.view.ptr[0] = cast(float)width; 13596 gl.view.ptr[1] = cast(float)height; 13597 // kill FBOs if we need to create new ones (flushing will recreate 'em if necessary) 13598 if (width != gl.fboWidth || height != gl.fboHeight) { 13599 glnvg__killFBOs(gl); 13600 gl.fboWidth = width; 13601 gl.fboHeight = height; 13602 } 13603 gl.msp = 1; 13604 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13605 // texture cleanup 13606 import core.atomic : atomicLoad; 13607 if (atomicLoad(gl.mustCleanTextures)) { 13608 try { 13609 import core.thread : Thread; 13610 static if (__VERSION__ < 2076) { 13611 DGNoThrowNoGC(() { 13612 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13613 })(); 13614 } else { 13615 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13616 } 13617 synchronized(GLNVGTextureLocker.classinfo) { 13618 gl.mustCleanTextures = false; 13619 foreach (immutable tidx, ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) { 13620 // no need to use atomic ops here, as we're locked 13621 if (tex.rc == 0 && tex.tex != 0 && tex.id == 0) { 13622 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** cleaned up texture with glid=%u\n", tex.tex); }} 13623 import core.stdc.string : memset; 13624 if ((tex.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tex.tex); 13625 memset(&tex, 0, tex.sizeof); 13626 tex.nextfree = gl.freetexid; 13627 gl.freetexid = cast(int)tidx; 13628 } 13629 } 13630 } 13631 } catch (Exception e) {} 13632 } 13633 } 13634 13635 void glnvg__fill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13636 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13637 int npaths = call.pathCount; 13638 13639 if (call.clipmode == NVGClipMode.None) { 13640 // Draw shapes 13641 glEnable(GL_STENCIL_TEST); 13642 glnvg__stencilMask(gl, 0xffU); 13643 glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xffU); 13644 13645 glnvg__setUniforms(gl, call.uniformOffset, 0); 13646 glnvg__checkError(gl, "fill simple"); 13647 13648 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13649 if (call.evenOdd) { 13650 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT); 13651 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT); 13652 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); 13653 } else { 13654 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 13655 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 13656 } 13657 glDisable(GL_CULL_FACE); 13658 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13659 glEnable(GL_CULL_FACE); 13660 13661 // Draw anti-aliased pixels 13662 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13663 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image); 13664 glnvg__checkError(gl, "fill fill"); 13665 13666 if (gl.flags&NVGContextFlag.Antialias) { 13667 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xffU); 13668 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13669 // Draw fringes 13670 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13671 } 13672 13673 // Draw fill 13674 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xffU); 13675 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13676 if (call.evenOdd) { 13677 glDisable(GL_CULL_FACE); 13678 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount); 13679 //foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13680 glEnable(GL_CULL_FACE); 13681 } else { 13682 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount); 13683 } 13684 13685 glDisable(GL_STENCIL_TEST); 13686 } else { 13687 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); // this activates our FBO 13688 glnvg__checkError(gl, "fillclip simple"); 13689 glnvg__stencilFunc(gl, GL_ALWAYS, 0x00, 0xffU); 13690 if (call.evenOdd) { 13691 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT); 13692 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT); 13693 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); 13694 } else { 13695 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 13696 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 13697 } 13698 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13699 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13700 } 13701 } 13702 13703 void glnvg__convexFill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13704 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13705 int npaths = call.pathCount; 13706 13707 if (call.clipmode == NVGClipMode.None) { 13708 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13709 glnvg__checkError(gl, "convex fill"); 13710 if (call.evenOdd) glDisable(GL_CULL_FACE); 13711 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13712 if (gl.flags&NVGContextFlag.Antialias) { 13713 // Draw fringes 13714 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13715 } 13716 if (call.evenOdd) glEnable(GL_CULL_FACE); 13717 } else { 13718 glnvg__setClipUniforms(gl, call.uniformOffset, call.clipmode); // this activates our FBO 13719 glnvg__checkError(gl, "clip convex fill"); 13720 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13721 if (gl.flags&NVGContextFlag.Antialias) { 13722 // Draw fringes 13723 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13724 } 13725 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13726 } 13727 } 13728 13729 void glnvg__stroke (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13730 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13731 int npaths = call.pathCount; 13732 13733 if (call.clipmode == NVGClipMode.None) { 13734 if (gl.flags&NVGContextFlag.StencilStrokes) { 13735 glEnable(GL_STENCIL_TEST); 13736 glnvg__stencilMask(gl, 0xff); 13737 13738 // Fill the stroke base without overlap 13739 glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff); 13740 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 13741 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image); 13742 glnvg__checkError(gl, "stroke fill 0"); 13743 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13744 13745 // Draw anti-aliased pixels. 13746 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13747 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13748 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13749 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13750 13751 // Clear stencil buffer. 13752 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13753 glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff); 13754 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13755 glnvg__checkError(gl, "stroke fill 1"); 13756 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13757 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13758 13759 glDisable(GL_STENCIL_TEST); 13760 13761 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f); 13762 } else { 13763 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13764 glnvg__checkError(gl, "stroke fill"); 13765 // Draw Strokes 13766 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13767 } 13768 } else { 13769 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); 13770 glnvg__checkError(gl, "stroke fill 0"); 13771 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13772 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13773 } 13774 } 13775 13776 void glnvg__triangles (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13777 if (call.clipmode == NVGClipMode.None) { 13778 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13779 glnvg__checkError(gl, "triangles fill"); 13780 glDrawArrays(GL_TRIANGLES, call.triangleOffset, call.triangleCount); 13781 } else { 13782 //TODO(?): use texture as mask? 13783 } 13784 } 13785 13786 void glnvg__affine (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13787 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, call.affine.mat.ptr); 13788 glnvg__checkError(gl, "affine"); 13789 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, call.affine.mat.ptr+4); 13790 glnvg__checkError(gl, "affine"); 13791 //glnvg__setUniforms(gl, call.uniformOffset, call.image); 13792 } 13793 13794 void glnvg__renderCancelInternal (GLNVGcontext* gl, bool clearTextures) nothrow @trusted @nogc { 13795 scope(exit) gl.inFrame = false; 13796 if (clearTextures && gl.inFrame) { 13797 try { 13798 import core.thread : Thread; 13799 static if (__VERSION__ < 2076) { 13800 DGNoThrowNoGC(() { 13801 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13802 })(); 13803 } else { 13804 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13805 } 13806 } catch (Exception e) {} 13807 foreach (ref GLNVGcall c; gl.calls[0..gl.ncalls]) if (c.image > 0) glnvg__deleteTexture(gl, c.image); 13808 } 13809 gl.nverts = 0; 13810 gl.npaths = 0; 13811 gl.ncalls = 0; 13812 gl.nuniforms = 0; 13813 gl.msp = 1; 13814 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13815 } 13816 13817 void glnvg__renderCancel (void* uptr) nothrow @trusted @nogc { 13818 glnvg__renderCancelInternal(cast(GLNVGcontext*)uptr, true); 13819 } 13820 13821 GLenum glnvg_convertBlendFuncFactor (NVGBlendFactor factor) pure nothrow @trusted @nogc { 13822 if (factor == NVGBlendFactor.Zero) return GL_ZERO; 13823 if (factor == NVGBlendFactor.One) return GL_ONE; 13824 if (factor == NVGBlendFactor.SrcColor) return GL_SRC_COLOR; 13825 if (factor == NVGBlendFactor.OneMinusSrcColor) return GL_ONE_MINUS_SRC_COLOR; 13826 if (factor == NVGBlendFactor.DstColor) return GL_DST_COLOR; 13827 if (factor == NVGBlendFactor.OneMinusDstColor) return GL_ONE_MINUS_DST_COLOR; 13828 if (factor == NVGBlendFactor.SrcAlpha) return GL_SRC_ALPHA; 13829 if (factor == NVGBlendFactor.OneMinusSrcAlpha) return GL_ONE_MINUS_SRC_ALPHA; 13830 if (factor == NVGBlendFactor.DstAlpha) return GL_DST_ALPHA; 13831 if (factor == NVGBlendFactor.OneMinusDstAlpha) return GL_ONE_MINUS_DST_ALPHA; 13832 if (factor == NVGBlendFactor.SrcAlphaSaturate) return GL_SRC_ALPHA_SATURATE; 13833 return GL_INVALID_ENUM; 13834 } 13835 13836 GLNVGblend glnvg__buildBlendFunc (NVGCompositeOperationState op) pure nothrow @trusted @nogc { 13837 GLNVGblend res; 13838 res.simple = op.simple; 13839 res.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB); 13840 res.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB); 13841 res.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha); 13842 res.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha); 13843 if (res.simple) { 13844 if (res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) { 13845 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM; 13846 } 13847 } else { 13848 if (res.srcRGB == GL_INVALID_ENUM || res.dstRGB == GL_INVALID_ENUM || res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) { 13849 res.simple = true; 13850 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM; 13851 } 13852 } 13853 return res; 13854 } 13855 13856 void glnvg__blendCompositeOperation() (GLNVGcontext* gl, in auto ref GLNVGblend op) nothrow @trusted @nogc { 13857 //glBlendFuncSeparate(glnvg_convertBlendFuncFactor(op.srcRGB), glnvg_convertBlendFuncFactor(op.dstRGB), glnvg_convertBlendFuncFactor(op.srcAlpha), glnvg_convertBlendFuncFactor(op.dstAlpha)); 13858 static if (NANOVG_GL_USE_STATE_FILTER) { 13859 if (gl.blendFunc.simple == op.simple) { 13860 if (op.simple) { 13861 if (gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return; 13862 } else { 13863 if (gl.blendFunc.srcRGB == op.srcRGB && gl.blendFunc.dstRGB == op.dstRGB && gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return; 13864 } 13865 } 13866 gl.blendFunc = op; 13867 } 13868 if (op.simple) { 13869 if (op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) { 13870 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13871 } else { 13872 glBlendFunc(op.srcAlpha, op.dstAlpha); 13873 } 13874 } else { 13875 if (op.srcRGB == GL_INVALID_ENUM || op.dstRGB == GL_INVALID_ENUM || op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) { 13876 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13877 } else { 13878 glBlendFuncSeparate(op.srcRGB, op.dstRGB, op.srcAlpha, op.dstAlpha); 13879 } 13880 } 13881 } 13882 13883 void glnvg__renderSetAffine (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc { 13884 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13885 GLNVGcall* call; 13886 // if last operation was GLNVG_AFFINE, simply replace the matrix 13887 if (gl.ncalls > 0 && gl.calls[gl.ncalls-1].type == GLNVG_AFFINE) { 13888 call = &gl.calls[gl.ncalls-1]; 13889 } else { 13890 call = glnvg__allocCall(gl); 13891 if (call is null) return; 13892 call.type = GLNVG_AFFINE; 13893 } 13894 call.affine.mat.ptr[0..6] = mat.mat.ptr[0..6]; 13895 } 13896 13897 version(nanovega_debug_clipping) public __gshared bool nanovegaClipDebugDump = false; 13898 13899 void glnvg__renderFlush (void* uptr) nothrow @trusted @nogc { 13900 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13901 if (!gl.inFrame) assert(0, "NanoVega: internal driver error"); 13902 try { 13903 import core.thread : Thread; 13904 static if (__VERSION__ < 2076) { 13905 DGNoThrowNoGC(() { 13906 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13907 })(); 13908 } else { 13909 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13910 } 13911 } catch (Exception e) {} 13912 scope(exit) gl.inFrame = false; 13913 13914 glnvg__resetError!true(gl); 13915 { 13916 int vv = 0; 13917 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &vv); 13918 if (glGetError() || vv < 0) vv = 0; 13919 gl.mainFBO = cast(uint)vv; 13920 } 13921 13922 enum ShaderType { None, Fill, Clip } 13923 auto lastShader = ShaderType.None; 13924 if (gl.ncalls > 0) { 13925 gl.msp = 1; 13926 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13927 13928 // Setup require GL state. 13929 glUseProgram(gl.shader.prog); 13930 13931 glActiveTexture(GL_TEXTURE1); 13932 glBindTexture(GL_TEXTURE_2D, 0); 13933 glActiveTexture(GL_TEXTURE0); 13934 glnvg__resetFBOClipTextureCache(gl); 13935 13936 //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13937 static if (NANOVG_GL_USE_STATE_FILTER) { 13938 gl.blendFunc.simple = true; 13939 gl.blendFunc.srcRGB = gl.blendFunc.dstRGB = gl.blendFunc.srcAlpha = gl.blendFunc.dstAlpha = GL_INVALID_ENUM; 13940 } 13941 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // just in case 13942 glEnable(GL_CULL_FACE); 13943 glCullFace(GL_BACK); 13944 glFrontFace(GL_CCW); 13945 glEnable(GL_BLEND); 13946 glDisable(GL_DEPTH_TEST); 13947 glDisable(GL_SCISSOR_TEST); 13948 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13949 glStencilMask(0xffffffff); 13950 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13951 glStencilFunc(GL_ALWAYS, 0, 0xffffffff); 13952 glActiveTexture(GL_TEXTURE0); 13953 glBindTexture(GL_TEXTURE_2D, 0); 13954 static if (NANOVG_GL_USE_STATE_FILTER) { 13955 gl.boundTexture = 0; 13956 gl.stencilMask = 0xffffffff; 13957 gl.stencilFunc = GL_ALWAYS; 13958 gl.stencilFuncRef = 0; 13959 gl.stencilFuncMask = 0xffffffff; 13960 } 13961 glnvg__checkError(gl, "OpenGL setup"); 13962 13963 // Upload vertex data 13964 glBindBuffer(GL_ARRAY_BUFFER, gl.vertBuf); 13965 // ensure that there's space for at least 4 vertices in case we need to draw a quad (e. g. framebuffer copy) 13966 glBufferData(GL_ARRAY_BUFFER, (gl.nverts < 4 ? 4 : gl.nverts) * NVGVertex.sizeof, gl.verts, GL_STREAM_DRAW); 13967 glEnableVertexAttribArray(0); 13968 glEnableVertexAttribArray(1); 13969 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)cast(usize)0); 13970 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)(0+2*float.sizeof)); 13971 glnvg__checkError(gl, "vertex data uploading"); 13972 13973 // Set view and texture just once per frame. 13974 glUniform1i(gl.shader.loc[GLNVGuniformLoc.Tex], 0); 13975 if (gl.shader.loc[GLNVGuniformLoc.ClipTex] != -1) { 13976 //{ import core.stdc.stdio; printf("%d\n", gl.shader.loc[GLNVGuniformLoc.ClipTex]); } 13977 glUniform1i(gl.shader.loc[GLNVGuniformLoc.ClipTex], 1); 13978 } 13979 if (gl.shader.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shader.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 13980 glnvg__checkError(gl, "render shader setup"); 13981 13982 // Reset affine transformations. 13983 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, NVGMatrix.IdentityMat.ptr); 13984 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, NVGMatrix.IdentityMat.ptr+4); 13985 glnvg__checkError(gl, "affine setup"); 13986 13987 // set clip shaders params 13988 // fill 13989 glUseProgram(gl.shaderFillFBO.prog); 13990 glnvg__checkError(gl, "clip shaders setup (fill 0)"); 13991 if (gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 13992 glnvg__checkError(gl, "clip shaders setup (fill 1)"); 13993 // copy 13994 glUseProgram(gl.shaderCopyFBO.prog); 13995 glnvg__checkError(gl, "clip shaders setup (copy 0)"); 13996 if (gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 13997 glnvg__checkError(gl, "clip shaders setup (copy 1)"); 13998 //glUniform1i(gl.shaderFillFBO.loc[GLNVGuniformLoc.Tex], 0); 13999 glUniform1i(gl.shaderCopyFBO.loc[GLNVGuniformLoc.Tex], 0); 14000 glnvg__checkError(gl, "clip shaders setup (copy 2)"); 14001 // restore render shader 14002 glUseProgram(gl.shader.prog); 14003 14004 //{ 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]); } 14005 14006 gl.lastAffine.identity; 14007 14008 foreach (int i; 0..gl.ncalls) { 14009 GLNVGcall* call = &gl.calls[i]; 14010 switch (call.type) { 14011 case GLNVG_FILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__fill(gl, call); break; 14012 case GLNVG_CONVEXFILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__convexFill(gl, call); break; 14013 case GLNVG_STROKE: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__stroke(gl, call); break; 14014 case GLNVG_TRIANGLES: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__triangles(gl, call); break; 14015 case GLNVG_AFFINE: gl.lastAffine = call.affine; glnvg__affine(gl, call); break; 14016 // clip region management 14017 case GLNVG_PUSHCLIP: 14018 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]); } 14019 if (gl.msp >= gl.maskStack.length) assert(0, "NanoVega: mask stack overflow in OpenGL backend"); 14020 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.DontMask) { 14021 gl.maskStack.ptr[gl.msp++] = GLMaskState.DontMask; 14022 } else { 14023 gl.maskStack.ptr[gl.msp++] = GLMaskState.Uninitialized; 14024 } 14025 // no need to reset FBO cache here, as nothing was changed 14026 break; 14027 case GLNVG_POPCLIP: 14028 if (gl.msp <= 1) assert(0, "NanoVega: mask stack underflow in OpenGL backend"); 14029 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]); } 14030 --gl.msp; 14031 assert(gl.msp > 0); 14032 //{ import core.stdc.stdio; printf("popped; new msp is %d; state is %d\n", gl.msp, gl.maskStack.ptr[gl.msp]); } 14033 // check popped item 14034 final switch (gl.maskStack.ptr[gl.msp]) { 14035 case GLMaskState.DontMask: 14036 // if last FBO was "don't mask", reset cache if current is not "don't mask" 14037 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) { 14038 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14039 glnvg__resetFBOClipTextureCache(gl); 14040 } 14041 break; 14042 case GLMaskState.Uninitialized: 14043 // if last FBO texture was uninitialized, it means that nothing was changed, 14044 // so we can keep using cached FBO 14045 break; 14046 case GLMaskState.Initialized: 14047 // if last FBO was initialized, it means that something was definitely changed 14048 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14049 glnvg__resetFBOClipTextureCache(gl); 14050 break; 14051 case GLMaskState.JustCleared: assert(0, "NanoVega: internal FBO stack error"); 14052 } 14053 break; 14054 case GLNVG_RESETCLIP: 14055 // mark current mask as "don't mask" 14056 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]); } 14057 if (gl.msp > 0) { 14058 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) { 14059 gl.maskStack.ptr[gl.msp-1] = GLMaskState.DontMask; 14060 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14061 glnvg__resetFBOClipTextureCache(gl); 14062 } 14063 } 14064 break; 14065 case GLNVG_CLIP_DDUMP_ON: 14066 version(nanovega_debug_clipping) nanovegaClipDebugDump = true; 14067 break; 14068 case GLNVG_CLIP_DDUMP_OFF: 14069 version(nanovega_debug_clipping) nanovegaClipDebugDump = false; 14070 break; 14071 case GLNVG_NONE: break; 14072 default: 14073 { 14074 import core.stdc.stdio; stderr.fprintf("NanoVega FATAL: invalid command in OpenGL backend: %d\n", call.type); 14075 } 14076 assert(0, "NanoVega: invalid command in OpenGL backend (fatal internal error)"); 14077 } 14078 // and free texture, why not 14079 glnvg__deleteTexture(gl, call.image); 14080 } 14081 14082 glDisableVertexAttribArray(0); 14083 glDisableVertexAttribArray(1); 14084 glDisable(GL_CULL_FACE); 14085 glBindBuffer(GL_ARRAY_BUFFER, 0); 14086 glUseProgram(0); 14087 glnvg__bindTexture(gl, 0); 14088 } 14089 14090 // this will do all necessary cleanup 14091 glnvg__renderCancelInternal(gl, false); // no need to clear textures 14092 } 14093 14094 int glnvg__maxVertCount (const(NVGpath)* paths, int npaths) nothrow @trusted @nogc { 14095 int count = 0; 14096 foreach (int i; 0..npaths) { 14097 count += paths[i].nfill; 14098 count += paths[i].nstroke; 14099 } 14100 return count; 14101 } 14102 14103 GLNVGcall* glnvg__allocCall (GLNVGcontext* gl) nothrow @trusted @nogc { 14104 GLNVGcall* ret = null; 14105 if (gl.ncalls+1 > gl.ccalls) { 14106 GLNVGcall* calls; 14107 int ccalls = glnvg__maxi(gl.ncalls+1, 128)+gl.ccalls/2; // 1.5x Overallocate 14108 calls = cast(GLNVGcall*)realloc(gl.calls, GLNVGcall.sizeof*ccalls); 14109 if (calls is null) return null; 14110 gl.calls = calls; 14111 gl.ccalls = ccalls; 14112 } 14113 ret = &gl.calls[gl.ncalls++]; 14114 memset(ret, 0, GLNVGcall.sizeof); 14115 return ret; 14116 } 14117 14118 int glnvg__allocPaths (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14119 int ret = 0; 14120 if (gl.npaths+n > gl.cpaths) { 14121 GLNVGpath* paths; 14122 int cpaths = glnvg__maxi(gl.npaths+n, 128)+gl.cpaths/2; // 1.5x Overallocate 14123 paths = cast(GLNVGpath*)realloc(gl.paths, GLNVGpath.sizeof*cpaths); 14124 if (paths is null) return -1; 14125 gl.paths = paths; 14126 gl.cpaths = cpaths; 14127 } 14128 ret = gl.npaths; 14129 gl.npaths += n; 14130 return ret; 14131 } 14132 14133 int glnvg__allocVerts (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14134 int ret = 0; 14135 if (gl.nverts+n > gl.cverts) { 14136 NVGVertex* verts; 14137 int cverts = glnvg__maxi(gl.nverts+n, 4096)+gl.cverts/2; // 1.5x Overallocate 14138 verts = cast(NVGVertex*)realloc(gl.verts, NVGVertex.sizeof*cverts); 14139 if (verts is null) return -1; 14140 gl.verts = verts; 14141 gl.cverts = cverts; 14142 } 14143 ret = gl.nverts; 14144 gl.nverts += n; 14145 return ret; 14146 } 14147 14148 int glnvg__allocFragUniforms (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14149 int ret = 0, structSize = gl.fragSize; 14150 if (gl.nuniforms+n > gl.cuniforms) { 14151 ubyte* uniforms; 14152 int cuniforms = glnvg__maxi(gl.nuniforms+n, 128)+gl.cuniforms/2; // 1.5x Overallocate 14153 uniforms = cast(ubyte*)realloc(gl.uniforms, structSize*cuniforms); 14154 if (uniforms is null) return -1; 14155 gl.uniforms = uniforms; 14156 gl.cuniforms = cuniforms; 14157 } 14158 ret = gl.nuniforms*structSize; 14159 gl.nuniforms += n; 14160 return ret; 14161 } 14162 14163 GLNVGfragUniforms* nvg__fragUniformPtr (GLNVGcontext* gl, int i) nothrow @trusted @nogc { 14164 return cast(GLNVGfragUniforms*)&gl.uniforms[i]; 14165 } 14166 14167 void glnvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc { 14168 vtx.x = x; 14169 vtx.y = y; 14170 vtx.u = u; 14171 vtx.v = v; 14172 } 14173 14174 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 { 14175 if (npaths < 1) return; 14176 14177 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14178 GLNVGcall* call = glnvg__allocCall(gl); 14179 NVGVertex* quad; 14180 GLNVGfragUniforms* frag; 14181 int maxverts, offset; 14182 14183 if (call is null) return; 14184 14185 call.type = GLNVG_FILL; 14186 call.evenOdd = evenOdd; 14187 call.clipmode = clipmode; 14188 //if (clipmode != NVGClipMode.None) { import core.stdc.stdio; printf("CLIP!\n"); } 14189 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14190 call.triangleCount = 4; 14191 call.pathOffset = glnvg__allocPaths(gl, npaths); 14192 if (call.pathOffset == -1) goto error; 14193 call.pathCount = npaths; 14194 call.image = paint.image.id; 14195 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14196 14197 if (npaths == 1 && paths[0].convex) { 14198 call.type = GLNVG_CONVEXFILL; 14199 call.triangleCount = 0; // Bounding box fill quad not needed for convex fill 14200 } 14201 14202 // Allocate vertices for all the paths. 14203 maxverts = glnvg__maxVertCount(paths, npaths)+call.triangleCount; 14204 offset = glnvg__allocVerts(gl, maxverts); 14205 if (offset == -1) goto error; 14206 14207 foreach (int i; 0..npaths) { 14208 GLNVGpath* copy = &gl.paths[call.pathOffset+i]; 14209 const(NVGpath)* path = &paths[i]; 14210 memset(copy, 0, GLNVGpath.sizeof); 14211 if (path.nfill > 0) { 14212 copy.fillOffset = offset; 14213 copy.fillCount = path.nfill; 14214 memcpy(&gl.verts[offset], path.fill, NVGVertex.sizeof*path.nfill); 14215 offset += path.nfill; 14216 } 14217 if (path.nstroke > 0) { 14218 copy.strokeOffset = offset; 14219 copy.strokeCount = path.nstroke; 14220 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke); 14221 offset += path.nstroke; 14222 } 14223 } 14224 14225 // Setup uniforms for draw calls 14226 if (call.type == GLNVG_FILL) { 14227 import core.stdc.string : memcpy; 14228 // Quad 14229 call.triangleOffset = offset; 14230 quad = &gl.verts[call.triangleOffset]; 14231 glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f); 14232 glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f); 14233 glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f); 14234 glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f); 14235 // Get uniform 14236 call.uniformOffset = glnvg__allocFragUniforms(gl, 2); 14237 if (call.uniformOffset == -1) goto error; 14238 // Simple shader for stencil 14239 frag = nvg__fragUniformPtr(gl, call.uniformOffset); 14240 memset(frag, 0, (*frag).sizeof); 14241 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f); 14242 memcpy(nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), frag, (*frag).sizeof); 14243 frag.strokeThr = -1.0f; 14244 frag.type = NSVG_SHADER_SIMPLE; 14245 // Fill shader 14246 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, fringe, fringe, -1.0f); 14247 } else { 14248 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14249 if (call.uniformOffset == -1) goto error; 14250 // Fill shader 14251 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f); 14252 } 14253 14254 return; 14255 14256 error: 14257 // We get here if call alloc was ok, but something else is not. 14258 // Roll back the last call to prevent drawing it. 14259 if (gl.ncalls > 0) --gl.ncalls; 14260 } 14261 14262 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 { 14263 if (npaths < 1) return; 14264 14265 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14266 GLNVGcall* call = glnvg__allocCall(gl); 14267 int maxverts, offset; 14268 14269 if (call is null) return; 14270 14271 call.type = GLNVG_STROKE; 14272 call.clipmode = clipmode; 14273 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14274 call.pathOffset = glnvg__allocPaths(gl, npaths); 14275 if (call.pathOffset == -1) goto error; 14276 call.pathCount = npaths; 14277 call.image = paint.image.id; 14278 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14279 14280 // Allocate vertices for all the paths. 14281 maxverts = glnvg__maxVertCount(paths, npaths); 14282 offset = glnvg__allocVerts(gl, maxverts); 14283 if (offset == -1) goto error; 14284 14285 foreach (int i; 0..npaths) { 14286 GLNVGpath* copy = &gl.paths[call.pathOffset+i]; 14287 const(NVGpath)* path = &paths[i]; 14288 memset(copy, 0, GLNVGpath.sizeof); 14289 if (path.nstroke) { 14290 copy.strokeOffset = offset; 14291 copy.strokeCount = path.nstroke; 14292 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke); 14293 offset += path.nstroke; 14294 } 14295 } 14296 14297 if (gl.flags&NVGContextFlag.StencilStrokes) { 14298 // Fill shader 14299 call.uniformOffset = glnvg__allocFragUniforms(gl, 2); 14300 if (call.uniformOffset == -1) goto error; 14301 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); 14302 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f); 14303 } else { 14304 // Fill shader 14305 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14306 if (call.uniformOffset == -1) goto error; 14307 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); 14308 } 14309 14310 return; 14311 14312 error: 14313 // We get here if call alloc was ok, but something else is not. 14314 // Roll back the last call to prevent drawing it. 14315 if (gl.ncalls > 0) --gl.ncalls; 14316 } 14317 14318 void glnvg__renderTriangles (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc { 14319 if (nverts < 1) return; 14320 14321 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14322 GLNVGcall* call = glnvg__allocCall(gl); 14323 GLNVGfragUniforms* frag; 14324 14325 if (call is null) return; 14326 14327 call.type = GLNVG_TRIANGLES; 14328 call.clipmode = clipmode; 14329 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14330 call.image = paint.image.id; 14331 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14332 14333 // Allocate vertices for all the paths. 14334 call.triangleOffset = glnvg__allocVerts(gl, nverts); 14335 if (call.triangleOffset == -1) goto error; 14336 call.triangleCount = nverts; 14337 14338 memcpy(&gl.verts[call.triangleOffset], verts, NVGVertex.sizeof*nverts); 14339 14340 // Fill shader 14341 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14342 if (call.uniformOffset == -1) goto error; 14343 frag = nvg__fragUniformPtr(gl, call.uniformOffset); 14344 glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f); 14345 frag.type = NSVG_SHADER_IMG; 14346 14347 return; 14348 14349 error: 14350 // We get here if call alloc was ok, but something else is not. 14351 // Roll back the last call to prevent drawing it. 14352 if (gl.ncalls > 0) --gl.ncalls; 14353 } 14354 14355 void glnvg__renderDelete (void* uptr) nothrow @trusted @nogc { 14356 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14357 if (gl is null) return; 14358 14359 glnvg__killFBOs(gl); 14360 glnvg__deleteShader(&gl.shader); 14361 glnvg__deleteShader(&gl.shaderFillFBO); 14362 glnvg__deleteShader(&gl.shaderCopyFBO); 14363 14364 if (gl.vertBuf != 0) glDeleteBuffers(1, &gl.vertBuf); 14365 14366 foreach (ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) { 14367 if (tex.id != 0 && (tex.flags&NVGImageFlag.NoDelete) == 0) { 14368 assert(tex.tex != 0); 14369 glDeleteTextures(1, &tex.tex); 14370 } 14371 } 14372 free(gl.textures); 14373 14374 free(gl.paths); 14375 free(gl.verts); 14376 free(gl.uniforms); 14377 free(gl.calls); 14378 14379 free(gl); 14380 } 14381 14382 14383 /** Creates NanoVega contexts for OpenGL2+. 14384 * 14385 * Specify creation flags as additional arguments, like this: 14386 * `nvgCreateContext(NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes);` 14387 * 14388 * If you won't specify any flags, defaults will be used: 14389 * `[NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes]`. 14390 * 14391 * Group: context_management 14392 */ 14393 public NVGContext nvgCreateContext (const(NVGContextFlag)[] flagList...) nothrow @trusted @nogc { 14394 version(aliced) { 14395 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA; 14396 } else { 14397 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes; 14398 } 14399 uint flags = 0; 14400 if (flagList.length != 0) { 14401 foreach (immutable flg; flagList) flags |= (flg != NVGContextFlag.Default ? flg : DefaultFlags); 14402 } else { 14403 flags = DefaultFlags; 14404 } 14405 NVGparams params = void; 14406 NVGContext ctx = null; 14407 version(nanovg_builtin_opengl_bindings) nanovgInitOpenGL(); // why not? 14408 version(nanovg_bindbc_opengl_bindings) nanovgInitOpenGL(); 14409 GLNVGcontext* gl = cast(GLNVGcontext*)malloc(GLNVGcontext.sizeof); 14410 if (gl is null) goto error; 14411 memset(gl, 0, GLNVGcontext.sizeof); 14412 14413 memset(¶ms, 0, params.sizeof); 14414 params.renderCreate = &glnvg__renderCreate; 14415 params.renderCreateTexture = &glnvg__renderCreateTexture; 14416 params.renderTextureIncRef = &glnvg__renderTextureIncRef; 14417 params.renderDeleteTexture = &glnvg__renderDeleteTexture; 14418 params.renderUpdateTexture = &glnvg__renderUpdateTexture; 14419 params.renderGetTextureSize = &glnvg__renderGetTextureSize; 14420 params.renderViewport = &glnvg__renderViewport; 14421 params.renderCancel = &glnvg__renderCancel; 14422 params.renderFlush = &glnvg__renderFlush; 14423 params.renderPushClip = &glnvg__renderPushClip; 14424 params.renderPopClip = &glnvg__renderPopClip; 14425 params.renderResetClip = &glnvg__renderResetClip; 14426 params.renderFill = &glnvg__renderFill; 14427 params.renderStroke = &glnvg__renderStroke; 14428 params.renderTriangles = &glnvg__renderTriangles; 14429 params.renderSetAffine = &glnvg__renderSetAffine; 14430 params.renderDelete = &glnvg__renderDelete; 14431 params.userPtr = gl; 14432 params.edgeAntiAlias = (flags&NVGContextFlag.Antialias ? true : false); 14433 if (flags&(NVGContextFlag.FontAA|NVGContextFlag.FontNoAA)) { 14434 params.fontAA = (flags&NVGContextFlag.FontNoAA ? NVG_INVERT_FONT_AA : !NVG_INVERT_FONT_AA); 14435 } else { 14436 params.fontAA = NVG_INVERT_FONT_AA; 14437 } 14438 14439 gl.flags = flags; 14440 gl.freetexid = -1; 14441 14442 ctx = createInternal(¶ms); 14443 if (ctx is null) goto error; 14444 14445 static if (__VERSION__ < 2076) { 14446 DGNoThrowNoGC(() { import core.thread; gl.mainTID = Thread.getThis.id; })(); 14447 } else { 14448 try { import core.thread; gl.mainTID = Thread.getThis.id; } catch (Exception e) {} 14449 } 14450 14451 return ctx; 14452 14453 error: 14454 // 'gl' is freed by nvgDeleteInternal. 14455 if (ctx !is null) ctx.deleteInternal(); 14456 return null; 14457 } 14458 14459 /// Using OpenGL texture id creates GLNVGtexture and return its id. 14460 /// Group: images 14461 public int glCreateImageFromHandleGL2 (NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc { 14462 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr; 14463 GLNVGtexture* tex = glnvg__allocTexture(gl); 14464 14465 if (tex is null) return 0; 14466 14467 tex.type = NVGtexture.RGBA; 14468 tex.tex = textureId; 14469 tex.flags = imageFlags; 14470 tex.width = w; 14471 tex.height = h; 14472 14473 return tex.id; 14474 } 14475 14476 /// Create NVGImage from OpenGL texture id. 14477 /// Group: images 14478 public NVGImage glCreateImageFromOpenGLTexture(NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc { 14479 auto id = glCreateImageFromHandleGL2(ctx, textureId, w, h, imageFlags); 14480 14481 NVGImage res; 14482 if (id > 0) { 14483 res.id = id; 14484 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); } 14485 res.ctx = ctx; 14486 ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount 14487 } 14488 return res; 14489 } 14490 14491 /// Returns OpenGL texture id for NanoVega image. 14492 /// Group: images 14493 public GLuint glImageHandleGL2 (NVGContext ctx, int image) nothrow @trusted @nogc { 14494 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr; 14495 GLNVGtexture* tex = glnvg__findTexture(gl, image); 14496 return tex.tex; 14497 } 14498 14499 14500 // ////////////////////////////////////////////////////////////////////////// // 14501 private: 14502 14503 static if (NanoVegaHasFontConfig) { 14504 version(nanovg_builtin_fontconfig_bindings) { 14505 pragma(lib, "fontconfig"); 14506 14507 private extern(C) nothrow @trusted @nogc { 14508 enum FC_FILE = "file"; /* String */ 14509 alias FcBool = int; 14510 alias FcChar8 = char; 14511 struct FcConfig; 14512 struct FcPattern; 14513 alias FcMatchKind = int; 14514 enum : FcMatchKind { 14515 FcMatchPattern, 14516 FcMatchFont, 14517 FcMatchScan 14518 } 14519 alias FcResult = int; 14520 enum : FcResult { 14521 FcResultMatch, 14522 FcResultNoMatch, 14523 FcResultTypeMismatch, 14524 FcResultNoId, 14525 FcResultOutOfMemory 14526 } 14527 FcBool FcInit (); 14528 FcBool FcConfigSubstituteWithPat (FcConfig* config, FcPattern* p, FcPattern* p_pat, FcMatchKind kind); 14529 void FcDefaultSubstitute (FcPattern* pattern); 14530 FcBool FcConfigSubstitute (FcConfig* config, FcPattern* p, FcMatchKind kind); 14531 FcPattern* FcFontMatch (FcConfig* config, FcPattern* p, FcResult* result); 14532 FcPattern* FcNameParse (const(FcChar8)* name); 14533 void FcPatternDestroy (FcPattern* p); 14534 FcResult FcPatternGetString (const(FcPattern)* p, const(char)* object, int n, FcChar8** s); 14535 } 14536 } 14537 14538 __gshared bool fontconfigAvailable = false; 14539 // initialize fontconfig 14540 shared static this () { 14541 if (FcInit()) { 14542 fontconfigAvailable = true; 14543 } else { 14544 import core.stdc.stdio : stderr, fprintf; 14545 stderr.fprintf("***NanoVega WARNING: cannot init fontconfig!\n"); 14546 } 14547 } 14548 } 14549 14550 14551 // ////////////////////////////////////////////////////////////////////////// // 14552 public enum BaphometDims = 512.0f; // baphomet icon is 512x512 ([0..511]) 14553 14554 private static immutable ubyte[7641] baphometPath = [ 14555 0x01,0x04,0x06,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x08,0xa0,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43, 14556 0x00,0x80,0xff,0x43,0xa2,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x08,0x00,0x80,0xff, 14557 0x43,0x7a,0x89,0xe5,0x42,0xa0,0x1d,0xc6,0x43,0x00,0x00,0x00,0x00,0x30,0x89,0x7f,0x43,0x00,0x00,0x00, 14558 0x00,0x08,0x7a,0x89,0xe5,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7a,0x89,0xe5,0x42,0x00,0x00, 14559 0x00,0x00,0x30,0x89,0x7f,0x43,0x08,0x00,0x00,0x00,0x00,0xa2,0x1d,0xc6,0x43,0x7a,0x89,0xe5,0x42,0x00, 14560 0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x09,0x06,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd, 14561 0x43,0x08,0x16,0x68,0xb3,0x43,0x72,0x87,0xdd,0x43,0x71,0x87,0xdd,0x43,0x17,0x68,0xb3,0x43,0x71,0x87, 14562 0xdd,0x43,0x30,0x89,0x7f,0x43,0x08,0x71,0x87,0xdd,0x43,0xd2,0x2f,0x18,0x43,0x16,0x68,0xb3,0x43,0x35, 14563 0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x35,0xe2,0x87,0x42,0x08,0xd1,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42, 14564 0x35,0xe2,0x87,0x42,0xd2,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x08,0x35,0xe2,0x87, 14565 0x42,0x17,0x68,0xb3,0x43,0xd1,0x2f,0x18,0x43,0x72,0x87,0xdd,0x43,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd, 14566 0x43,0x09,0x06,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x07,0xa4,0x3f,0x7f,0x43,0x0b,0x86,0xdc,0x43, 14567 0x07,0x6c,0xb9,0xb2,0x43,0xe8,0xd1,0xca,0x42,0x07,0x6e,0x4d,0xa0,0x42,0xa9,0x10,0x9c,0x43,0x07,0xb7, 14568 0x40,0xd7,0x43,0xa9,0x10,0x9c,0x43,0x07,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x09,0x06,0x98,0x42, 14569 0x74,0x43,0xb1,0x8d,0x68,0x43,0x08,0xd7,0x24,0x79,0x43,0xba,0x83,0x6e,0x43,0xa9,0x16,0x7c,0x43,0x56, 14570 0xa1,0x76,0x43,0x74,0x2a,0x7d,0x43,0x44,0x73,0x80,0x43,0x08,0x55,0xd1,0x7e,0x43,0xe3,0xea,0x76,0x43, 14571 0xbc,0x18,0x81,0x43,0x7f,0xa8,0x6e,0x43,0x8f,0x0a,0x84,0x43,0x02,0xfc,0x68,0x43,0x09,0x06,0x92,0x29, 14572 0x8d,0x43,0x73,0xc3,0x67,0x43,0x08,0xa4,0xd9,0x8e,0x43,0xf2,0xa6,0x7a,0x43,0x8f,0x22,0x88,0x43,0x75, 14573 0x2a,0x7d,0x43,0x42,0x7f,0x82,0x43,0x08,0xc8,0x88,0x43,0x09,0x06,0xc1,0x79,0x74,0x43,0x50,0x64,0x89, 14574 0x43,0x08,0x68,0x2d,0x72,0x43,0xee,0x21,0x81,0x43,0xcd,0x97,0x55,0x43,0xe6,0xf1,0x7b,0x43,0x91,0xec, 14575 0x5d,0x43,0xa8,0xc7,0x6a,0x43,0x09,0x06,0xfa,0xa5,0x52,0x43,0x60,0x97,0x7c,0x43,0x08,0x19,0xff,0x50, 14576 0x43,0xe9,0x6e,0x8a,0x43,0xb0,0xbd,0x70,0x43,0x4c,0x51,0x82,0x43,0x04,0xeb,0x69,0x43,0x66,0x0f,0x8e, 14577 0x43,0x09,0x06,0x17,0xbf,0x71,0x43,0x2c,0x58,0x94,0x43,0x08,0x1c,0x96,0x6e,0x43,0x61,0x68,0x99,0x43, 14578 0x2d,0x3a,0x6e,0x43,0xc8,0x81,0x9e,0x43,0xb7,0x9b,0x72,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0x30,0xdb, 14579 0x82,0x43,0xdb,0xe9,0x93,0x43,0x08,0x11,0x82,0x84,0x43,0x61,0x68,0x99,0x43,0xe8,0x4a,0x84,0x43,0x8e, 14580 0xa6,0x9e,0x43,0x42,0x7f,0x82,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0xc4,0x02,0x85,0x43,0xd1,0x0b,0x92, 14581 0x43,0x08,0xd6,0xb2,0x86,0x43,0x34,0x1e,0x92,0x43,0x4f,0x58,0x87,0x43,0xa4,0xf1,0x92,0x43,0x03,0xd9, 14582 0x87,0x43,0x7b,0xc6,0x94,0x43,0x09,0x06,0x87,0x3e,0x64,0x43,0x31,0x3b,0x93,0x43,0x08,0x3b,0xbf,0x64, 14583 0x43,0x6f,0xf9,0x91,0x43,0x96,0x0b,0x67,0x43,0xc5,0x4a,0x91,0x43,0xcf,0xfe,0x6a,0x43,0x31,0x2f,0x91, 14584 0x43,0x09,0x06,0x16,0x74,0xb5,0x43,0x08,0xec,0x8e,0x43,0x08,0x1b,0x4b,0xb2,0x43,0xee,0x5d,0x8b,0x43, 14585 0x48,0x4d,0xad,0x43,0x12,0xa6,0x8a,0x43,0xf3,0xd7,0xa7,0x43,0x74,0xb8,0x8a,0x43,0x08,0x8c,0xb2,0xa0, 14586 0x43,0xcd,0xf8,0x8a,0x43,0x68,0x46,0x9b,0x43,0x79,0x8f,0x87,0x43,0x49,0xc9,0x96,0x43,0xe9,0x3e,0x82, 14587 0x43,0x08,0x60,0x5c,0x97,0x43,0xa1,0xde,0x8b,0x43,0x4e,0xa0,0x93,0x43,0x31,0x3b,0x93,0x43,0x9f,0xea, 14588 0x8d,0x43,0x27,0x8d,0x99,0x43,0x08,0x07,0xe0,0x8c,0x43,0x06,0x34,0x9b,0x43,0x38,0xe9,0x8c,0x43,0x46, 14589 0x0a,0x9e,0x43,0x3d,0xcc,0x8b,0x43,0xb2,0x06,0xa2,0x43,0x08,0xf1,0x40,0x8a,0x43,0xb0,0x12,0xa4,0x43, 14590 0x39,0xd1,0x88,0x43,0x76,0x43,0xa6,0x43,0xfa,0x06,0x88,0x43,0xa4,0x75,0xa9,0x43,0x08,0x19,0x6c,0x88, 14591 0x43,0x9f,0x9e,0xac,0x43,0x66,0xeb,0x87,0x43,0x44,0x76,0xb0,0x43,0x6b,0xce,0x86,0x43,0x3b,0xbc,0xb4, 14592 0x43,0x08,0xa9,0x8c,0x85,0x43,0x06,0xd0,0xb5,0x43,0xfa,0xee,0x83,0x43,0x74,0xa3,0xb6,0x43,0x3d,0x90, 14593 0x81,0x43,0x31,0xf6,0xb6,0x43,0x08,0x9d,0x61,0x7d,0x43,0xee,0x48,0xb7,0x43,0x3b,0x1f,0x75,0x43,0xcf, 14594 0xe3,0xb6,0x43,0xee,0x6f,0x6d,0x43,0x68,0xe2,0xb5,0x43,0x08,0xd4,0xed,0x6b,0x43,0x87,0x2f,0xb2,0x43, 14595 0x0e,0xc9,0x6b,0x43,0xa7,0x7c,0xae,0x43,0x98,0xfa,0x67,0x43,0xab,0x53,0xab,0x43,0x08,0x25,0x2c,0x64, 14596 0x43,0x33,0xa2,0xa8,0x43,0x40,0x96,0x61,0x43,0xc3,0xc2,0xa5,0x43,0x64,0xde,0x60,0x43,0xfa,0xa2,0xa2, 14597 0x43,0x08,0xb0,0x5d,0x60,0x43,0x06,0x4c,0x9f,0x43,0x9a,0xca,0x5f,0x43,0x38,0x3d,0x9b,0x43,0x3b,0x8f, 14598 0x5c,0x43,0x85,0xb0,0x98,0x43,0x08,0x42,0x36,0x51,0x43,0x3d,0xf0,0x91,0x43,0xcd,0x4f,0x49,0x43,0xdb, 14599 0xb9,0x8b,0x43,0xe0,0xdb,0x44,0x43,0x42,0x8b,0x84,0x43,0x08,0x7e,0xc9,0x44,0x43,0x8a,0x57,0x8d,0x43, 14600 0xbc,0x6c,0x0f,0x43,0x23,0x62,0x8e,0x43,0xf5,0x17,0x07,0x43,0xc5,0x3e,0x8f,0x43,0x09,0x06,0xe0,0xea, 14601 0x76,0x43,0xab,0xef,0xc5,0x43,0x08,0x12,0x00,0x79,0x43,0xab,0xcb,0xbf,0x43,0x79,0xb9,0x6d,0x43,0x7e, 14602 0x8d,0xba,0x43,0xee,0x6f,0x6d,0x43,0x98,0xeb,0xb5,0x43,0x08,0xe0,0x02,0x7b,0x43,0x5f,0x1c,0xb8,0x43, 14603 0x85,0x2c,0x82,0x43,0xe9,0x65,0xb8,0x43,0xd6,0xb2,0x86,0x43,0xc6,0x05,0xb5,0x43,0x08,0x03,0xcd,0x85, 14604 0x43,0x5a,0x39,0xb9,0x43,0xe4,0x4f,0x81,0x43,0xdb,0xd4,0xbf,0x43,0xdf,0x6c,0x82,0x43,0xbc,0x93,0xc5, 14605 0x43,0x09,0x06,0xf0,0xd0,0x22,0x43,0x5d,0x19,0x08,0x43,0x08,0xbc,0xab,0x49,0x43,0x4a,0x35,0x29,0x43, 14606 0xcb,0xf7,0x65,0x43,0xce,0x37,0x45,0x43,0x0e,0x99,0x63,0x43,0x67,0xc6,0x5c,0x43,0x09,0x06,0x05,0x94, 14607 0xab,0x43,0xc2,0x13,0x04,0x43,0x08,0x9f,0x26,0x98,0x43,0x11,0x42,0x25,0x43,0x97,0x00,0x8a,0x43,0x32, 14608 0x32,0x41,0x43,0xf5,0x2f,0x8b,0x43,0xc7,0xc0,0x58,0x43,0x09,0x06,0x8f,0x85,0x48,0x43,0xe0,0xa8,0x8c, 14609 0x43,0x08,0x55,0xaa,0x48,0x43,0xe0,0xa8,0x8c,0x43,0x6b,0x3d,0x49,0x43,0xc1,0x43,0x8c,0x43,0x31,0x62, 14610 0x49,0x43,0xc1,0x43,0x8c,0x43,0x08,0x2f,0xe3,0x2f,0x43,0xad,0xe7,0x98,0x43,0xff,0x0d,0x0d,0x43,0xad, 14611 0xf3,0x9a,0x43,0xf0,0xaf,0xcc,0x42,0x74,0x00,0x97,0x43,0x08,0xbb,0xa2,0xf7,0x42,0x93,0x4d,0x93,0x43, 14612 0x5e,0x19,0x08,0x43,0x5a,0x2a,0x87,0x43,0x23,0x6e,0x10,0x43,0x42,0x97,0x86,0x43,0x08,0xca,0xe8,0x33, 14613 0x43,0x1b,0x3c,0x80,0x43,0x80,0xe8,0x4d,0x43,0xda,0xf4,0x70,0x43,0xae,0x0e,0x4f,0x43,0x2b,0x1b,0x65, 14614 0x43,0x08,0x66,0x96,0x54,0x43,0xa3,0xe1,0x3b,0x43,0x4e,0xc4,0x19,0x43,0xa0,0x1a,0x16,0x43,0x10,0xe2, 14615 0x14,0x43,0x26,0x14,0xe0,0x42,0x08,0x5c,0x91,0x1c,0x43,0xcb,0x27,0xee,0x42,0xa9,0x40,0x24,0x43,0x71, 14616 0x3b,0xfc,0x42,0xf3,0xef,0x2b,0x43,0x8b,0x27,0x05,0x43,0x08,0xe2,0x4b,0x2c,0x43,0x48,0x86,0x07,0x43, 14617 0x79,0x62,0x2f,0x43,0x05,0xe5,0x09,0x43,0x55,0x32,0x34,0x43,0xa0,0xd2,0x09,0x43,0x08,0x74,0xa3,0x36, 14618 0x43,0x3a,0xd1,0x08,0x43,0x7e,0x81,0x38,0x43,0x09,0xd4,0x0a,0x43,0x0d,0xba,0x39,0x43,0xa0,0xea,0x0d, 14619 0x43,0x08,0x6f,0xe4,0x3d,0x43,0x43,0xc7,0x0e,0x43,0xd6,0xe5,0x3e,0x43,0xc4,0x4a,0x11,0x43,0x55,0x7a, 14620 0x40,0x43,0x59,0x72,0x13,0x43,0x08,0x55,0x92,0x44,0x43,0xbf,0x73,0x14,0x43,0x23,0x95,0x46,0x43,0xa5, 14621 0x09,0x17,0x43,0xe0,0xf3,0x48,0x43,0xfe,0x55,0x19,0x43,0x08,0xcd,0x4f,0x49,0x43,0xaa,0x10,0x1c,0x43, 14622 0x61,0x77,0x4b,0x43,0xfe,0x6d,0x1d,0x43,0x80,0xe8,0x4d,0x43,0x2b,0x94,0x1e,0x43,0x08,0x58,0xc9,0x51, 14623 0x43,0x41,0x27,0x1f,0x43,0x9b,0x82,0x53,0x43,0x35,0x72,0x20,0x43,0x53,0xf2,0x54,0x43,0x88,0xcf,0x21, 14624 0x43,0x08,0x7b,0x29,0x55,0x43,0xe8,0x0a,0x25,0x43,0xb2,0x2d,0x58,0x43,0xef,0xe8,0x26,0x43,0x9b,0xb2, 14625 0x5b,0x43,0xd0,0x8f,0x28,0x43,0x08,0x5f,0xef,0x5f,0x43,0xeb,0x11,0x2a,0x43,0xfd,0xdc,0x5f,0x43,0x6e, 14626 0x95,0x2c,0x43,0x3b,0xa7,0x60,0x43,0x2b,0xf4,0x2e,0x43,0x08,0x06,0xbb,0x61,0x43,0xfd,0xe5,0x31,0x43, 14627 0xe7,0x61,0x63,0x43,0xef,0x30,0x33,0x43,0x53,0x52,0x65,0x43,0xa3,0xb1,0x33,0x43,0x08,0x12,0xa0,0x68, 14628 0x43,0x7f,0x69,0x34,0x43,0x40,0xc6,0x69,0x43,0x64,0xff,0x36,0x43,0x7e,0x90,0x6a,0x43,0x71,0xcc,0x39, 14629 0x43,0x08,0xbc,0x5a,0x6b,0x43,0x51,0x73,0x3b,0x43,0xc1,0x49,0x6c,0x43,0xa5,0xd0,0x3c,0x43,0xe0,0xba, 14630 0x6e,0x43,0xb8,0x74,0x3c,0x43,0x08,0x6b,0x1c,0x73,0x43,0x13,0xc1,0x3e,0x43,0x40,0xf6,0x71,0x43,0xce, 14631 0x1f,0x41,0x43,0x55,0x89,0x72,0x43,0x8d,0x7e,0x43,0x43,0x08,0x68,0x2d,0x72,0x43,0x89,0xae,0x4b,0x43, 14632 0xc1,0x79,0x74,0x43,0xcb,0x78,0x4c,0x43,0x55,0xa1,0x76,0x43,0x5b,0xb1,0x4d,0x43,0x08,0xa2,0x38,0x7a, 14633 0x43,0xd1,0x56,0x4e,0x43,0x85,0xb6,0x78,0x43,0xb1,0x15,0x54,0x43,0x83,0xc7,0x77,0x43,0x89,0x0e,0x5c, 14634 0x43,0x08,0xcf,0x46,0x77,0x43,0x0f,0x81,0x5f,0x43,0x1a,0xde,0x7a,0x43,0xce,0xc7,0x5d,0x43,0x42,0x73, 14635 0x80,0x43,0x99,0xc3,0x5a,0x43,0x08,0x85,0x2c,0x82,0x43,0xf6,0xe6,0x59,0x43,0x81,0x3d,0x81,0x43,0x16, 14636 0x10,0x50,0x43,0xd6,0x8e,0x80,0x43,0x5b,0x99,0x49,0x43,0x08,0xc4,0xea,0x80,0x43,0x22,0x95,0x46,0x43, 14637 0xfa,0xe2,0x81,0x43,0xda,0xec,0x43,0x43,0x78,0x77,0x83,0x43,0xe4,0xb2,0x41,0x43,0x08,0x8a,0x27,0x85, 14638 0x43,0x86,0x77,0x3e,0x43,0x0c,0x9f,0x85,0x43,0x07,0xf4,0x3b,0x43,0x8f,0x16,0x86,0x43,0xe6,0x82,0x39, 14639 0x43,0x08,0x85,0x44,0x86,0x43,0x37,0xd9,0x35,0x43,0x1e,0x4f,0x87,0x43,0xe1,0x7b,0x34,0x43,0xdf,0x90, 14640 0x88,0x43,0xb6,0x55,0x33,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0xe5,0x31,0x43,0xfa,0x12,0x8a,0x43,0xbf, 14641 0x03,0x2d,0x43,0x19,0x78,0x8a,0x43,0x45,0x5e,0x2c,0x43,0x08,0x03,0xf1,0x8b,0x43,0xac,0x47,0x29,0x43, 14642 0x2f,0x17,0x8d,0x43,0x45,0x46,0x28,0x43,0xc8,0x21,0x8e,0x43,0x30,0xb3,0x27,0x43,0x08,0xa9,0xc8,0x8f, 14643 0x43,0xef,0xe8,0x26,0x43,0xbf,0x5b,0x90,0x43,0x5b,0xc1,0x24,0x43,0x10,0xca,0x90,0x43,0xa0,0x62,0x22, 14644 0x43,0x08,0x26,0x5d,0x91,0x43,0xbb,0xcc,0x1f,0x43,0xf0,0x70,0x92,0x43,0x78,0x13,0x1e,0x43,0x77,0xd7, 14645 0x93,0x43,0x73,0x24,0x1d,0x43,0x08,0x65,0x3f,0x96,0x43,0xce,0x58,0x1b,0x43,0xbe,0x7f,0x96,0x43,0xbf, 14646 0x8b,0x18,0x43,0x60,0x5c,0x97,0x43,0xb6,0xad,0x16,0x43,0x08,0xba,0xa8,0x99,0x43,0x78,0xcb,0x11,0x43, 14647 0x49,0xe1,0x9a,0x43,0x78,0xcb,0x11,0x43,0x01,0x51,0x9c,0x43,0x73,0xdc,0x10,0x43,0x08,0x72,0x24,0x9d, 14648 0x43,0xd2,0xff,0x0f,0x43,0x1c,0xd3,0x9d,0x43,0x07,0xec,0x0e,0x43,0xeb,0xc9,0x9d,0x43,0xe8,0x7a,0x0c, 14649 0x43,0x08,0x60,0x80,0x9d,0x43,0xd7,0xbe,0x08,0x43,0x4d,0xe8,0x9f,0x43,0x86,0x50,0x08,0x43,0x25,0xbd, 14650 0xa1,0x43,0x5b,0x2a,0x07,0x43,0x08,0x99,0x7f,0xa3,0x43,0xc9,0xf1,0x05,0x43,0x48,0x1d,0xa5,0x43,0x86, 14651 0x38,0x04,0x43,0x6c,0x71,0xa6,0x43,0x18,0x59,0x01,0x43,0x08,0x32,0x96,0xa6,0x43,0x6e,0x64,0xff,0x42, 14652 0x48,0x29,0xa7,0x43,0xed,0xcf,0xfd,0x42,0x5f,0xbc,0xa7,0x43,0x71,0x3b,0xfc,0x42,0x08,0xf3,0xe3,0xa9, 14653 0x43,0xf7,0x7d,0xf7,0x42,0xd8,0x6d,0xaa,0x43,0x45,0xe5,0xf2,0x42,0x48,0x41,0xab,0x43,0xcb,0x27,0xee, 14654 0x42,0x08,0x24,0xf9,0xab,0x43,0x52,0x6a,0xe9,0x42,0xee,0x0c,0xad,0x43,0x4c,0x8c,0xe7,0x42,0x1b,0x33, 14655 0xae,0x43,0xcc,0xf7,0xe5,0x42,0x08,0xaa,0x6b,0xaf,0x43,0xe8,0x61,0xe3,0x42,0x90,0xf5,0xaf,0x43,0xc9, 14656 0xf0,0xe0,0x42,0xe0,0x63,0xb0,0x43,0xe5,0x5a,0xde,0x42,0x08,0xaa,0x83,0xb3,0x43,0x29,0x2d,0x09,0x43, 14657 0x6a,0xfe,0x8e,0x43,0xb8,0x74,0x3c,0x43,0xd5,0x06,0x95,0x43,0xe6,0x79,0x67,0x43,0x08,0x2f,0x53,0x97, 14658 0x43,0xe9,0xb0,0x74,0x43,0xa8,0x28,0xa0,0x43,0x43,0xfd,0x76,0x43,0x83,0x28,0xad,0x43,0x17,0x59,0x81, 14659 0x43,0x08,0x3d,0xe7,0xbf,0x43,0x4b,0x8d,0x8c,0x43,0xae,0x96,0xba,0x43,0x66,0x27,0x92,0x43,0x15,0xe0, 14660 0xc7,0x43,0x6f,0x11,0x96,0x43,0x08,0x7e,0x5d,0xb2,0x43,0xdb,0x01,0x98,0x43,0x9e,0x56,0xa0,0x43,0x80, 14661 0xc1,0x97,0x43,0x69,0x2e,0x97,0x43,0x31,0x17,0x8d,0x43,0x09,0x06,0xab,0xa7,0x39,0x43,0x67,0x0f,0x0e, 14662 0x43,0x08,0xdb,0xbc,0x3b,0x43,0xe8,0x92,0x10,0x43,0xb5,0x85,0x3b,0x43,0x97,0x3c,0x14,0x43,0xab,0xa7, 14663 0x39,0x43,0x0c,0x0b,0x18,0x43,0x09,0x06,0xca,0x30,0x40,0x43,0x30,0x3b,0x13,0x43,0x08,0x17,0xc8,0x43, 14664 0x43,0xa5,0x09,0x17,0x43,0x7e,0xc9,0x44,0x43,0x1a,0xd8,0x1a,0x43,0x9d,0x22,0x43,0x43,0x8d,0xa6,0x1e, 14665 0x43,0x09,0x06,0xc8,0x78,0x4c,0x43,0xed,0xc9,0x1d,0x43,0x08,0x0b,0x32,0x4e,0x43,0x22,0xce,0x20,0x43, 14666 0x23,0xc5,0x4e,0x43,0x58,0xd2,0x23,0x43,0x0b,0x32,0x4e,0x43,0x2b,0xc4,0x26,0x43,0x09,0x06,0xec,0x08, 14667 0x58,0x43,0xc7,0xb1,0x26,0x43,0x08,0x02,0x9c,0x58,0x43,0xef,0x00,0x2b,0x43,0xd9,0x64,0x58,0x43,0x02, 14668 0xbd,0x2e,0x43,0x10,0x51,0x57,0x43,0x37,0xc1,0x31,0x43,0x09,0x06,0xcb,0xdf,0x61,0x43,0x4a,0x65,0x31, 14669 0x43,0x08,0xbe,0x2a,0x63,0x43,0xbd,0x33,0x35,0x43,0x32,0xe1,0x62,0x43,0x56,0x4a,0x38,0x43,0xde,0x83, 14670 0x61,0x43,0x3c,0xe0,0x3a,0x43,0x09,0x06,0x1c,0x7e,0x6a,0x43,0x5b,0x39,0x39,0x43,0x08,0x31,0x11,0x6b, 14671 0x43,0x0c,0xd2,0x3d,0x43,0x1c,0x7e,0x6a,0x43,0x13,0xd9,0x42,0x43,0xd9,0xc4,0x68,0x43,0xcb,0x60,0x48, 14672 0x43,0x09,0x06,0xe5,0xc1,0x73,0x43,0x16,0xf8,0x4b,0x43,0x08,0xa6,0xf7,0x72,0x43,0xb1,0xfd,0x4f,0x43, 14673 0x3b,0x07,0x71,0x43,0x4a,0x14,0x53,0x43,0xa2,0xf0,0x6d,0x43,0x7c,0x29,0x55,0x43,0x09,0x06,0x00,0x8d, 14674 0xa6,0x43,0xef,0x21,0x01,0x43,0x08,0x52,0xfb,0xa6,0x43,0xce,0xc8,0x02,0x43,0xe6,0x16,0xa7,0x43,0x51, 14675 0x4c,0x05,0x43,0x3b,0x68,0xa6,0x43,0x4c,0x75,0x08,0x43,0x09,0x06,0xde,0x20,0xa1,0x43,0x86,0x50,0x08, 14676 0x43,0x08,0xd4,0x4e,0xa1,0x43,0xd3,0xe7,0x0b,0x43,0xb5,0xe9,0xa0,0x43,0x59,0x5a,0x0f,0x43,0xba,0xcc, 14677 0x9f,0x43,0x54,0x83,0x12,0x43,0x09,0x06,0x77,0xfb,0x99,0x43,0x6c,0x16,0x13,0x43,0x08,0xde,0xfc,0x9a, 14678 0x43,0x4a,0xbd,0x14,0x43,0x06,0x34,0x9b,0x43,0xfe,0x55,0x19,0x43,0x13,0xe9,0x99,0x43,0x41,0x27,0x1f, 14679 0x43,0x09,0x06,0x46,0xce,0x93,0x43,0x26,0xa5,0x1d,0x43,0x08,0xe7,0xaa,0x94,0x43,0xbb,0xcc,0x1f,0x43, 14680 0x18,0xb4,0x94,0x43,0xa8,0x40,0x24,0x43,0xe2,0xbb,0x93,0x43,0x21,0xfe,0x28,0x43,0x09,0x06,0xb1,0x8e, 14681 0x8d,0x43,0xa8,0x58,0x28,0x43,0x08,0x19,0x90,0x8e,0x43,0x54,0x13,0x2b,0x43,0xa4,0xd9,0x8e,0x43,0x84, 14682 0x40,0x31,0x43,0x46,0xaa,0x8d,0x43,0x29,0x24,0x37,0x43,0x09,0x06,0xd6,0xbe,0x88,0x43,0xef,0x30,0x33, 14683 0x43,0x08,0x0c,0xb7,0x89,0x43,0x0e,0xa2,0x35,0x43,0xc0,0x37,0x8a,0x43,0x7a,0xaa,0x3b,0x43,0xbb,0x48, 14684 0x89,0x43,0xbb,0x7b,0x41,0x43,0x09,0x06,0x3a,0xad,0x82,0x43,0xc4,0x59,0x43,0x43,0x08,0xd2,0xb7,0x83, 14685 0x43,0x2b,0x5b,0x44,0x43,0x35,0xd6,0x85,0x43,0x48,0xf5,0x49,0x43,0x42,0x97,0x86,0x43,0xc4,0xa1,0x4f, 14686 0x43,0x09,0x06,0x9c,0xb3,0x80,0x43,0x48,0x55,0x5a,0x43,0x08,0xff,0xc5,0x80,0x43,0x09,0x73,0x55,0x43, 14687 0x93,0xe1,0x80,0x43,0x0f,0x39,0x53,0x43,0xf1,0xbe,0x7e,0x43,0x18,0xe7,0x4c,0x43,0x09,0x06,0xe0,0x02, 14688 0x7b,0x43,0x92,0xec,0x5d,0x43,0x08,0x09,0x3a,0x7b,0x43,0xf0,0xf7,0x58,0x43,0x09,0x3a,0x7b,0x43,0xe6, 14689 0x31,0x5b,0x43,0xe0,0x02,0x7b,0x43,0xa8,0x4f,0x56,0x43,0x09,0x06,0x39,0x4f,0x7d,0x43,0x3e,0x8f,0x5c, 14690 0x43,0x08,0xe9,0xe0,0x7c,0x43,0x03,0x9c,0x58,0x43,0x1e,0x2b,0x81,0x43,0x7f,0x30,0x5a,0x43,0xff,0x73, 14691 0x7d,0x43,0xf6,0xb6,0x51,0x43,0x09,0x06,0x5c,0xb8,0x52,0x43,0x28,0x21,0x87,0x43,0x08,0xae,0x3e,0x57, 14692 0x43,0x12,0x9a,0x88,0x43,0x23,0xf5,0x56,0x43,0x04,0xf1,0x8b,0x43,0x25,0xfc,0x5b,0x43,0x85,0x74,0x8e, 14693 0x43,0x08,0x2f,0xf2,0x61,0x43,0x8e,0x52,0x90,0x43,0xd9,0xdc,0x6c,0x43,0x85,0x74,0x8e,0x43,0xc6,0x20, 14694 0x69,0x43,0x3d,0xd8,0x8d,0x43,0x08,0x6d,0x8c,0x5a,0x43,0xf5,0x3b,0x8d,0x43,0x3d,0x77,0x58,0x43,0xa1, 14695 0xc6,0x87,0x43,0xf8,0xed,0x5e,0x43,0x5e,0x0d,0x86,0x43,0x09,0x06,0xde,0xcc,0x92,0x43,0xf7,0x17,0x87, 14696 0x43,0x08,0xb6,0x89,0x90,0x43,0xae,0x87,0x88,0x43,0x4a,0xa5,0x90,0x43,0xa1,0xde,0x8b,0x43,0xf9,0x2a, 14697 0x8e,0x43,0x23,0x62,0x8e,0x43,0x08,0xf5,0x2f,0x8b,0x43,0x5c,0x49,0x90,0x43,0x35,0xd6,0x85,0x43,0x8e, 14698 0x46,0x8e,0x43,0x3d,0xb4,0x87,0x43,0x47,0xaa,0x8d,0x43,0x08,0x6a,0xfe,0x8e,0x43,0xff,0x0d,0x8d,0x43, 14699 0xbb,0x6c,0x8f,0x43,0xf7,0x17,0x87,0x43,0x5c,0x31,0x8c,0x43,0xb2,0x5e,0x85,0x43,0x09,0x06,0x60,0x38, 14700 0x91,0x43,0x69,0x5d,0x7a,0x43,0x08,0x34,0x1e,0x92,0x43,0x1e,0x5b,0x89,0x43,0x04,0x63,0x7e,0x43,0x5e, 14701 0x01,0x84,0x43,0x59,0x2a,0x87,0x43,0x0d,0xcf,0x8d,0x43,0x09,0x03,0x04,0x06,0x5a,0x18,0x63,0x43,0x82, 14702 0x79,0x8b,0x43,0x08,0x25,0x2c,0x64,0x43,0x82,0x79,0x8b,0x43,0x2a,0x1b,0x65,0x43,0x9d,0xef,0x8a,0x43, 14703 0x2a,0x1b,0x65,0x43,0xc1,0x37,0x8a,0x43,0x08,0x2a,0x1b,0x65,0x43,0x17,0x89,0x89,0x43,0x25,0x2c,0x64, 14704 0x43,0x31,0xff,0x88,0x43,0x5a,0x18,0x63,0x43,0x31,0xff,0x88,0x43,0x08,0xf3,0x16,0x62,0x43,0x31,0xff, 14705 0x88,0x43,0xee,0x27,0x61,0x43,0x17,0x89,0x89,0x43,0xee,0x27,0x61,0x43,0xc1,0x37,0x8a,0x43,0x08,0xee, 14706 0x27,0x61,0x43,0x9d,0xef,0x8a,0x43,0xf3,0x16,0x62,0x43,0x82,0x79,0x8b,0x43,0x5a,0x18,0x63,0x43,0x82, 14707 0x79,0x8b,0x43,0x09,0x06,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x08,0x34,0xee,0x89,0x43,0x82,0x79, 14708 0x8b,0x43,0x85,0x5c,0x8a,0x43,0x9d,0xef,0x8a,0x43,0x85,0x5c,0x8a,0x43,0xc1,0x37,0x8a,0x43,0x08,0x85, 14709 0x5c,0x8a,0x43,0x17,0x89,0x89,0x43,0x34,0xee,0x89,0x43,0x31,0xff,0x88,0x43,0x4f,0x64,0x89,0x43,0x31, 14710 0xff,0x88,0x43,0x08,0x9c,0xe3,0x88,0x43,0x31,0xff,0x88,0x43,0x19,0x6c,0x88,0x43,0x17,0x89,0x89,0x43, 14711 0x19,0x6c,0x88,0x43,0xc1,0x37,0x8a,0x43,0x08,0x19,0x6c,0x88,0x43,0x9d,0xef,0x8a,0x43,0x9c,0xe3,0x88, 14712 0x43,0x82,0x79,0x8b,0x43,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x09,0x02,0x04,0x06,0x19,0x60,0x86, 14713 0x43,0xec,0xed,0xa3,0x43,0x08,0x35,0xd6,0x85,0x43,0x76,0x43,0xa6,0x43,0x93,0xe1,0x80,0x43,0x57,0x02, 14714 0xac,0x43,0x61,0xd8,0x80,0x43,0x87,0x17,0xae,0x43,0x08,0xa5,0x85,0x80,0x43,0xc3,0xfe,0xaf,0x43,0xce, 14715 0xbc,0x80,0x43,0x83,0x40,0xb1,0x43,0xa5,0x91,0x82,0x43,0x79,0x6e,0xb1,0x43,0x08,0x23,0x26,0x84,0x43, 14716 0x40,0x93,0xb1,0x43,0x30,0xe7,0x84,0x43,0xbe,0x1b,0xb1,0x43,0x11,0x82,0x84,0x43,0xab,0x6b,0xaf,0x43, 14717 0x08,0xb7,0x41,0x84,0x43,0x3b,0x98,0xae,0x43,0xb7,0x41,0x84,0x43,0xc3,0xf2,0xad,0x43,0xa1,0xae,0x83, 14718 0x43,0x83,0x28,0xad,0x43,0x08,0xb2,0x52,0x83,0x43,0x80,0x39,0xac,0x43,0x81,0x49,0x83,0x43,0xf0,0x00, 14719 0xab,0x43,0xe4,0x67,0x85,0x43,0x76,0x4f,0xa8,0x43,0x08,0x9c,0xd7,0x86,0x43,0xd1,0x83,0xa6,0x43,0xec, 14720 0x45,0x87,0x43,0x01,0x75,0xa2,0x43,0x19,0x60,0x86,0x43,0xec,0xed,0xa3,0x43,0x09,0x06,0xd9,0xdc,0x6c, 14721 0x43,0x14,0x25,0xa4,0x43,0x08,0xa2,0xf0,0x6d,0x43,0x9f,0x7a,0xa6,0x43,0x47,0xec,0x77,0x43,0x80,0x39, 14722 0xac,0x43,0xa9,0xfe,0x77,0x43,0xb0,0x4e,0xae,0x43,0x08,0x23,0xa4,0x78,0x43,0xea,0x35,0xb0,0x43,0xd2, 14723 0x35,0x78,0x43,0xab,0x77,0xb1,0x43,0xc1,0x79,0x74,0x43,0xa2,0xa5,0xb1,0x43,0x08,0xc6,0x50,0x71,0x43, 14724 0x68,0xca,0xb1,0x43,0xab,0xce,0x6f,0x43,0xe7,0x52,0xb1,0x43,0xea,0x98,0x70,0x43,0xd4,0xa2,0xaf,0x43, 14725 0x08,0x9d,0x19,0x71,0x43,0x96,0xd8,0xae,0x43,0x9d,0x19,0x71,0x43,0xec,0x29,0xae,0x43,0xca,0x3f,0x72, 14726 0x43,0xab,0x5f,0xad,0x43,0x08,0xa6,0xf7,0x72,0x43,0xa7,0x70,0xac,0x43,0x09,0x0a,0x73,0x43,0x17,0x38, 14727 0xab,0x43,0x44,0xcd,0x6e,0x43,0x9f,0x86,0xa8,0x43,0x08,0xd4,0xed,0x6b,0x43,0xf8,0xba,0xa6,0x43,0x31, 14728 0x11,0x6b,0x43,0x2a,0xac,0xa2,0x43,0xd9,0xdc,0x6c,0x43,0x14,0x25,0xa4,0x43,0x09,0x01,0x05,0x06,0x66, 14729 0x5d,0x7a,0x43,0x74,0xeb,0xc2,0x43,0x08,0x09,0x22,0x77,0x43,0x50,0xbb,0xc7,0x43,0xe9,0xe0,0x7c,0x43, 14730 0xf5,0x86,0xc9,0x43,0x8f,0x94,0x7a,0x43,0xc5,0x95,0xcd,0x43,0x09,0x06,0x08,0x98,0x80,0x43,0x6b,0x19, 14731 0xc3,0x43,0x08,0xb7,0x35,0x82,0x43,0x79,0xf2,0xc7,0x43,0xf1,0xbe,0x7e,0x43,0x1e,0xbe,0xc9,0x43,0x73, 14732 0x7c,0x80,0x43,0xec,0xcc,0xcd,0x43,0x09,0x06,0x28,0xab,0x7d,0x43,0xae,0xde,0xc6,0x43,0x08,0x1e,0xcd, 14733 0x7b,0x43,0x8a,0xa2,0xc9,0x43,0x30,0x89,0x7f,0x43,0x5c,0x94,0xcc,0x43,0x28,0xab,0x7d,0x43,0x42,0x2a, 14734 0xcf,0x43,0x09,0x01,0x05,0x06,0x24,0x14,0xe0,0x42,0xf5,0x77,0x97,0x43,0x08,0xf7,0x1d,0xe7,0x42,0x74, 14735 0x00,0x97,0x43,0x4d,0x93,0xec,0x42,0xdb,0xf5,0x95,0x43,0x29,0x4b,0xed,0x42,0xcd,0x34,0x95,0x43,0x09, 14736 0x06,0x29,0x7b,0xf5,0x42,0x6f,0x1d,0x98,0x43,0x08,0xe4,0xf1,0xfb,0x42,0x61,0x5c,0x97,0x43,0xdb,0x7d, 14737 0x01,0x43,0xb2,0xbe,0x95,0x43,0x55,0x23,0x02,0x43,0xe7,0xaa,0x94,0x43,0x09,0x06,0x98,0xdc,0x03,0x43, 14738 0xbe,0x8b,0x98,0x43,0x08,0x66,0xdf,0x05,0x43,0x47,0xe6,0x97,0x43,0xae,0x87,0x08,0x43,0x98,0x48,0x96, 14739 0x43,0x61,0x08,0x09,0x43,0xd6,0x06,0x95,0x43,0x09,0x06,0x31,0x0b,0x0b,0x43,0x8e,0x82,0x98,0x43,0x08, 14740 0xdb,0xc5,0x0d,0x43,0x80,0xc1,0x97,0x43,0xd6,0xee,0x10,0x43,0xa9,0xec,0x95,0x43,0x79,0xcb,0x11,0x43, 14741 0x55,0x8f,0x94,0x43,0x09,0x06,0xd1,0x2f,0x18,0x43,0xdb,0x01,0x98,0x43,0x08,0xad,0xe7,0x18,0x43,0x38, 14742 0x25,0x97,0x43,0x8a,0x9f,0x19,0x43,0x80,0xb5,0x95,0x43,0xd6,0x1e,0x19,0x43,0xe0,0xd8,0x94,0x43,0x09, 14743 0x06,0x9a,0x5b,0x1d,0x43,0x58,0x8a,0x97,0x43,0x08,0x01,0x5d,0x1e,0x43,0xf1,0x88,0x96,0x43,0x2f,0x83, 14744 0x1f,0x43,0x19,0xb4,0x94,0x43,0x19,0xf0,0x1e,0x43,0x6f,0x05,0x94,0x43,0x09,0x06,0x0b,0x53,0x24,0x43, 14745 0xae,0xdb,0x96,0x43,0x08,0x25,0xd5,0x25,0x43,0x50,0xac,0x95,0x43,0x53,0xfb,0x26,0x43,0x8a,0x7b,0x93, 14746 0x43,0x76,0x43,0x26,0x43,0xb7,0x95,0x92,0x43,0x09,0x06,0x76,0x5b,0x2a,0x43,0x47,0xda,0x95,0x43,0x08, 14747 0xf3,0xef,0x2b,0x43,0x10,0xe2,0x94,0x43,0x6d,0x95,0x2c,0x43,0xae,0xc3,0x92,0x43,0x68,0xa6,0x2b,0x43, 14748 0x47,0xc2,0x91,0x43,0x09,0x06,0x36,0xc1,0x31,0x43,0x2c,0x58,0x94,0x43,0x08,0x8c,0x1e,0x33,0x43,0x31, 14749 0x3b,0x93,0x43,0x79,0x7a,0x33,0x43,0xff,0x25,0x91,0x43,0xd9,0x9d,0x32,0x43,0xc1,0x5b,0x90,0x43,0x09, 14750 0x06,0x25,0x35,0x36,0x43,0x31,0x3b,0x93,0x43,0x08,0x3f,0xb7,0x37,0x43,0xc1,0x67,0x92,0x43,0xe0,0x93, 14751 0x38,0x43,0xae,0xb7,0x90,0x43,0x7e,0x81,0x38,0x43,0x0d,0xdb,0x8f,0x43,0x09,0x06,0xb5,0x85,0x3b,0x43, 14752 0xe4,0xaf,0x91,0x43,0x08,0xcf,0x07,0x3d,0x43,0x9d,0x13,0x91,0x43,0xbc,0x63,0x3d,0x43,0x47,0xb6,0x8f, 14753 0x43,0xe5,0x9a,0x3d,0x43,0x74,0xd0,0x8e,0x43,0x09,0x06,0xae,0xc6,0x42,0x43,0xa4,0xd9,0x8e,0x43,0x08, 14754 0xca,0x48,0x44,0x43,0xfa,0x2a,0x8e,0x43,0xa2,0x11,0x44,0x43,0x9d,0xfb,0x8c,0x43,0x55,0x92,0x44,0x43, 14755 0x0d,0xc3,0x8b,0x43,0x09,0x06,0x39,0x10,0xc3,0x43,0x34,0x36,0x96,0x43,0x08,0x92,0x44,0xc1,0x43,0xe4, 14756 0xc7,0x95,0x43,0x6f,0xf0,0xbf,0x43,0x4b,0xbd,0x94,0x43,0x47,0xb9,0xbf,0x43,0x0b,0xf3,0x93,0x43,0x09, 14757 0x06,0x8f,0x49,0xbe,0x43,0xb7,0xad,0x96,0x43,0x08,0x11,0xb5,0xbc,0x43,0x77,0xe3,0x95,0x43,0x9c,0xf2, 14758 0xba,0x43,0xfa,0x4e,0x94,0x43,0xae,0x96,0xba,0x43,0x31,0x3b,0x93,0x43,0x09,0x06,0xdb,0xb0,0xb9,0x43, 14759 0x10,0xee,0x96,0x43,0x08,0x42,0xa6,0xb8,0x43,0xc8,0x51,0x96,0x43,0x50,0x5b,0xb7,0x43,0x19,0xb4,0x94, 14760 0x43,0xf7,0x1a,0xb7,0x43,0x58,0x72,0x93,0x43,0x09,0x06,0xf2,0x2b,0xb6,0x43,0x10,0xee,0x96,0x43,0x08, 14761 0x9d,0xce,0xb4,0x43,0x04,0x2d,0x96,0x43,0xed,0x30,0xb3,0x43,0x2c,0x58,0x94,0x43,0xce,0xcb,0xb2,0x43, 14762 0xd6,0xfa,0x92,0x43,0x09,0x06,0x5a,0x09,0xb1,0x43,0x19,0xc0,0x96,0x43,0x08,0x6c,0xad,0xb0,0x43,0x77, 14763 0xe3,0x95,0x43,0x7e,0x51,0xb0,0x43,0xc0,0x73,0x94,0x43,0xd8,0x91,0xb0,0x43,0x1e,0x97,0x93,0x43,0x09, 14764 0x06,0x48,0x4d,0xad,0x43,0xbe,0x7f,0x96,0x43,0x08,0x95,0xcc,0xac,0x43,0x58,0x7e,0x95,0x43,0x4d,0x30, 14765 0xac,0x43,0x80,0xa9,0x93,0x43,0xd8,0x79,0xac,0x43,0xd6,0xfa,0x92,0x43,0x09,0x06,0x90,0xd1,0xa9,0x43, 14766 0x14,0xd1,0x95,0x43,0x08,0x83,0x10,0xa9,0x43,0xb7,0xa1,0x94,0x43,0x3b,0x74,0xa8,0x43,0xf1,0x70,0x92, 14767 0x43,0x29,0xd0,0xa8,0x43,0x1e,0x8b,0x91,0x43,0x09,0x06,0x5a,0xcd,0xa6,0x43,0x8a,0x87,0x95,0x43,0x08, 14768 0x1c,0x03,0xa6,0x43,0x23,0x86,0x94,0x43,0x5f,0xb0,0xa5,0x43,0xc1,0x67,0x92,0x43,0xe1,0x27,0xa6,0x43, 14769 0x8a,0x6f,0x91,0x43,0x09,0x06,0xd4,0x5a,0xa3,0x43,0x2c,0x58,0x94,0x43,0x08,0x29,0xac,0xa2,0x43,0x31, 14770 0x3b,0x93,0x43,0x32,0x7e,0xa2,0x43,0xff,0x25,0x91,0x43,0x83,0xec,0xa2,0x43,0x8e,0x52,0x90,0x43,0x09, 14771 0x06,0xf8,0x96,0xa0,0x43,0x1e,0x97,0x93,0x43,0x08,0xeb,0xd5,0x9f,0x43,0x7b,0xba,0x92,0x43,0x99,0x67, 14772 0x9f,0x43,0x9d,0x13,0x91,0x43,0x99,0x67,0x9f,0x43,0xfa,0x36,0x90,0x43,0x09,0x06,0xeb,0xc9,0x9d,0x43, 14773 0xc8,0x39,0x92,0x43,0x08,0xde,0x08,0x9d,0x43,0xb2,0xa6,0x91,0x43,0xe6,0xda,0x9c,0x43,0x2c,0x40,0x90, 14774 0x43,0x52,0xbf,0x9c,0x43,0x5a,0x5a,0x8f,0x43,0x09,0x06,0x37,0x3d,0x9b,0x43,0x85,0x80,0x90,0x43,0x08, 14775 0x2a,0x7c,0x9a,0x43,0xdb,0xd1,0x8f,0x43,0xf0,0xa0,0x9a,0x43,0x7d,0xa2,0x8e,0x43,0x65,0x57,0x9a,0x43, 14776 0xee,0x69,0x8d,0x43,0x09,0x02,0x04,0x06,0x2a,0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x08,0x0d,0x8a,0x31, 14777 0x42,0x9f,0x0e,0x94,0x43,0xf3,0x1f,0x34,0x42,0x3d,0xfc,0x93,0x43,0x63,0xff,0x36,0x42,0xa9,0xe0,0x93, 14778 0x43,0x08,0xb5,0x34,0x5d,0x42,0x0b,0xf3,0x93,0x43,0x6d,0xa4,0x5e,0x42,0x03,0x39,0x98,0x43,0xe7,0x31, 14779 0x5b,0x42,0x93,0x89,0x9d,0x43,0x08,0x02,0x9c,0x58,0x42,0xd4,0x5a,0xa3,0x43,0x38,0x70,0x53,0x42,0x14, 14780 0x49,0xaa,0x43,0xf8,0xed,0x5e,0x42,0x83,0x28,0xad,0x43,0x08,0xea,0x68,0x68,0x42,0x20,0x22,0xaf,0x43, 14781 0x12,0xb8,0x6c,0x42,0xb5,0x49,0xb1,0x43,0x2a,0x4b,0x6d,0x42,0x0d,0x96,0xb3,0x43,0x07,0x2a,0x4b,0x6d, 14782 0x42,0xc6,0x05,0xb5,0x43,0x08,0x87,0x6e,0x6c,0x42,0x68,0xee,0xb7,0x43,0x1c,0x66,0x66,0x42,0x31,0x0e, 14783 0xbb,0x43,0x57,0x11,0x5e,0x42,0x8f,0x49,0xbe,0x43,0x08,0x66,0x96,0x54,0x42,0xb9,0x5c,0xb8,0x43,0x2c, 14784 0x2b,0x3c,0x42,0x68,0xd6,0xb3,0x43,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x07,0x2a,0xf4,0x2e,0x42, 14785 0x61,0xa4,0xa3,0x43,0x08,0x55,0x1a,0x30,0x42,0xf0,0xd0,0xa2,0x43,0xf8,0xf6,0x30,0x42,0xb2,0x06,0xa2, 14786 0x43,0x98,0xd3,0x31,0x42,0xd6,0x4e,0xa1,0x43,0x08,0x1c,0x6f,0x38,0x42,0x2a,0x94,0x9e,0x43,0xc1,0x22, 14787 0x36,0x42,0xf5,0x9b,0x9d,0x43,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x57, 14788 0xa2,0x9b,0x43,0x08,0xab,0x8f,0x35,0x42,0x8a,0xab,0x9b,0x43,0xe9,0x71,0x3a,0x42,0xb2,0xe2,0x9b,0x43, 14789 0xb7,0x74,0x3c,0x42,0x34,0x5a,0x9c,0x43,0x08,0x23,0x7d,0x42,0x42,0x0b,0x2f,0x9e,0x43,0xe5,0x9a,0x3d, 14790 0x42,0x38,0x6d,0xa3,0x43,0x36,0xd9,0x35,0x42,0xf3,0xd7,0xa7,0x43,0x08,0x12,0x61,0x2e,0x42,0xb0,0x42, 14791 0xac,0x43,0x63,0xff,0x36,0x42,0xdd,0x74,0xaf,0x43,0x1e,0xa6,0x45,0x42,0x44,0x82,0xb2,0x43,0x08,0x74, 14792 0x1b,0x4b,0x42,0x79,0x7a,0xb3,0x43,0x10,0x21,0x4f,0x42,0x2a,0x18,0xb5,0x43,0xdb,0x4c,0x54,0x42,0x91, 14793 0x19,0xb6,0x43,0x08,0xee,0x3f,0x65,0x42,0x5f,0x28,0xba,0x43,0xa7,0xaf,0x66,0x42,0xb9,0x50,0xb6,0x43, 14794 0x14,0x58,0x5c,0x42,0xca,0xdc,0xb1,0x43,0x08,0x2c,0x8b,0x4c,0x42,0x4e,0x30,0xac,0x43,0x19,0xcf,0x48, 14795 0x42,0x2a,0xd0,0xa8,0x43,0xbc,0xab,0x49,0x42,0xa9,0x4c,0xa6,0x43,0x08,0x61,0x5f,0x47,0x42,0xfa,0xa2, 14796 0xa2,0x43,0xa7,0xaf,0x66,0x42,0x85,0x98,0x94,0x43,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x07,0x2a, 14797 0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x09,0x06,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x08,0xdc,0xe3, 14798 0xf1,0x41,0xe9,0x9e,0x92,0x43,0xd2,0xe7,0x0b,0x42,0xd6,0x06,0x95,0x43,0x2a,0xf4,0x2e,0x42,0x04,0x21, 14799 0x94,0x43,0x07,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x08,0x87,0x17,0x2e,0x42,0xc3,0x62,0x95,0x43, 14800 0xe7,0x3a,0x2d,0x42,0xf5,0x6b,0x95,0x43,0x44,0x5e,0x2c,0x42,0xf5,0x6b,0x95,0x43,0x08,0xd1,0x47,0x1c, 14801 0x42,0x19,0xc0,0x96,0x43,0x66,0xdf,0x05,0x42,0x38,0x19,0x95,0x43,0x12,0x6a,0x00,0x42,0xb2,0xbe,0x95, 14802 0x43,0x08,0xbb,0x6b,0xea,0x41,0xd6,0x12,0x97,0x43,0x2d,0x82,0xfa,0x41,0x61,0x74,0x9b,0x43,0x7e,0x72, 14803 0x06,0x42,0x8a,0xab,0x9b,0x43,0x08,0xc8,0x39,0x12,0x42,0x4e,0xd0,0x9b,0x43,0x53,0xe3,0x22,0x42,0xc3, 14804 0x86,0x9b,0x43,0x2a,0xf4,0x2e,0x42,0x57,0xa2,0x9b,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43, 14805 0x08,0x01,0xa5,0x2a,0x42,0xa4,0x2d,0x9d,0x43,0x96,0x9c,0x24,0x42,0x06,0x40,0x9d,0x43,0x8a,0xb7,0x1d, 14806 0x42,0x9a,0x5b,0x9d,0x43,0x08,0x6b,0x16,0x13,0x42,0xcd,0x64,0x9d,0x43,0x42,0xc7,0x0e,0x42,0x9a,0x5b, 14807 0x9d,0x43,0x23,0x26,0x04,0x42,0xcd,0x64,0x9d,0x43,0x08,0xe6,0x91,0xeb,0x41,0x38,0x49,0x9d,0x43,0x73, 14808 0x7b,0xdb,0x41,0xf5,0x83,0x99,0x43,0x7f,0x60,0xe2,0x41,0x0b,0x0b,0x98,0x43,0x08,0x7f,0x60,0xe2,0x41, 14809 0xec,0x99,0x95,0x43,0xe3,0x5a,0xde,0x41,0xbe,0x7f,0x96,0x43,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43, 14810 0x07,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x09,0x06,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x08, 14811 0xd4,0x7e,0x29,0x42,0xab,0x6b,0xaf,0x43,0x4e,0x0c,0x26,0x42,0x44,0x6a,0xae,0x43,0x38,0x79,0x25,0x42, 14812 0xd4,0x96,0xad,0x43,0x08,0x25,0xbd,0x21,0x42,0xe2,0x4b,0xac,0x43,0x49,0x35,0x29,0x42,0x9a,0x97,0xa7, 14813 0x43,0x2a,0xf4,0x2e,0x42,0x61,0xa4,0xa3,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x09,0x06, 14814 0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x08,0x86,0x20,0x80,0x43,0x57,0x41,0xe6,0x43,0x7d,0x4e,0x80, 14815 0x43,0x25,0x38,0xe6,0x43,0xa5,0x85,0x80,0x43,0xf3,0x2e,0xe6,0x43,0x08,0x35,0xca,0x83,0x43,0xd4,0xc9, 14816 0xe5,0x43,0x9c,0xd7,0x86,0x43,0x44,0x91,0xe4,0x43,0xd5,0xca,0x8a,0x43,0x91,0x1c,0xe6,0x43,0x08,0x53, 14817 0x5f,0x8c,0x43,0xf8,0x1d,0xe7,0x43,0x2f,0x17,0x8d,0x43,0x4e,0x7b,0xe8,0x43,0x92,0x29,0x8d,0x43,0x2f, 14818 0x22,0xea,0x43,0x07,0x92,0x29,0x8d,0x43,0x44,0xb5,0xea,0x43,0x08,0xfe,0x0d,0x8d,0x43,0x2a,0x4b,0xed, 14819 0x43,0xe3,0x8b,0x8b,0x43,0x55,0x7d,0xf0,0x43,0xec,0x51,0x89,0x43,0x72,0x0b,0xf4,0x43,0x08,0xcd,0xd4, 14820 0x84,0x43,0x9d,0x55,0xfb,0x43,0xc9,0xe5,0x83,0x43,0x74,0x1e,0xfb,0x43,0x73,0x94,0x84,0x43,0x5a,0x90, 14821 0xf7,0x43,0x08,0xe8,0x62,0x88,0x43,0xfd,0x30,0xee,0x43,0x39,0xc5,0x86,0x43,0xdd,0xbf,0xeb,0x43,0x35, 14822 0xbe,0x81,0x43,0x40,0xde,0xed,0x43,0x08,0x4f,0x34,0x81,0x43,0x36,0x0c,0xee,0x43,0x08,0x98,0x80,0x43, 14823 0xfd,0x30,0xee,0x43,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x40,0xec, 14824 0x43,0x08,0x35,0xbe,0x81,0x43,0x06,0xf7,0xeb,0x43,0x15,0x65,0x83,0x43,0x49,0xa4,0xeb,0x43,0x1e,0x43, 14825 0x85,0x43,0xbe,0x5a,0xeb,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0x18,0xea,0x43,0x42,0x97,0x86,0x43,0x5f, 14826 0x67,0xf4,0x43,0xa9,0x98,0x87,0x43,0xd4,0x1d,0xf4,0x43,0x08,0x5c,0x25,0x8a,0x43,0xcf,0x16,0xef,0x43, 14827 0x46,0xaa,0x8d,0x43,0x5a,0x3c,0xe9,0x43,0x19,0x6c,0x88,0x43,0x53,0x5e,0xe7,0x43,0x08,0xc4,0x02,0x85, 14828 0x43,0x96,0x0b,0xe7,0x43,0x85,0x2c,0x82,0x43,0x83,0x67,0xe7,0x43,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7, 14829 0x43,0x07,0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x09,0x06,0xfd,0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43, 14830 0x08,0xfa,0x6c,0x78,0x43,0xd1,0xc2,0xe0,0x43,0x25,0x5c,0x6c,0x43,0x25,0x44,0xe8,0x43,0x1d,0xe5,0x7f, 14831 0x43,0x87,0x4a,0xe6,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,0x43,0x08,0xa6,0x27,0x7b,0x43,0x91, 14832 0x28,0xe8,0x43,0xbc,0xa2,0x77,0x43,0xb0,0x8d,0xe8,0x43,0xc6,0x68,0x75,0x43,0x57,0x4d,0xe8,0x43,0x08, 14833 0xe0,0xd2,0x72,0x43,0xab,0x9e,0xe7,0x43,0x50,0x9a,0x71,0x43,0x2a,0x27,0xe7,0x43,0xea,0x98,0x70,0x43, 14834 0x57,0x35,0xe4,0x43,0x08,0x94,0x3b,0x6f,0x43,0x14,0x7c,0xe2,0x43,0xff,0x13,0x6d,0x43,0x06,0xbb,0xe1, 14835 0x43,0xcf,0xfe,0x6a,0x43,0x06,0xbb,0xe1,0x43,0x08,0x44,0x9d,0x66,0x43,0x77,0x8e,0xe2,0x43,0x3b,0xef, 14836 0x6c,0x43,0x91,0x10,0xe4,0x43,0xfd,0x24,0x6c,0x43,0xb0,0x81,0xe6,0x43,0x08,0x96,0x23,0x6b,0x43,0xee, 14837 0x57,0xe9,0x43,0xca,0x0f,0x6a,0x43,0x5f,0x37,0xec,0x43,0x55,0x71,0x6e,0x43,0x9f,0x01,0xed,0x43,0x08, 14838 0xdb,0xfb,0x75,0x43,0x3b,0xef,0xec,0x43,0x09,0x3a,0x7b,0x43,0xb0,0xa5,0xec,0x43,0x1d,0xe5,0x7f,0x43, 14839 0x91,0x40,0xec,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x08,0xa9,0x16,0x7c,0x43,0xb0,0xb1, 14840 0xee,0x43,0x47,0xec,0x77,0x43,0xd9,0xe8,0xee,0x43,0x1e,0x9d,0x73,0x43,0xcf,0x16,0xef,0x43,0x08,0x0e, 14841 0xc9,0x6b,0x43,0xee,0x7b,0xef,0x43,0x7e,0x90,0x6a,0x43,0xfd,0x30,0xee,0x43,0x01,0xfc,0x68,0x43,0x4e, 14842 0x93,0xec,0x43,0x08,0x31,0xf9,0x66,0x43,0x4e,0x87,0xea,0x43,0x31,0x11,0x6b,0x43,0xd4,0xd5,0xe7,0x43, 14843 0xd9,0xc4,0x68,0x43,0xd4,0xc9,0xe5,0x43,0x08,0xe5,0x79,0x67,0x43,0x77,0x9a,0xe4,0x43,0x44,0x9d,0x66, 14844 0x43,0xab,0x86,0xe3,0x43,0x7e,0x78,0x66,0x43,0x0b,0xaa,0xe2,0x43,0x07,0x7e,0x78,0x66,0x43,0x57,0x29, 14845 0xe2,0x43,0x08,0xa7,0xaf,0x66,0x43,0xbe,0x1e,0xe1,0x43,0x87,0x56,0x68,0x43,0x77,0x82,0xe0,0x43,0xfd, 14846 0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,0x09,0x06,0xc4,0x41,0xbf,0x43,0x85,0xc0,0x72,0x42,0x08,0x73,0xdf, 14847 0xc0,0x43,0xf4,0x76,0x72,0x42,0x97,0x33,0xc2,0x43,0x85,0xc0,0x72,0x42,0xb2,0xb5,0xc3,0x43,0x64,0x56, 14848 0x75,0x42,0x08,0x03,0x24,0xc4,0x43,0x5e,0x7f,0x78,0x42,0xfa,0x51,0xc4,0x43,0x01,0x85,0x7c,0x42,0x5c, 14849 0x64,0xc4,0x43,0xa0,0xb3,0x80,0x42,0x07,0x5c,0x64,0xc4,0x43,0x10,0x93,0x83,0x42,0x08,0xc8,0x48,0xc4, 14850 0x43,0x1c,0x78,0x8a,0x42,0x27,0x6c,0xc3,0x43,0xaf,0xcf,0x94,0x42,0x23,0x7d,0xc2,0x43,0x99,0x9c,0xa4, 14851 0x42,0x08,0x3d,0xe7,0xbf,0x43,0xfb,0xfd,0xb5,0x42,0xb3,0x9d,0xbf,0x43,0x88,0x17,0xae,0x42,0xc4,0x41, 14852 0xbf,0x43,0x69,0x76,0xa3,0x42,0x07,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x08,0x4f,0x8b,0xbf,0x43, 14853 0xed,0x81,0x91,0x42,0xe4,0xa6,0xbf,0x43,0x5d,0x61,0x94,0x42,0xfa,0x39,0xc0,0x43,0x3b,0x49,0x9d,0x42, 14854 0x08,0x2b,0x43,0xc0,0x43,0x28,0xed,0xa9,0x42,0x61,0x3b,0xc1,0x43,0x00,0x9e,0xa5,0x42,0xe4,0xb2,0xc1, 14855 0x43,0x5d,0x91,0x9c,0x42,0x08,0x78,0xce,0xc1,0x43,0xfd,0x36,0x90,0x42,0x22,0x89,0xc4,0x43,0x81,0x72, 14856 0x86,0x42,0xae,0xc6,0xc2,0x43,0xa0,0xb3,0x80,0x42,0x08,0x54,0x86,0xc2,0x43,0x58,0xd1,0x7e,0x42,0x30, 14857 0x32,0xc1,0x43,0xce,0x5e,0x7b,0x42,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x07,0xc4,0x41,0xbf,0x43, 14858 0x85,0xc0,0x72,0x42,0x09,0x06,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x08,0x35,0xfd,0xbb,0x43,0xa4, 14859 0xa1,0x5c,0x42,0x5e,0x34,0xbc,0x43,0x9d,0x2a,0x70,0x42,0x5e,0x40,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08, 14860 0x4c,0x9c,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,0xef,0xbe,0x43,0x0e,0x0a,0x73,0x42,0xc4,0x41,0xbf,0x43, 14861 0x85,0xc0,0x72,0x42,0x07,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x08,0xcd,0x13,0xbf,0x43,0xe8,0xf1, 14862 0x7b,0x42,0xd6,0xe5,0xbe,0x43,0x71,0x3b,0x7c,0x42,0xdf,0xb7,0xbe,0x43,0x71,0x3b,0x7c,0x42,0x08,0x08, 14863 0xe3,0xbc,0x43,0xa4,0x61,0x7d,0x42,0x28,0x3c,0xbb,0x43,0x91,0x45,0x69,0x42,0x28,0x3c,0xbb,0x43,0x58, 14864 0x71,0x6e,0x42,0x08,0xce,0xfb,0xba,0x43,0xd5,0x35,0x78,0x42,0x59,0x45,0xbb,0x43,0x58,0x23,0x82,0x42, 14865 0xa1,0xe1,0xbb,0x43,0xd7,0xbe,0x88,0x42,0x08,0xc9,0x18,0xbc,0x43,0xaf,0x9f,0x8c,0x42,0x1e,0x76,0xbd, 14866 0x43,0x51,0x7c,0x8d,0x42,0xd6,0xe5,0xbe,0x43,0xf4,0x58,0x8e,0x42,0x08,0x9c,0x0a,0xbf,0x43,0x45,0xc7, 14867 0x8e,0x42,0x30,0x26,0xbf,0x43,0x96,0x35,0x8f,0x42,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x07,0xc4, 14868 0x41,0xbf,0x43,0x69,0x76,0xa3,0x42,0x08,0x08,0xef,0xbe,0x43,0xb1,0xd6,0x99,0x42,0xe8,0x89,0xbe,0x43, 14869 0xde,0xc5,0x8d,0x42,0xc0,0x46,0xbc,0x43,0xc2,0x5b,0x90,0x42,0x08,0x9c,0xf2,0xba,0x43,0x86,0x80,0x90, 14870 0x42,0xf2,0x43,0xba,0x43,0xe8,0x73,0x87,0x42,0x8f,0x31,0xba,0x43,0xb6,0xf4,0x7d,0x42,0x07,0x8f,0x31, 14871 0xba,0x43,0x21,0xc6,0x76,0x42,0x08,0xc0,0x3a,0xba,0x43,0x5f,0x48,0x6b,0x42,0xae,0x96,0xba,0x43,0xe3, 14872 0x83,0x61,0x42,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x09,0x06,0xea,0x74,0xea,0x43,0x61,0x44,0x93, 14873 0x43,0x08,0x24,0x5c,0xec,0x43,0x31,0x3b,0x93,0x43,0xfb,0x30,0xee,0x43,0x93,0x4d,0x93,0x43,0x0d,0xe1, 14874 0xef,0x43,0x80,0xa9,0x93,0x43,0x08,0x8f,0x58,0xf0,0x43,0xd1,0x17,0x94,0x43,0xb7,0x8f,0xf0,0x43,0x10, 14875 0xe2,0x94,0x43,0xea,0x98,0xf0,0x43,0xa9,0xec,0x95,0x43,0x07,0xea,0x98,0xf0,0x43,0x38,0x25,0x97,0x43, 14876 0x08,0x23,0x74,0xf0,0x43,0x9f,0x32,0x9a,0x43,0x5a,0x60,0xef,0x43,0x53,0xcb,0x9e,0x43,0x2d,0x3a,0xee, 14877 0x43,0xfd,0x91,0xa3,0x43,0x08,0xa2,0xf0,0xed,0x43,0xdd,0x38,0xa5,0x43,0x17,0xa7,0xed,0x43,0xbe,0xdf, 14878 0xa6,0x43,0x5a,0x54,0xed,0x43,0x9f,0x86,0xa8,0x43,0x08,0xfc,0x24,0xec,0x43,0xca,0xc4,0xad,0x43,0x48, 14879 0xa4,0xeb,0x43,0x40,0x6f,0xab,0x43,0x28,0x3f,0xeb,0x43,0x1c,0x0f,0xa8,0x43,0x08,0x1f,0x6d,0xeb,0x43, 14880 0x72,0x48,0xa3,0x43,0x67,0x09,0xec,0x43,0xd1,0x53,0x9e,0x43,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,0x43, 14881 0x07,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x08,0x7e,0x90,0xea,0x43,0x8a,0x9f,0x99,0x43,0x12,0xac, 14882 0xea,0x43,0xbc,0xa8,0x99,0x43,0xa7,0xc7,0xea,0x43,0xbc,0xa8,0x99,0x43,0x08,0x51,0x76,0xeb,0x43,0x9f, 14883 0x32,0x9a,0x43,0x5e,0x37,0xec,0x43,0x49,0xed,0x9c,0x43,0xb0,0xa5,0xec,0x43,0x2a,0xa0,0xa0,0x43,0x08, 14884 0x09,0xe6,0xec,0x43,0xd1,0x77,0xa4,0x43,0x28,0x4b,0xed,0x43,0x61,0xa4,0xa3,0x43,0xab,0xc2,0xed,0x43, 14885 0x8e,0xb2,0xa0,0x43,0x08,0x70,0xe7,0xed,0x43,0xde,0x08,0x9d,0x43,0x87,0x86,0xf0,0x43,0x2f,0x53,0x97, 14886 0x43,0x87,0x7a,0xee,0x43,0xec,0x99,0x95,0x43,0x08,0xca,0x27,0xee,0x43,0xff,0x3d,0x95,0x43,0x74,0xca, 14887 0xec,0x43,0x55,0x8f,0x94,0x43,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x07,0xea,0x74,0xea,0x43,0x61, 14888 0x44,0x93,0x43,0x09,0x06,0x05,0xd3,0xe5,0x43,0x19,0x9c,0x90,0x43,0x08,0x09,0xc2,0xe6,0x43,0xd1,0xff, 14889 0x8f,0x43,0x4d,0x6f,0xe6,0x43,0x74,0xe8,0x92,0x43,0x3b,0xd7,0xe8,0x43,0xc3,0x56,0x93,0x43,0x08,0x1f, 14890 0x61,0xe9,0x43,0x93,0x4d,0x93,0x43,0x05,0xeb,0xe9,0x43,0x93,0x4d,0x93,0x43,0xea,0x74,0xea,0x43,0x61, 14891 0x44,0x93,0x43,0x07,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x08,0x24,0x50,0xea,0x43,0xe7,0xaa,0x94, 14892 0x43,0x2d,0x22,0xea,0x43,0xe7,0xaa,0x94,0x43,0x36,0xf4,0xe9,0x43,0xe7,0xaa,0x94,0x43,0x08,0xa2,0xcc, 14893 0xe7,0x43,0xe0,0xd8,0x94,0x43,0xd4,0xc9,0xe5,0x43,0x19,0xa8,0x92,0x43,0xd4,0xc9,0xe5,0x43,0x27,0x69, 14894 0x93,0x43,0x08,0x17,0x77,0xe5,0x43,0xe0,0xd8,0x94,0x43,0x67,0xe5,0xe5,0x43,0x47,0xda,0x95,0x43,0x43, 14895 0x9d,0xe6,0x43,0xe2,0xd3,0x97,0x43,0x08,0x9d,0xdd,0xe6,0x43,0xad,0xe7,0x98,0x43,0x09,0xce,0xe8,0x43, 14896 0xff,0x55,0x99,0x43,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x07,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b, 14897 0x43,0x08,0x71,0xcf,0xe9,0x43,0x53,0xb3,0x9a,0x43,0xa7,0xbb,0xe8,0x43,0xdb,0x0d,0x9a,0x43,0xc6,0x14, 14898 0xe7,0x43,0xdb,0x0d,0x9a,0x43,0x08,0x48,0x80,0xe5,0x43,0xdb,0x0d,0x9a,0x43,0x0a,0xb6,0xe4,0x43,0xc3, 14899 0x6e,0x97,0x43,0x76,0x9a,0xe4,0x43,0x74,0xf4,0x94,0x43,0x07,0x76,0x9a,0xe4,0x43,0x79,0xd7,0x93,0x43, 14900 0x08,0xd8,0xac,0xe4,0x43,0x66,0x27,0x92,0x43,0x29,0x1b,0xe5,0x43,0xe0,0xc0,0x90,0x43,0x05,0xd3,0xe5, 14901 0x43,0x19,0x9c,0x90,0x43,0x09,0x06,0x1b,0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x08,0x71,0x0b,0xf4,0x42, 14902 0x00,0x0e,0x8d,0x42,0x8c,0x0f,0x01,0x43,0x3e,0xc0,0x89,0x42,0xf3,0x28,0x06,0x43,0x48,0x9e,0x8b,0x42, 14903 0x08,0x15,0x89,0x09,0x43,0x00,0x0e,0x8d,0x42,0xe0,0x9c,0x0a,0x43,0xc1,0x8b,0x98,0x42,0xa6,0xc1,0x0a, 14904 0x43,0x02,0xa5,0xaa,0x42,0x07,0xa6,0xc1,0x0a,0x43,0xf9,0xf6,0xb0,0x42,0x08,0xa6,0xc1,0x0a,0x43,0x47, 14905 0x8e,0xb4,0x42,0x42,0xaf,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0xe0,0x9c,0x0a,0x43,0xba,0x74,0xbc,0x42,0x08, 14906 0xa1,0xd2,0x09,0x43,0x40,0x47,0xd0,0x42,0x0d,0xab,0x07,0x43,0x91,0xb5,0xd0,0x42,0x3b,0xb9,0x04,0x43, 14907 0xec,0x71,0xba,0x42,0x08,0xe5,0x5b,0x03,0x43,0xe3,0x33,0xa8,0x42,0x63,0xd8,0x00,0x43,0xce,0x70,0x9f, 14908 0x42,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x07,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,0x42,0x08,0xed, 14909 0x6f,0xed,0x42,0x73,0x24,0x9d,0x42,0xd8,0x0c,0xf5,0x42,0x99,0x6c,0x9c,0x42,0x27,0xab,0xfd,0x42,0xea, 14910 0xda,0x9c,0x42,0x08,0x36,0xca,0x03,0x43,0x2b,0x94,0x9e,0x42,0x68,0xc7,0x01,0x43,0x8f,0xbe,0xa2,0x42, 14911 0xfa,0x06,0x08,0x43,0x73,0xb4,0xb5,0x42,0x08,0x8e,0x2e,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0x9d,0xe3,0x08, 14912 0x43,0xd7,0x1e,0x99,0x42,0x28,0x15,0x05,0x43,0x32,0x3b,0x93,0x42,0x08,0x63,0xf0,0x04,0x43,0x70,0xed, 14913 0x8f,0x42,0x71,0x0b,0xf4,0x42,0x32,0x3b,0x93,0x42,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x07,0x1b, 14914 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x09,0x06,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,0x08,0x8e,0x55, 14915 0xc0,0x42,0xb8,0x4d,0x86,0x42,0x60,0xbf,0xd7,0x42,0x3e,0xf0,0x91,0x42,0x63,0xf6,0xe4,0x42,0x70,0xed, 14916 0x8f,0x42,0x08,0x7a,0x89,0xe5,0x42,0xac,0xc8,0x8f,0x42,0xcc,0xf7,0xe5,0x42,0xac,0xc8,0x8f,0x42,0x1b, 14917 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x07,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x08,0x63,0xf6,0xe4, 14918 0x42,0x3b,0x19,0x95,0x42,0xe6,0x61,0xe3,0x42,0x00,0x3e,0x95,0x42,0xf4,0x16,0xe2,0x42,0xc4,0x62,0x95, 14919 0x42,0x08,0x6e,0x74,0xd6,0x42,0x15,0xd1,0x95,0x42,0x97,0x63,0xca,0x42,0xaf,0xcf,0x94,0x42,0xfb,0x2d, 14920 0xbe,0x42,0x86,0x80,0x90,0x42,0x08,0x97,0x03,0xba,0x42,0xce,0x10,0x8f,0x42,0x5e,0x28,0xba,0x42,0x3e, 14921 0xf0,0x91,0x42,0xf2,0x4f,0xbc,0x42,0x45,0xf7,0x96,0x42,0x08,0x27,0x54,0xbf,0x42,0x73,0x24,0x9d,0x42, 14922 0xa5,0xe8,0xc0,0x42,0x86,0xe0,0xa0,0x42,0xe4,0xca,0xc5,0x42,0xed,0x11,0xaa,0x42,0x08,0x54,0xaa,0xc8, 14923 0x42,0x86,0x40,0xb1,0x42,0x59,0x81,0xc5,0x42,0xa1,0x11,0xc4,0x42,0x3e,0xe7,0xbf,0x42,0xfb,0x8d,0xce, 14924 0x42,0x08,0xb4,0x6d,0xb7,0x42,0x30,0xc2,0xd9,0x42,0x46,0xf5,0xc9,0x42,0xdf,0x53,0xd9,0x42,0x38,0x40, 14925 0xcb,0x42,0x62,0x8f,0xcf,0x42,0x08,0x7d,0xf9,0xcc,0x42,0xec,0xa1,0xc2,0x42,0x07,0x43,0xcd,0x42,0x6c, 14926 0xdd,0xb8,0x42,0x2b,0x8b,0xcc,0x42,0x92,0xf5,0xaf,0x42,0x08,0xf9,0x8d,0xce,0x42,0x41,0x57,0xa7,0x42, 14927 0x5b,0xb8,0xd2,0x42,0xae,0x2f,0xa5,0x42,0x18,0x2f,0xd9,0x42,0x13,0x2a,0xa1,0x42,0x08,0x41,0x7e,0xdd, 14928 0x42,0xe3,0x03,0xa0,0x42,0x2e,0xf2,0xe1,0x42,0x7c,0x02,0x9f,0x42,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e, 14929 0x42,0x07,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x08,0x4d,0x63,0xe4,0x42,0x00,0x9e,0xa5,0x42,0xf4, 14930 0x16,0xe2,0x42,0x15,0x31,0xa6,0x42,0x99,0xca,0xdf,0x42,0x2b,0xc4,0xa6,0x42,0x08,0xc0,0x82,0xc6,0x42, 14931 0xc4,0xc2,0xa5,0x42,0x57,0xe1,0xd5,0x42,0x91,0xb5,0xd0,0x42,0x54,0xda,0xd0,0x42,0x97,0x93,0xd2,0x42, 14932 0x08,0x9c,0x3a,0xc7,0x42,0x17,0x58,0xdc,0x42,0x9c,0x0a,0xbf,0x42,0x6e,0xa4,0xde,0x42,0x90,0x25,0xb8, 14933 0x42,0xdf,0x53,0xd9,0x42,0x08,0x59,0x21,0xb5,0x42,0xf2,0xdf,0xd4,0x42,0x51,0x43,0xb3,0x42,0x91,0xb5, 14934 0xd0,0x42,0xc5,0x29,0xbb,0x42,0x0e,0x1a,0xca,0x42,0x08,0x65,0x36,0xc4,0x42,0xd0,0x07,0xbd,0x42,0x3e, 14935 0xe7,0xbf,0x42,0x37,0x09,0xbe,0x42,0x0c,0xea,0xc1,0x42,0xcd,0xd0,0xaf,0x42,0x08,0x2b,0x5b,0xc4,0x42, 14936 0x18,0x08,0xa3,0x42,0x67,0xa6,0xab,0x42,0x99,0x3c,0x94,0x42,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42, 14937 0x09,]; 14938 14939 private struct ThePath { 14940 public: 14941 enum Command { 14942 Bounds, // always first, has 4 args (x0, y0, x1, y1) 14943 StrokeMode, 14944 FillMode, 14945 StrokeFillMode, 14946 NormalStroke, 14947 ThinStroke, 14948 MoveTo, 14949 LineTo, 14950 CubicTo, // cubic bezier 14951 EndPath, 14952 } 14953 14954 public: 14955 const(ubyte)[] path; 14956 uint ppos; 14957 14958 public: 14959 this (const(void)[] apath) pure nothrow @trusted @nogc { 14960 path = cast(const(ubyte)[])apath; 14961 } 14962 14963 @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (ppos >= path.length); } 14964 14965 Command getCommand () nothrow @trusted @nogc { 14966 pragma(inline, true); 14967 if (ppos >= cast(uint)path.length) assert(0, "invalid path"); 14968 return cast(Command)(path.ptr[ppos++]); 14969 } 14970 14971 // number of (x,y) pairs for this command 14972 static int argCount (in Command cmd) nothrow @safe @nogc { 14973 version(aliced) pragma(inline, true); 14974 if (cmd == Command.Bounds) return 2; 14975 else if (cmd == Command.MoveTo || cmd == Command.LineTo) return 1; 14976 else if (cmd == Command.CubicTo) return 3; 14977 else return 0; 14978 } 14979 14980 void skipArgs (int argc) nothrow @trusted @nogc { 14981 pragma(inline, true); 14982 ppos += cast(uint)(float.sizeof*2*argc); 14983 } 14984 14985 float getFloat () nothrow @trusted @nogc { 14986 pragma(inline, true); 14987 if (ppos >= cast(uint)path.length || cast(uint)path.length-ppos < float.sizeof) assert(0, "invalid path"); 14988 version(LittleEndian) { 14989 float res = *cast(const(float)*)(&path.ptr[ppos]); 14990 ppos += cast(uint)float.sizeof; 14991 return res; 14992 } else { 14993 static assert(float.sizeof == 4); 14994 uint xp = path.ptr[ppos]|(path.ptr[ppos+1]<<8)|(path.ptr[ppos+2]<<16)|(path.ptr[ppos+3]<<24); 14995 ppos += cast(uint)float.sizeof; 14996 return *cast(const(float)*)(&xp); 14997 } 14998 } 14999 } 15000 15001 // this will add baphomet's background path to the current NanoVega path, so you can fill it. 15002 public void addBaphometBack (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc { 15003 if (nvg is null) return; 15004 15005 auto path = ThePath(baphometPath); 15006 15007 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15008 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15009 15010 bool inPath = false; 15011 while (!path.empty) { 15012 auto cmd = path.getCommand(); 15013 switch (cmd) { 15014 case ThePath.Command.MoveTo: 15015 inPath = true; 15016 immutable float ex = getScaledX(); 15017 immutable float ey = getScaledY(); 15018 nvg.moveTo(ex, ey); 15019 break; 15020 case ThePath.Command.LineTo: 15021 inPath = true; 15022 immutable float ex = getScaledX(); 15023 immutable float ey = getScaledY(); 15024 nvg.lineTo(ex, ey); 15025 break; 15026 case ThePath.Command.CubicTo: // cubic bezier 15027 inPath = true; 15028 immutable float x1 = getScaledX(); 15029 immutable float y1 = getScaledY(); 15030 immutable float x2 = getScaledX(); 15031 immutable float y2 = getScaledY(); 15032 immutable float ex = getScaledX(); 15033 immutable float ey = getScaledY(); 15034 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15035 break; 15036 case ThePath.Command.EndPath: 15037 if (inPath) return; 15038 break; 15039 default: 15040 path.skipArgs(path.argCount(cmd)); 15041 break; 15042 } 15043 } 15044 } 15045 15046 // this will add baphomet's pupil paths to the current NanoVega path, so you can fill it. 15047 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 { 15048 // pupils starts with "fill-and-stroke" mode 15049 if (nvg is null) return; 15050 15051 auto path = ThePath(baphometPath); 15052 15053 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15054 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15055 15056 bool inPath = false; 15057 bool pupLeft = true; 15058 while (!path.empty) { 15059 auto cmd = path.getCommand(); 15060 switch (cmd) { 15061 case ThePath.Command.StrokeFillMode: inPath = true; break; 15062 case ThePath.Command.MoveTo: 15063 if (!inPath) goto default; 15064 static if (!left) { if (pupLeft) goto default; } 15065 static if (!right) { if (!pupLeft) goto default; } 15066 immutable float ex = getScaledX(); 15067 immutable float ey = getScaledY(); 15068 nvg.moveTo(ex, ey); 15069 break; 15070 case ThePath.Command.LineTo: 15071 if (!inPath) goto default; 15072 static if (!left) { if (pupLeft) goto default; } 15073 static if (!right) { if (!pupLeft) goto default; } 15074 immutable float ex = getScaledX(); 15075 immutable float ey = getScaledY(); 15076 nvg.lineTo(ex, ey); 15077 break; 15078 case ThePath.Command.CubicTo: // cubic bezier 15079 if (!inPath) goto default; 15080 static if (!left) { if (pupLeft) goto default; } 15081 static if (!right) { if (!pupLeft) goto default; } 15082 immutable float x1 = getScaledX(); 15083 immutable float y1 = getScaledY(); 15084 immutable float x2 = getScaledX(); 15085 immutable float y2 = getScaledY(); 15086 immutable float ex = getScaledX(); 15087 immutable float ey = getScaledY(); 15088 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15089 break; 15090 case ThePath.Command.EndPath: 15091 if (inPath) { 15092 if (pupLeft) pupLeft = false; else return; 15093 } 15094 break; 15095 default: 15096 path.skipArgs(path.argCount(cmd)); 15097 break; 15098 } 15099 } 15100 } 15101 15102 // mode: 'f' to allow fills; 's' to allow strokes; 'w' to allow stroke widths; 'c' to replace fills with strokes 15103 public void renderBaphomet(string mode="fs") (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc { 15104 template hasChar(char ch, string s) { 15105 static if (s.length == 0) enum hasChar = false; 15106 else static if (s[0] == ch) enum hasChar = true; 15107 else enum hasChar = hasChar!(ch, s[1..$]); 15108 } 15109 enum AllowStroke = hasChar!('s', mode); 15110 enum AllowFill = hasChar!('f', mode); 15111 enum AllowWidth = hasChar!('w', mode); 15112 enum Contour = hasChar!('c', mode); 15113 //static assert(AllowWidth || AllowFill); 15114 15115 if (nvg is null) return; 15116 15117 auto path = ThePath(baphometPath); 15118 15119 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15120 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15121 15122 int mode = 0; 15123 int sw = ThePath.Command.NormalStroke; 15124 nvg.beginPath(); 15125 while (!path.empty) { 15126 auto cmd = path.getCommand(); 15127 switch (cmd) { 15128 case ThePath.Command.StrokeMode: mode = ThePath.Command.StrokeMode; break; 15129 case ThePath.Command.FillMode: mode = ThePath.Command.FillMode; break; 15130 case ThePath.Command.StrokeFillMode: mode = ThePath.Command.StrokeFillMode; break; 15131 case ThePath.Command.NormalStroke: sw = ThePath.Command.NormalStroke; break; 15132 case ThePath.Command.ThinStroke: sw = ThePath.Command.ThinStroke; break; 15133 case ThePath.Command.MoveTo: 15134 immutable float ex = getScaledX(); 15135 immutable float ey = getScaledY(); 15136 nvg.moveTo(ex, ey); 15137 break; 15138 case ThePath.Command.LineTo: 15139 immutable float ex = getScaledX(); 15140 immutable float ey = getScaledY(); 15141 nvg.lineTo(ex, ey); 15142 break; 15143 case ThePath.Command.CubicTo: // cubic bezier 15144 immutable float x1 = getScaledX(); 15145 immutable float y1 = getScaledY(); 15146 immutable float x2 = getScaledX(); 15147 immutable float y2 = getScaledY(); 15148 immutable float ex = getScaledX(); 15149 immutable float ey = getScaledY(); 15150 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15151 break; 15152 case ThePath.Command.EndPath: 15153 if (mode == ThePath.Command.FillMode || mode == ThePath.Command.StrokeFillMode) { 15154 static if (AllowFill || Contour) { 15155 static if (Contour) { 15156 if (mode == ThePath.Command.FillMode) { nvg.strokeWidth = 1; nvg.stroke(); } 15157 } else { 15158 nvg.fill(); 15159 } 15160 } 15161 } 15162 if (mode == ThePath.Command.StrokeMode || mode == ThePath.Command.StrokeFillMode) { 15163 static if (AllowStroke || Contour) { 15164 static if (AllowWidth) { 15165 if (sw == ThePath.Command.NormalStroke) nvg.strokeWidth = 1; 15166 else if (sw == ThePath.Command.ThinStroke) nvg.strokeWidth = 0.5; 15167 else assert(0, "wtf?!"); 15168 } 15169 nvg.stroke(); 15170 } 15171 } 15172 nvg.newPath(); 15173 break; 15174 default: 15175 path.skipArgs(path.argCount(cmd)); 15176 break; 15177 } 15178 } 15179 nvg.newPath(); 15180 }