1 // 2 // Would be nice: way to take output of the canvas to an image file (raster and/or svg) 3 // 4 // 5 // Copyright (c) 2013 Mikko Mononen memon@inside.org 6 // 7 // This software is provided 'as-is', without any express or implied 8 // warranty. In no event will the authors be held liable for any damages 9 // arising from the use of this software. 10 // Permission is granted to anyone to use this software for any purpose, 11 // including commercial applications, and to alter it and redistribute it 12 // freely, subject to the following restrictions: 13 // 1. The origin of this software must not be misrepresented; you must not 14 // claim that you wrote the original software. If you use this software 15 // in a product, an acknowledgment in the product documentation would be 16 // appreciated but is not required. 17 // 2. Altered source versions must be plainly marked as such, and must not be 18 // misrepresented as being the original software. 19 // 3. This notice may not be removed or altered from any source distribution. 20 // 21 // Fork developement, feature integration and new bugs: 22 // Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org> 23 // Contains code from various contributors. 24 /** 25 The NanoVega API is modeled loosely on HTML5 canvas API. 26 If you know canvas, you're up to speed with NanoVega in no time. 27 28 $(SIDE_BY_SIDE 29 30 $(COLUMN 31 D code with nanovega: 32 33 --- 34 import arsd.nanovega; 35 import arsd.simpledisplay; 36 37 void main () { 38 // The NVGWindow class creates a window and sets up the nvg context for you 39 // you can also do these steps yourself, see the other examples in these docs 40 auto window = new NVGWindow(800, 600, "NanoVega Simple Sample"); 41 42 window.redrawNVGScene = delegate (nvg) { 43 nvg.beginPath(); // start new path 44 nvg.roundedRect(20.5, 30.5, window.width-40, window.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation) 45 // now set filling mode for our rectangle 46 // you can create colors using HTML syntax, or with convenient constants 47 nvg.fillPaint = nvg.linearGradient(20.5, 30.5, window.width-40, window.height-60, 48 NVGColor("#f70"), NVGColor.green); 49 // now fill our rect 50 nvg.fill(); 51 // and draw a nice outline 52 nvg.strokeColor = NVGColor.white; 53 nvg.strokeWidth = 2; 54 nvg.stroke(); 55 // that's all, folks! 56 }; 57 58 window.eventLoop(0, 59 delegate (KeyEvent event) { 60 if (event == "*-Q" || event == "Escape") { window.close(); return; } // quit on Q, Ctrl+Q, and so on 61 }, 62 ); 63 } 64 --- 65 ) 66 67 $(COLUMN 68 Javascript code with HTML5 Canvas 69 70 ```html 71 <!DOCTYPE html> 72 <html> 73 <head> 74 <title>NanoVega Simple Sample (HTML5 Translation)</title> 75 <style> 76 body { background-color: black; } 77 </style> 78 </head> 79 <body> 80 <canvas id="my-canvas" width="800" height="600"></canvas> 81 <script> 82 var canvas = document.getElementById("my-canvas"); 83 var context = canvas.getContext("2d"); 84 85 context.beginPath(); 86 87 context.rect(20.5, 30.5, canvas.width - 40, canvas.height - 60); 88 89 var gradient = context.createLinearGradient(20.5, 30.5, canvas.width - 40, canvas.height - 60); 90 gradient.addColorStop(0, "#f70"); 91 gradient.addColorStop(1, "green"); 92 93 context.fillStyle = gradient; 94 context.fill(); 95 context.closePath(); 96 context.strokeStyle = "white"; 97 context.lineWidth = 2; 98 context.stroke(); 99 </script> 100 </body> 101 </html> 102 ``` 103 ) 104 ) 105 106 $(TIP 107 This library can use either inbuilt or BindBC (external dependency) provided bindings for OpenGL and FreeType. 108 Former are used by default, latter can be activated by passing the `bindbc` version specifier to the compiler. 109 ) 110 111 112 Creating drawing context 113 ======================== 114 115 The drawing context is created using platform specific constructor function. 116 117 --- 118 NVGContext vg = nvgCreateContext(); 119 --- 120 121 $(WARNING You must use created context ONLY in that thread where you created it. 122 There is no way to "transfer" context between threads. Trying to do so 123 will lead to UB.) 124 125 $(WARNING Never issue any commands outside of [beginFrame]/[endFrame]. Trying to 126 do so will lead to UB.) 127 128 129 Drawing shapes with NanoVega 130 ============================ 131 132 Drawing a simple shape using NanoVega consists of four steps: 133 $(LIST 134 * begin a new shape, 135 * define the path to draw, 136 * set fill or stroke, 137 * and finally fill or stroke the path. 138 ) 139 140 --- 141 vg.beginPath(); 142 vg.rect(100, 100, 120, 30); 143 vg.fillColor(nvgRGBA(255, 192, 0, 255)); 144 vg.fill(); 145 --- 146 147 Calling [beginPath] will clear any existing paths and start drawing from blank slate. 148 There are number of number of functions to define the path to draw, such as rectangle, 149 rounded rectangle and ellipse, or you can use the common moveTo, lineTo, bezierTo and 150 arcTo API to compose the paths step by step. 151 152 153 Understanding Composite Paths 154 ============================= 155 156 Because of the way the rendering backend is built in NanoVega, drawing a composite path, 157 that is path consisting from multiple paths defining holes and fills, is a bit more 158 involved. NanoVega uses non-zero filling rule and by default, and paths are wound in counter 159 clockwise order. Keep that in mind when drawing using the low level draw API. In order to 160 wind one of the predefined shapes as a hole, you should call `pathWinding(NVGSolidity.Hole)`, 161 or `pathWinding(NVGSolidity.Solid)` $(B after) defining the path. 162 163 --- 164 vg.beginPath(); 165 vg.rect(100, 100, 120, 30); 166 vg.circle(120, 120, 5); 167 vg.pathWinding(NVGSolidity.Hole); // mark circle as a hole 168 vg.fillColor(nvgRGBA(255, 192, 0, 255)); 169 vg.fill(); 170 --- 171 172 173 Rendering is wrong, what to do? 174 =============================== 175 176 $(LIST 177 * make sure you have created NanoVega context using [nvgCreateContext] call 178 * make sure you have initialised OpenGL with $(B stencil buffer) 179 * make sure you have cleared stencil buffer 180 * make sure all rendering calls happen between [beginFrame] and [endFrame] 181 * to enable more checks for OpenGL errors, add `NVGContextFlag.Debug` flag to [nvgCreateContext] 182 ) 183 184 185 OpenGL state touched by the backend 186 =================================== 187 188 The OpenGL back-end touches following states: 189 190 When textures are uploaded or updated, the following pixel store is set to defaults: 191 `GL_UNPACK_ALIGNMENT`, `GL_UNPACK_ROW_LENGTH`, `GL_UNPACK_SKIP_PIXELS`, `GL_UNPACK_SKIP_ROWS`. 192 Texture binding is also affected. Texture updates can happen when the user loads images, 193 or when new font glyphs are added. Glyphs are added as needed between calls to [beginFrame] 194 and [endFrame]. 195 196 The data for the whole frame is buffered and flushed in [endFrame]. 197 The following code illustrates the OpenGL state touched by the rendering code: 198 199 --- 200 glUseProgram(prog); 201 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 202 glEnable(GL_CULL_FACE); 203 glCullFace(GL_BACK); 204 glFrontFace(GL_CCW); 205 glEnable(GL_BLEND); 206 glDisable(GL_DEPTH_TEST); 207 glDisable(GL_SCISSOR_TEST); 208 glDisable(GL_COLOR_LOGIC_OP); 209 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 210 glStencilMask(0xffffffff); 211 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 212 glStencilFunc(GL_ALWAYS, 0, 0xffffffff); 213 glActiveTexture(GL_TEXTURE1); 214 glActiveTexture(GL_TEXTURE0); 215 glBindBuffer(GL_UNIFORM_BUFFER, buf); 216 glBindVertexArray(arr); 217 glBindBuffer(GL_ARRAY_BUFFER, buf); 218 glBindTexture(GL_TEXTURE_2D, tex); 219 glUniformBlockBinding(... , GLNVG_FRAG_BINDING); 220 --- 221 222 Symbol_groups: 223 224 context_management = 225 ## Context Management 226 227 Functions to create and destory NanoVega context. 228 229 frame_management = 230 ## Frame Management 231 232 To start drawing with NanoVega context, you have to "begin frame", and then 233 "end frame" to flush your rendering commands to GPU. 234 235 composite_operation = 236 ## Composite Operation 237 238 The composite operations in NanoVega are modeled after HTML Canvas API, and 239 the blend func is based on OpenGL (see corresponding manuals for more info). 240 The colors in the blending state have premultiplied alpha. 241 242 color_utils = 243 ## Color Utils 244 245 Colors in NanoVega are stored as ARGB. Zero alpha means "transparent color". 246 247 matrices = 248 ## Matrices and Transformations 249 250 The paths, gradients, patterns and scissor region are transformed by an transformation 251 matrix at the time when they are passed to the API. 252 The current transformation matrix is an affine matrix: 253 254 ---------------------- 255 [sx kx tx] 256 [ky sy ty] 257 [ 0 0 1] 258 ---------------------- 259 260 Where: (sx, sy) define scaling, (kx, ky) skewing, and (tx, ty) translation. 261 The last row is assumed to be (0, 0, 1) and is not stored. 262 263 Apart from [resetTransform], each transformation function first creates 264 specific transformation matrix and pre-multiplies the current transformation by it. 265 266 Current coordinate system (transformation) can be saved and restored using [save] and [restore]. 267 268 The following functions can be used to make calculations on 2x3 transformation matrices. 269 A 2x3 matrix is represented as float[6]. 270 271 state_handling = 272 ## State Handling 273 274 NanoVega contains state which represents how paths will be rendered. 275 The state contains transform, fill and stroke styles, text and font styles, 276 and scissor clipping. 277 278 render_styles = 279 ## Render Styles 280 281 Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern. 282 Solid color is simply defined as a color value, different kinds of paints can be created 283 using [linearGradient], [boxGradient], [radialGradient] and [imagePattern]. 284 285 Current render style can be saved and restored using [save] and [restore]. 286 287 Note that if you want "almost perfect" pixel rendering, you should set aspect ratio to 1, 288 and use `integerCoord+0.5f` as pixel coordinates. 289 290 render_transformations = 291 ## Render Transformations 292 293 Transformation matrix management for the current rendering style. Transformations are applied in 294 backwards order. I.e. if you first translate, and then rotate, your path will be rotated around 295 it's origin, and then translated to the destination point. 296 297 scissoring = 298 ## Scissoring 299 300 Scissoring allows you to clip the rendering into a rectangle. This is useful for various 301 user interface cases like rendering a text edit or a timeline. 302 303 images = 304 ## Images 305 306 NanoVega allows you to load image files in various formats (if arsd loaders are in place) to be used for rendering. 307 In addition you can upload your own image. 308 The parameter imageFlagsList is a list of flags defined in [NVGImageFlag]. 309 310 If you will use your image as fill pattern, it will be scaled by default. To make it repeat, pass 311 [NVGImageFlag.RepeatX] and [NVGImageFlag.RepeatY] flags to image creation function respectively. 312 313 paints = 314 ## Paints 315 316 NanoVega supports four types of paints: linear gradient, box gradient, radial gradient and image pattern. 317 These can be used as paints for strokes and fills. 318 319 gpu_affine = 320 ## Render-Time Affine Transformations 321 322 It is possible to set affine transformation matrix for GPU. That matrix will 323 be applied by the shader code. This can be used to quickly translate and rotate 324 saved paths. Call this $(B only) between [beginFrame] and [endFrame]. 325 326 Note that [beginFrame] resets this matrix to identity one. 327 328 $(WARNING Don't use this for scaling or skewing, or your image will be heavily distorted!) 329 330 paths = 331 ## Paths 332 333 Drawing a new shape starts with [beginPath], it clears all the currently defined paths. 334 Then you define one or more paths and sub-paths which describe the shape. The are functions 335 to draw common shapes like rectangles and circles, and lower level step-by-step functions, 336 which allow to define a path curve by curve. 337 338 NanoVega uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise 339 winding and holes should have counter clockwise order. To specify winding of a path you can 340 call [pathWinding]. This is useful especially for the common shapes, which are drawn CCW. 341 342 Finally you can fill the path using current fill style by calling [fill], and stroke it 343 with current stroke style by calling [stroke]. 344 345 The curve segments and sub-paths are transformed by the current transform. 346 347 picking_api = 348 ## Picking API 349 350 This is picking API that works directly on paths, without rasterizing them first. 351 352 [beginFrame] resets picking state. Then you can create paths as usual, but 353 there is a possibility to perform hit checks $(B before) rasterizing a path. 354 Call either id assigning functions ([currFillHitId]/[currStrokeHitId]), or 355 immediate hit test functions ([hitTestCurrFill]/[hitTestCurrStroke]) 356 before rasterizing (i.e. calling [fill] or [stroke]) to perform hover 357 effects, for example. 358 359 Also note that picking API is ignoring GPU affine transformation matrix. 360 You can "untransform" picking coordinates before checking with [gpuUntransformPoint]. 361 362 $(WARNING Picking API completely ignores clipping. If you want to check for 363 clip regions, you have to manually register them as fill/stroke paths, 364 and perform the necessary logic. See [hitTestForId] function.) 365 366 clipping = 367 ## Clipping with paths 368 369 If scissoring is not enough for you, you can clip rendering with arbitrary path, 370 or with combination of paths. Clip region is saved by [save] and restored by 371 [restore] NanoVega functions. You can combine clip paths with various logic 372 operations, see [NVGClipMode]. 373 374 Note that both [clip] and [clipStroke] are ignoring scissoring (i.e. clip mask 375 is created as if there was no scissor set). Actual rendering is affected by 376 scissors, though. 377 378 text_api = 379 ## Text 380 381 NanoVega allows you to load .ttf files and use the font to render text. 382 You have to load some font, and set proper font size before doing anything 383 with text, as there is no "default" font provided by NanoVega. Also, don't 384 forget to check return value of `createFont()`, 'cause NanoVega won't fail 385 if it cannot load font, it will silently try to render nothing. 386 387 The appearance of the text can be defined by setting the current text style 388 and by specifying the fill color. Common text and font settings such as 389 font size, letter spacing and text align are supported. Font blur allows you 390 to create simple text effects such as drop shadows. 391 392 At render time the font face can be set based on the font handles or name. 393 394 Font measure functions return values in local space, the calculations are 395 carried in the same resolution as the final rendering. This is done because 396 the text glyph positions are snapped to the nearest pixels sharp rendering. 397 398 The local space means that values are not rotated or scale as per the current 399 transformation. For example if you set font size to 12, which would mean that 400 line height is 16, then regardless of the current scaling and rotation, the 401 returned line height is always 16. Some measures may vary because of the scaling 402 since aforementioned pixel snapping. 403 404 While this may sound a little odd, the setup allows you to always render the 405 same way regardless of scaling. I.e. following works regardless of scaling: 406 407 ---------------------- 408 string txt = "Text me up."; 409 vg.textBounds(x, y, txt, bounds); 410 vg.beginPath(); 411 vg.roundedRect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1], 6); 412 vg.fill(); 413 ---------------------- 414 415 Note: currently only solid color fill is supported for text. 416 417 font_stash = 418 ## Low-Level Font Engine (FontStash) 419 420 FontStash is used to load fonts, to manage font atlases, and to get various text metrics. 421 You don't need any graphics context to use FontStash, so you can do things like text 422 layouting outside of your rendering code. Loaded fonts are refcounted, so it is cheap 423 to create new FontStash, copy fonts from NanoVega context into it, and use that new 424 FontStash to do some UI layouting, for example. Also note that you can get text metrics 425 without creating glyph bitmaps (by using [FONSTextBoundsIterator], for example); this way 426 you don't need to waste CPU and memory resources to render unneeded images into font atlas, 427 and you can layout alot of text very fast. 428 429 Note that "FontStash" is abbrevated as "FONS". So when you see some API that contains 430 word "fons" in it, this is not a typo, and it should not read "font" intead. 431 432 TODO for Ketmar: write some nice example code here, and finish documenting FontStash API. 433 */ 434 module arsd.nanovega; 435 436 /// This example shows how to do the NanoVega sample without the [NVGWindow] helper class. 437 unittest { 438 import arsd.simpledisplay; 439 440 import arsd.nanovega; 441 442 void main () { 443 NVGContext nvg; // our NanoVega context 444 445 // we need at least OpenGL3 with GLSL to use NanoVega, 446 // so let's tell simpledisplay about that 447 setOpenGLContextVersion(3, 0); 448 449 // now create OpenGL window 450 auto sdmain = new SimpleWindow(800, 600, "NanoVega Simple Sample", OpenGlOptions.yes, Resizability.allowResizing); 451 452 // we need to destroy NanoVega context on window close 453 // stricly speaking, it is not necessary, as nothing fatal 454 // will happen if you'll forget it, but let's be polite. 455 // note that we cannot do that *after* our window was closed, 456 // as we need alive OpenGL context to do proper cleanup. 457 sdmain.onClosing = delegate () { 458 nvg.kill(); 459 }; 460 461 // this is called just before our window will be shown for the first time. 462 // we must create NanoVega context here, as it needs to initialize 463 // internal OpenGL subsystem with valid OpenGL context. 464 sdmain.visibleForTheFirstTime = delegate () { 465 // yes, that's all 466 nvg = nvgCreateContext(); 467 if (nvg is null) assert(0, "cannot initialize NanoVega"); 468 }; 469 470 // this callback will be called when we will need to repaint our window 471 sdmain.redrawOpenGlScene = delegate () { 472 // fix viewport (we can do this in resize event, or here, it doesn't matter) 473 glViewport(0, 0, sdmain.width, sdmain.height); 474 475 // clear window 476 glClearColor(0, 0, 0, 0); 477 glClear(glNVGClearFlags); // use NanoVega API to get flags for OpenGL call 478 479 { 480 nvg.beginFrame(sdmain.width, sdmain.height); // begin rendering 481 scope(exit) nvg.endFrame(); // and flush render queue on exit 482 483 nvg.beginPath(); // start new path 484 nvg.roundedRect(20.5, 30.5, sdmain.width-40, sdmain.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation) 485 // now set filling mode for our rectangle 486 // you can create colors using HTML syntax, or with convenient constants 487 nvg.fillPaint = nvg.linearGradient(20.5, 30.5, sdmain.width-40, sdmain.height-60, NVGColor("#f70"), NVGColor.green); 488 // now fill our rect 489 nvg.fill(); 490 // and draw a nice outline 491 nvg.strokeColor = NVGColor.white; 492 nvg.strokeWidth = 2; 493 nvg.stroke(); 494 // that's all, folks! 495 } 496 }; 497 498 sdmain.eventLoop(0, // no pulse timer required 499 delegate (KeyEvent event) { 500 if (event == "*-Q" || event == "Escape") { sdmain.close(); return; } // quit on Q, Ctrl+Q, and so on 501 }, 502 ); 503 504 flushGui(); // let OS do it's cleanup 505 } 506 } 507 508 private: 509 510 version(aliced) { 511 import iv.meta; 512 import iv.vfs; 513 } else { 514 private alias usize = size_t; 515 // i fear phobos! 516 private template Unqual(T) { 517 static if (is(T U == immutable U)) alias Unqual = U; 518 else static if (is(T U == shared inout const U)) alias Unqual = U; 519 else static if (is(T U == shared inout U)) alias Unqual = U; 520 else static if (is(T U == shared const U)) alias Unqual = U; 521 else static if (is(T U == shared U)) alias Unqual = U; 522 else static if (is(T U == inout const U)) alias Unqual = U; 523 else static if (is(T U == inout U)) alias Unqual = U; 524 else static if (is(T U == const U)) alias Unqual = U; 525 else alias Unqual = T; 526 } 527 private template isAnyCharType(T, bool unqual=false) { 528 static if (unqual) private alias UT = Unqual!T; else private alias UT = T; 529 enum isAnyCharType = is(UT == char) || is(UT == wchar) || is(UT == dchar); 530 } 531 private template isWideCharType(T, bool unqual=false) { 532 static if (unqual) private alias UT = Unqual!T; else private alias UT = T; 533 enum isWideCharType = is(UT == wchar) || is(UT == dchar); 534 } 535 } 536 version(nanovg_disable_vfs) { 537 enum NanoVegaHasIVVFS = false; 538 } else { 539 static if (is(typeof((){import iv.vfs;}))) { 540 enum NanoVegaHasIVVFS = true; 541 import iv.vfs; 542 } else { 543 enum NanoVegaHasIVVFS = false; 544 } 545 } 546 547 // ////////////////////////////////////////////////////////////////////////// // 548 // engine 549 // ////////////////////////////////////////////////////////////////////////// // 550 import core.stdc.stdlib : malloc, realloc, free; 551 import core.stdc.string : memset, memcpy, strlen; 552 import std.math : PI; 553 554 //version = nanovg_force_stb_ttf; 555 556 version(Posix) { 557 version = nanovg_use_freetype; 558 } else { 559 version = nanovg_disable_fontconfig; 560 } 561 562 version (bindbc) { 563 version = nanovg_builtin_fontconfig_bindings; 564 version = nanovg_bindbc_opengl_bindings; 565 version = nanovg_bindbc_freetype_bindings; 566 version(BindFT_Dynamic) 567 static assert(0, "AsumFace was too lazy to write the code for the dynamic bindbc freetype bindings"); 568 else { 569 version(BindFT_Static) {} 570 else 571 static assert(0, "well, duh. you got to pass the BindFT_Static version identifier to the compiler"); 572 } 573 } else version(aliced) { 574 version = nanovg_default_no_font_aa; 575 version = nanovg_builtin_fontconfig_bindings; 576 version = nanovg_builtin_freetype_bindings; 577 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings 578 } else { 579 version (Have_bindbc_opengl) 580 version = nanovg_bindbc_opengl_bindings; 581 else 582 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings 583 version (Have_bindbc_freetype) 584 version = nanovg_bindbc_freetype_bindings; 585 else 586 version = nanovg_builtin_freetype_bindings; 587 version = nanovg_builtin_fontconfig_bindings; 588 } 589 590 version(nanovg_disable_fontconfig) { 591 public enum NanoVegaHasFontConfig = false; 592 } else { 593 public enum NanoVegaHasFontConfig = true; 594 version(nanovg_builtin_fontconfig_bindings) {} else import iv.fontconfig; 595 } 596 597 //version = nanovg_bench_flatten; 598 599 /++ 600 Annotation to indicate the marked function is compatible with [arsd.script]. 601 602 603 Any function that takes a [Color] argument will be passed a string instead. 604 605 Scriptable Functions 606 ==================== 607 608 $(UDA_USES) 609 610 $(ALWAYS_DOCUMENT) 611 +/ 612 private enum scriptable = "arsd_jsvar_compatible"; 613 614 public: 615 alias NVG_PI = PI; 616 617 enum NanoVegaHasArsdColor = (is(typeof((){ import arsd.color; }))); 618 enum NanoVegaHasArsdImage = (is(typeof((){ import arsd.color; import arsd.image; }))); 619 620 static if (NanoVegaHasArsdColor) private import arsd.color; 621 static if (NanoVegaHasArsdImage) { 622 private import arsd.image; 623 } else { 624 void stbi_set_unpremultiply_on_load (int flag_true_if_should_unpremultiply) {} 625 void stbi_convert_iphone_png_to_rgb (int flag_true_if_should_convert) {} 626 ubyte* stbi_load (const(char)* filename, int* x, int* y, int* comp, int req_comp) { return null; } 627 ubyte* stbi_load_from_memory (const(void)* buffer, int len, int* x, int* y, int* comp, int req_comp) { return null; } 628 void stbi_image_free (void* retval_from_stbi_load) {} 629 } 630 631 version(nanovg_default_no_font_aa) { 632 __gshared bool NVG_INVERT_FONT_AA = false; 633 } else { 634 __gshared bool NVG_INVERT_FONT_AA = true; 635 } 636 637 638 /// this is branchless for ints on x86, and even for longs on x86_64 639 public ubyte nvgClampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) { 640 static if (__VERSION__ > 2067) pragma(inline, true); 641 static if (T.sizeof == 2 || T.sizeof == 4) { 642 static if (__traits(isUnsigned, T)) { 643 return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24))); 644 } else { 645 n &= -cast(int)(n >= 0); 646 return cast(ubyte)(n|((255-cast(int)n)>>31)); 647 } 648 } else static if (T.sizeof == 1) { 649 static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?"); 650 return cast(ubyte)n; 651 } else static if (T.sizeof == 8) { 652 static if (__traits(isUnsigned, T)) { 653 return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56))); 654 } else { 655 n &= -cast(long)(n >= 0); 656 return cast(ubyte)(n|((255-cast(long)n)>>63)); 657 } 658 } else { 659 static assert(false, "clampToByte: integer too big"); 660 } 661 } 662 663 664 /// NanoVega RGBA color 665 /// Group: color_utils 666 public align(1) struct NVGColor { 667 align(1): 668 public: 669 float[4] rgba = 0; /// default color is transparent (a=1 is opaque) 670 671 public: 672 @property string toString () const @safe { import std.string : format; return "NVGColor(%s,%s,%s,%s)".format(r, g, b, a); } 673 674 public: 675 enum transparent = NVGColor(0.0f, 0.0f, 0.0f, 0.0f); 676 enum k8orange = NVGColor(1.0f, 0.5f, 0.0f, 1.0f); 677 678 enum aliceblue = NVGColor(240, 248, 255); 679 enum antiquewhite = NVGColor(250, 235, 215); 680 enum aqua = NVGColor(0, 255, 255); 681 enum aquamarine = NVGColor(127, 255, 212); 682 enum azure = NVGColor(240, 255, 255); 683 enum beige = NVGColor(245, 245, 220); 684 enum bisque = NVGColor(255, 228, 196); 685 enum black = NVGColor(0, 0, 0); // basic color 686 enum blanchedalmond = NVGColor(255, 235, 205); 687 enum blue = NVGColor(0, 0, 255); // basic color 688 enum blueviolet = NVGColor(138, 43, 226); 689 enum brown = NVGColor(165, 42, 42); 690 enum burlywood = NVGColor(222, 184, 135); 691 enum cadetblue = NVGColor(95, 158, 160); 692 enum chartreuse = NVGColor(127, 255, 0); 693 enum chocolate = NVGColor(210, 105, 30); 694 enum coral = NVGColor(255, 127, 80); 695 enum cornflowerblue = NVGColor(100, 149, 237); 696 enum cornsilk = NVGColor(255, 248, 220); 697 enum crimson = NVGColor(220, 20, 60); 698 enum cyan = NVGColor(0, 255, 255); // basic color 699 enum darkblue = NVGColor(0, 0, 139); 700 enum darkcyan = NVGColor(0, 139, 139); 701 enum darkgoldenrod = NVGColor(184, 134, 11); 702 enum darkgray = NVGColor(169, 169, 169); 703 enum darkgreen = NVGColor(0, 100, 0); 704 enum darkgrey = NVGColor(169, 169, 169); 705 enum darkkhaki = NVGColor(189, 183, 107); 706 enum darkmagenta = NVGColor(139, 0, 139); 707 enum darkolivegreen = NVGColor(85, 107, 47); 708 enum darkorange = NVGColor(255, 140, 0); 709 enum darkorchid = NVGColor(153, 50, 204); 710 enum darkred = NVGColor(139, 0, 0); 711 enum darksalmon = NVGColor(233, 150, 122); 712 enum darkseagreen = NVGColor(143, 188, 143); 713 enum darkslateblue = NVGColor(72, 61, 139); 714 enum darkslategray = NVGColor(47, 79, 79); 715 enum darkslategrey = NVGColor(47, 79, 79); 716 enum darkturquoise = NVGColor(0, 206, 209); 717 enum darkviolet = NVGColor(148, 0, 211); 718 enum deeppink = NVGColor(255, 20, 147); 719 enum deepskyblue = NVGColor(0, 191, 255); 720 enum dimgray = NVGColor(105, 105, 105); 721 enum dimgrey = NVGColor(105, 105, 105); 722 enum dodgerblue = NVGColor(30, 144, 255); 723 enum firebrick = NVGColor(178, 34, 34); 724 enum floralwhite = NVGColor(255, 250, 240); 725 enum forestgreen = NVGColor(34, 139, 34); 726 enum fuchsia = NVGColor(255, 0, 255); 727 enum gainsboro = NVGColor(220, 220, 220); 728 enum ghostwhite = NVGColor(248, 248, 255); 729 enum gold = NVGColor(255, 215, 0); 730 enum goldenrod = NVGColor(218, 165, 32); 731 enum gray = NVGColor(128, 128, 128); // basic color 732 enum green = NVGColor(0, 128, 0); // basic color 733 enum greenyellow = NVGColor(173, 255, 47); 734 enum grey = NVGColor(128, 128, 128); // basic color 735 enum honeydew = NVGColor(240, 255, 240); 736 enum hotpink = NVGColor(255, 105, 180); 737 enum indianred = NVGColor(205, 92, 92); 738 enum indigo = NVGColor(75, 0, 130); 739 enum ivory = NVGColor(255, 255, 240); 740 enum khaki = NVGColor(240, 230, 140); 741 enum lavender = NVGColor(230, 230, 250); 742 enum lavenderblush = NVGColor(255, 240, 245); 743 enum lawngreen = NVGColor(124, 252, 0); 744 enum lemonchiffon = NVGColor(255, 250, 205); 745 enum lightblue = NVGColor(173, 216, 230); 746 enum lightcoral = NVGColor(240, 128, 128); 747 enum lightcyan = NVGColor(224, 255, 255); 748 enum lightgoldenrodyellow = NVGColor(250, 250, 210); 749 enum lightgray = NVGColor(211, 211, 211); 750 enum lightgreen = NVGColor(144, 238, 144); 751 enum lightgrey = NVGColor(211, 211, 211); 752 enum lightpink = NVGColor(255, 182, 193); 753 enum lightsalmon = NVGColor(255, 160, 122); 754 enum lightseagreen = NVGColor(32, 178, 170); 755 enum lightskyblue = NVGColor(135, 206, 250); 756 enum lightslategray = NVGColor(119, 136, 153); 757 enum lightslategrey = NVGColor(119, 136, 153); 758 enum lightsteelblue = NVGColor(176, 196, 222); 759 enum lightyellow = NVGColor(255, 255, 224); 760 enum lime = NVGColor(0, 255, 0); 761 enum limegreen = NVGColor(50, 205, 50); 762 enum linen = NVGColor(250, 240, 230); 763 enum magenta = NVGColor(255, 0, 255); // basic color 764 enum maroon = NVGColor(128, 0, 0); 765 enum mediumaquamarine = NVGColor(102, 205, 170); 766 enum mediumblue = NVGColor(0, 0, 205); 767 enum mediumorchid = NVGColor(186, 85, 211); 768 enum mediumpurple = NVGColor(147, 112, 219); 769 enum mediumseagreen = NVGColor(60, 179, 113); 770 enum mediumslateblue = NVGColor(123, 104, 238); 771 enum mediumspringgreen = NVGColor(0, 250, 154); 772 enum mediumturquoise = NVGColor(72, 209, 204); 773 enum mediumvioletred = NVGColor(199, 21, 133); 774 enum midnightblue = NVGColor(25, 25, 112); 775 enum mintcream = NVGColor(245, 255, 250); 776 enum mistyrose = NVGColor(255, 228, 225); 777 enum moccasin = NVGColor(255, 228, 181); 778 enum navajowhite = NVGColor(255, 222, 173); 779 enum navy = NVGColor(0, 0, 128); 780 enum oldlace = NVGColor(253, 245, 230); 781 enum olive = NVGColor(128, 128, 0); 782 enum olivedrab = NVGColor(107, 142, 35); 783 enum orange = NVGColor(255, 165, 0); 784 enum orangered = NVGColor(255, 69, 0); 785 enum orchid = NVGColor(218, 112, 214); 786 enum palegoldenrod = NVGColor(238, 232, 170); 787 enum palegreen = NVGColor(152, 251, 152); 788 enum paleturquoise = NVGColor(175, 238, 238); 789 enum palevioletred = NVGColor(219, 112, 147); 790 enum papayawhip = NVGColor(255, 239, 213); 791 enum peachpuff = NVGColor(255, 218, 185); 792 enum peru = NVGColor(205, 133, 63); 793 enum pink = NVGColor(255, 192, 203); 794 enum plum = NVGColor(221, 160, 221); 795 enum powderblue = NVGColor(176, 224, 230); 796 enum purple = NVGColor(128, 0, 128); 797 enum red = NVGColor(255, 0, 0); // basic color 798 enum rosybrown = NVGColor(188, 143, 143); 799 enum royalblue = NVGColor(65, 105, 225); 800 enum saddlebrown = NVGColor(139, 69, 19); 801 enum salmon = NVGColor(250, 128, 114); 802 enum sandybrown = NVGColor(244, 164, 96); 803 enum seagreen = NVGColor(46, 139, 87); 804 enum seashell = NVGColor(255, 245, 238); 805 enum sienna = NVGColor(160, 82, 45); 806 enum silver = NVGColor(192, 192, 192); 807 enum skyblue = NVGColor(135, 206, 235); 808 enum slateblue = NVGColor(106, 90, 205); 809 enum slategray = NVGColor(112, 128, 144); 810 enum slategrey = NVGColor(112, 128, 144); 811 enum snow = NVGColor(255, 250, 250); 812 enum springgreen = NVGColor(0, 255, 127); 813 enum steelblue = NVGColor(70, 130, 180); 814 enum tan = NVGColor(210, 180, 140); 815 enum teal = NVGColor(0, 128, 128); 816 enum thistle = NVGColor(216, 191, 216); 817 enum tomato = NVGColor(255, 99, 71); 818 enum turquoise = NVGColor(64, 224, 208); 819 enum violet = NVGColor(238, 130, 238); 820 enum wheat = NVGColor(245, 222, 179); 821 enum white = NVGColor(255, 255, 255); // basic color 822 enum whitesmoke = NVGColor(245, 245, 245); 823 enum yellow = NVGColor(255, 255, 0); // basic color 824 enum yellowgreen = NVGColor(154, 205, 50); 825 826 nothrow @safe @nogc: 827 public: 828 /// 829 this (ubyte ar, ubyte ag, ubyte ab, ubyte aa=255) pure { 830 pragma(inline, true); 831 r = ar/255.0f; 832 g = ag/255.0f; 833 b = ab/255.0f; 834 a = aa/255.0f; 835 } 836 837 /// 838 this (float ar, float ag, float ab, float aa=1.0f) pure { 839 pragma(inline, true); 840 r = ar; 841 g = ag; 842 b = ab; 843 a = aa; 844 } 845 846 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 847 this (uint c) pure { 848 pragma(inline, true); 849 r = (c&0xff)/255.0f; 850 g = ((c>>8)&0xff)/255.0f; 851 b = ((c>>16)&0xff)/255.0f; 852 a = ((c>>24)&0xff)/255.0f; 853 } 854 855 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb" 856 this (const(char)[] srgb) { 857 static int c2d (char ch) pure nothrow @safe @nogc { 858 pragma(inline, true); 859 return 860 ch >= '0' && ch <= '9' ? ch-'0' : 861 ch >= 'A' && ch <= 'F' ? ch-'A'+10 : 862 ch >= 'a' && ch <= 'f' ? ch-'a'+10 : 863 -1; 864 } 865 int[8] digs; 866 int dc = -1; 867 foreach (immutable char ch; srgb) { 868 if (ch <= ' ') continue; 869 if (ch == '#') { 870 if (dc != -1) { dc = -1; break; } 871 dc = 0; 872 } else { 873 if (dc >= digs.length) { dc = -1; break; } 874 if ((digs[dc++] = c2d(ch)) < 0) { dc = -1; break; } 875 } 876 } 877 switch (dc) { 878 case 3: // rgb 879 a = 1.0f; 880 r = digs[0]/15.0f; 881 g = digs[1]/15.0f; 882 b = digs[2]/15.0f; 883 break; 884 case 4: // argb 885 a = digs[0]/15.0f; 886 r = digs[1]/15.0f; 887 g = digs[2]/15.0f; 888 b = digs[3]/15.0f; 889 break; 890 case 6: // rrggbb 891 a = 1.0f; 892 r = (digs[0]*16+digs[1])/255.0f; 893 g = (digs[2]*16+digs[3])/255.0f; 894 b = (digs[4]*16+digs[5])/255.0f; 895 break; 896 case 8: // aarrggbb 897 a = (digs[0]*16+digs[1])/255.0f; 898 r = (digs[2]*16+digs[3])/255.0f; 899 g = (digs[4]*16+digs[5])/255.0f; 900 b = (digs[6]*16+digs[7])/255.0f; 901 break; 902 default: 903 break; 904 } 905 } 906 907 /// Is this color completely opaque? 908 @property bool isOpaque () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] >= 1.0f); } 909 /// Is this color completely transparent? 910 @property bool isTransparent () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] <= 0.0f); } 911 912 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 913 @property uint asUint () const pure { 914 pragma(inline, true); 915 return 916 cast(uint)(r*255)| 917 (cast(uint)(g*255)<<8)| 918 (cast(uint)(b*255)<<16)| 919 (cast(uint)(a*255)<<24); 920 } 921 922 alias asUintABGR = asUint; /// Ditto. 923 924 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 925 static NVGColor fromUint (uint c) pure { pragma(inline, true); return NVGColor(c); } 926 927 alias fromUintABGR = fromUint; /// Ditto. 928 929 /// AARRGGBB 930 @property uint asUintARGB () const pure { 931 pragma(inline, true); 932 return 933 cast(uint)(b*255)| 934 (cast(uint)(g*255)<<8)| 935 (cast(uint)(r*255)<<16)| 936 (cast(uint)(a*255)<<24); 937 } 938 939 /// AARRGGBB 940 static NVGColor fromUintARGB (uint c) pure { pragma(inline, true); return NVGColor((c>>16)&0xff, (c>>8)&0xff, c&0xff, (c>>24)&0xff); } 941 942 @property ref inout(float) r () inout pure @trusted { pragma(inline, true); return rgba.ptr[0]; } /// 943 @property ref inout(float) g () inout pure @trusted { pragma(inline, true); return rgba.ptr[1]; } /// 944 @property ref inout(float) b () inout pure @trusted { pragma(inline, true); return rgba.ptr[2]; } /// 945 @property ref inout(float) a () inout pure @trusted { pragma(inline, true); return rgba.ptr[3]; } /// 946 947 ref NVGColor applyTint() (const scope auto ref NVGColor tint) nothrow @trusted @nogc { 948 if (tint.a == 0) return this; 949 foreach (immutable idx, ref float v; rgba[0..4]) { 950 v = nvg__clamp(v*tint.rgba.ptr[idx], 0.0f, 1.0f); 951 } 952 return this; 953 } 954 955 NVGHSL asHSL() (bool useWeightedLightness=false) const { pragma(inline, true); return NVGHSL.fromColor(this, useWeightedLightness); } /// 956 static fromHSL() (const scope auto ref NVGHSL hsl) { pragma(inline, true); return hsl.asColor; } /// 957 958 static if (NanoVegaHasArsdColor) { 959 Color toArsd () const { pragma(inline, true); return Color(cast(int)(r*255), cast(int)(g*255), cast(int)(b*255), cast(int)(a*255)); } /// 960 static NVGColor fromArsd (in Color c) { pragma(inline, true); return NVGColor(c.r, c.g, c.b, c.a); } /// 961 /// 962 this (in Color c) { 963 version(aliced) pragma(inline, true); 964 r = c.r/255.0f; 965 g = c.g/255.0f; 966 b = c.b/255.0f; 967 a = c.a/255.0f; 968 } 969 } 970 } 971 972 973 /// NanoVega A-HSL color 974 /// Group: color_utils 975 public align(1) struct NVGHSL { 976 align(1): 977 float h=0, s=0, l=1, a=1; /// 978 979 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)); } 980 981 nothrow @safe @nogc: 982 public: 983 /// 984 this (float ah, float as, float al, float aa=1) pure { pragma(inline, true); h = ah; s = as; l = al; a = aa; } 985 986 NVGColor asColor () const { pragma(inline, true); return nvgHSLA(h, s, l, a); } /// 987 988 // taken from Adam's arsd.color 989 /** Converts an RGB color into an HSL triplet. 990 * [useWeightedLightness] will try to get a better value for luminosity for the human eye, 991 * which is more sensitive to green than red and more to red than blue. 992 * If it is false, it just does average of the rgb. */ 993 static NVGHSL fromColor() (const scope auto ref NVGColor c, bool useWeightedLightness=false) pure { 994 NVGHSL res; 995 res.a = c.a; 996 float r1 = c.r; 997 float g1 = c.g; 998 float b1 = c.b; 999 1000 float maxColor = r1; 1001 if (g1 > maxColor) maxColor = g1; 1002 if (b1 > maxColor) maxColor = b1; 1003 float minColor = r1; 1004 if (g1 < minColor) minColor = g1; 1005 if (b1 < minColor) minColor = b1; 1006 1007 res.l = (maxColor+minColor)/2; 1008 if (useWeightedLightness) { 1009 // the colors don't affect the eye equally 1010 // this is a little more accurate than plain HSL numbers 1011 res.l = 0.2126*r1+0.7152*g1+0.0722*b1; 1012 } 1013 if (maxColor != minColor) { 1014 if (res.l < 0.5) { 1015 res.s = (maxColor-minColor)/(maxColor+minColor); 1016 } else { 1017 res.s = (maxColor-minColor)/(2.0-maxColor-minColor); 1018 } 1019 if (r1 == maxColor) { 1020 res.h = (g1-b1)/(maxColor-minColor); 1021 } else if(g1 == maxColor) { 1022 res.h = 2.0+(b1-r1)/(maxColor-minColor); 1023 } else { 1024 res.h = 4.0+(r1-g1)/(maxColor-minColor); 1025 } 1026 } 1027 1028 res.h = res.h*60; 1029 if (res.h < 0) res.h += 360; 1030 res.h /= 360; 1031 1032 return res; 1033 } 1034 } 1035 1036 1037 //version = nanovega_debug_image_manager; 1038 //version = nanovega_debug_image_manager_rc; 1039 1040 /** NanoVega image handle. 1041 * 1042 * This is refcounted struct, so you don't need to do anything special to free it once it is allocated. 1043 * 1044 * Group: images 1045 */ 1046 struct NVGImage { 1047 enum isOpaqueStruct = true; 1048 private: 1049 NVGContext ctx; 1050 int id; // backend image id 1051 1052 public: 1053 /// 1054 this() (const scope auto ref NVGImage src) nothrow @trusted @nogc { 1055 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); } 1056 if (src.id > 0 && src.ctx !is null) { 1057 ctx = cast(NVGContext)src.ctx; 1058 id = src.id; 1059 ctx.nvg__imageIncRef(id); 1060 } 1061 } 1062 1063 /// 1064 ~this () nothrow @trusted @nogc { version(aliced) pragma(inline, true); clear(); } 1065 1066 /// 1067 this (this) nothrow @trusted @nogc { 1068 version(aliced) pragma(inline, true); 1069 if (id > 0 && ctx !is null) { 1070 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p postblit (imgid=%d)\n", &this, id); } 1071 ctx.nvg__imageIncRef(id); 1072 } 1073 } 1074 1075 /// 1076 void opAssign() (const scope auto ref NVGImage src) nothrow @trusted @nogc { 1077 if (src.id <= 0 || src.ctx is null) { 1078 clear(); 1079 } else { 1080 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); } 1081 if (src.id > 0 && src.ctx !is null) (cast(NVGContext)src.ctx).nvg__imageIncRef(src.id); 1082 if (id > 0 && ctx !is null) ctx.nvg__imageDecRef(id); 1083 ctx = cast(NVGContext)src.ctx; 1084 id = src.id; 1085 } 1086 } 1087 1088 /// Free this image. 1089 void clear () nothrow @trusted @nogc { 1090 if (id > 0 && ctx !is null) { 1091 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p cleared (imgid=%d)\n", &this, id); } 1092 ctx.nvg__imageDecRef(id); 1093 } 1094 id = 0; 1095 ctx = null; 1096 } 1097 1098 /// Is this image valid? 1099 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (id > 0 && ctx.valid); } 1100 1101 /// Is the given image valid and comes from the same context? 1102 @property bool isSameContext (const(NVGContext) actx) const pure nothrow @safe @nogc { pragma(inline, true); return (actx !is null && ctx is actx); } 1103 1104 /// Returns image width, or zero for invalid image. 1105 int width () const nothrow @trusted @nogc { 1106 int w = 0; 1107 if (valid) { 1108 int h = void; 1109 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h); 1110 } 1111 return w; 1112 } 1113 1114 /// Returns image height, or zero for invalid image. 1115 int height () const nothrow @trusted @nogc { 1116 int h = 0; 1117 if (valid) { 1118 int w = void; 1119 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h); 1120 } 1121 return h; 1122 } 1123 } 1124 1125 1126 /// Paint parameters for various fills. Don't change anything here! 1127 /// Group: render_styles 1128 public struct NVGPaint { 1129 enum isOpaqueStruct = true; 1130 1131 NVGMatrix xform; 1132 float[2] extent = 0.0f; 1133 float radius = 0.0f; 1134 float feather = 0.0f; 1135 NVGColor innerColor; /// this can be used to modulate images (fill/font) 1136 NVGColor middleColor; 1137 NVGColor outerColor; 1138 float midp = -1; // middle stop for 3-color gradient 1139 NVGImage image; 1140 bool simpleColor; /// if `true`, only innerColor is used, and this is solid-color paint 1141 1142 this() (const scope auto ref NVGPaint p) nothrow @trusted @nogc { 1143 xform = p.xform; 1144 extent[] = p.extent[]; 1145 radius = p.radius; 1146 feather = p.feather; 1147 innerColor = p.innerColor; 1148 middleColor = p.middleColor; 1149 midp = p.midp; 1150 outerColor = p.outerColor; 1151 image = p.image; 1152 simpleColor = p.simpleColor; 1153 } 1154 1155 void opAssign() (const scope auto ref NVGPaint p) nothrow @trusted @nogc { 1156 xform = p.xform; 1157 extent[] = p.extent[]; 1158 radius = p.radius; 1159 feather = p.feather; 1160 innerColor = p.innerColor; 1161 middleColor = p.middleColor; 1162 midp = p.midp; 1163 outerColor = p.outerColor; 1164 image = p.image; 1165 simpleColor = p.simpleColor; 1166 } 1167 1168 void clear () nothrow @trusted @nogc { 1169 version(aliced) pragma(inline, true); 1170 import core.stdc.string : memset; 1171 image.clear(); 1172 memset(&this, 0, this.sizeof); 1173 simpleColor = true; 1174 } 1175 } 1176 1177 /// Path winding. 1178 /// Group: paths 1179 public enum NVGWinding { 1180 CCW = 1, /// Winding for solid shapes 1181 CW = 2, /// Winding for holes 1182 } 1183 1184 /// Path solidity. 1185 /// Group: paths 1186 public enum NVGSolidity { 1187 Solid = 1, /// Solid shape (CCW winding). 1188 Hole = 2, /// Hole (CW winding). 1189 } 1190 1191 /// Line cap style. 1192 /// Group: render_styles 1193 public enum NVGLineCap { 1194 Butt, /// 1195 Round, /// 1196 Square, /// 1197 Bevel, /// 1198 Miter, /// 1199 } 1200 1201 /// Text align. 1202 /// Group: text_api 1203 public align(1) struct NVGTextAlign { 1204 align(1): 1205 /// Horizontal align. 1206 enum H : ubyte { 1207 Left = 0, /// Default, align text horizontally to left. 1208 Center = 1, /// Align text horizontally to center. 1209 Right = 2, /// Align text horizontally to right. 1210 } 1211 1212 /// Vertical align. 1213 enum V : ubyte { 1214 Baseline = 0, /// Default, align text vertically to baseline. 1215 Top = 1, /// Align text vertically to top. 1216 Middle = 2, /// Align text vertically to middle. 1217 Bottom = 3, /// Align text vertically to bottom. 1218 } 1219 1220 pure nothrow @safe @nogc: 1221 public: 1222 this (H h) { pragma(inline, true); value = h; } /// 1223 this (V v) { pragma(inline, true); value = cast(ubyte)(v<<4); } /// 1224 this (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1225 this (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1226 void reset () { pragma(inline, true); value = 0; } /// 1227 void reset (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1228 void reset (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1229 @property: 1230 bool left () const { pragma(inline, true); return ((value&0x0f) == H.Left); } /// 1231 void left (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Left : 0)); } /// 1232 bool center () const { pragma(inline, true); return ((value&0x0f) == H.Center); } /// 1233 void center (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Center : 0)); } /// 1234 bool right () const { pragma(inline, true); return ((value&0x0f) == H.Right); } /// 1235 void right (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Right : 0)); } /// 1236 // 1237 bool baseline () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Baseline); } /// 1238 void baseline (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Baseline<<4 : 0)); } /// 1239 bool top () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Top); } /// 1240 void top (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Top<<4 : 0)); } /// 1241 bool middle () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Middle); } /// 1242 void middle (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Middle<<4 : 0)); } /// 1243 bool bottom () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Bottom); } /// 1244 void bottom (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Bottom<<4 : 0)); } /// 1245 // 1246 H horizontal () const { pragma(inline, true); return cast(H)(value&0x0f); } /// 1247 void horizontal (H v) { pragma(inline, true); value = (value&0xf0)|v; } /// 1248 // 1249 V vertical () const { pragma(inline, true); return cast(V)((value>>4)&0x0f); } /// 1250 void vertical (V v) { pragma(inline, true); value = (value&0x0f)|cast(ubyte)(v<<4); } /// 1251 // 1252 private: 1253 ubyte value = 0; // low nibble: horizontal; high nibble: vertical 1254 } 1255 1256 /// Blending type. 1257 /// Group: composite_operation 1258 public enum NVGBlendFactor { 1259 Zero = 1<<0, /// 1260 One = 1<<1, /// 1261 SrcColor = 1<<2, /// 1262 OneMinusSrcColor = 1<<3, /// 1263 DstColor = 1<<4, /// 1264 OneMinusDstColor = 1<<5, /// 1265 SrcAlpha = 1<<6, /// 1266 OneMinusSrcAlpha = 1<<7, /// 1267 DstAlpha = 1<<8, /// 1268 OneMinusDstAlpha = 1<<9, /// 1269 SrcAlphaSaturate = 1<<10, /// 1270 } 1271 1272 /// Composite operation (HTML5-alike). 1273 /// Group: composite_operation 1274 public enum NVGCompositeOperation { 1275 SourceOver, /// 1276 SourceIn, /// 1277 SourceOut, /// 1278 SourceAtop, /// 1279 DestinationOver, /// 1280 DestinationIn, /// 1281 DestinationOut, /// 1282 DestinationAtop, /// 1283 Lighter, /// 1284 Copy, /// 1285 Xor, /// 1286 } 1287 1288 /// Composite operation state. 1289 /// Group: composite_operation 1290 public struct NVGCompositeOperationState { 1291 bool simple; /// `true`: use `glBlendFunc()` instead of `glBlendFuncSeparate()` 1292 NVGBlendFactor srcRGB; /// 1293 NVGBlendFactor dstRGB; /// 1294 NVGBlendFactor srcAlpha; /// 1295 NVGBlendFactor dstAlpha; /// 1296 } 1297 1298 /// Mask combining more 1299 /// Group: clipping 1300 public enum NVGClipMode { 1301 None, /// normal rendering (i.e. render path instead of modifying clip region) 1302 Union, /// old mask will be masked with the current one; this is the default mode for [clip] 1303 Or, /// new mask will be added to the current one (logical `OR` operation); 1304 Xor, /// new mask will be logically `XOR`ed with the current one 1305 Sub, /// "subtract" current path from mask 1306 Replace, /// replace current mask 1307 Add = Or, /// Synonym 1308 } 1309 1310 /// Glyph position info. 1311 /// Group: text_api 1312 public struct NVGGlyphPosition { 1313 usize strpos; /// Position of the glyph in the input string. 1314 float x; /// The x-coordinate of the logical glyph position. 1315 float minx, maxx; /// The bounds of the glyph shape. 1316 } 1317 1318 /// Text row storage. 1319 /// Group: text_api 1320 public struct NVGTextRow(CT) if (isAnyCharType!CT) { 1321 alias CharType = CT; 1322 const(CT)[] s; 1323 int start; /// Index in the input text where the row starts. 1324 int end; /// Index in the input text where the row ends (one past the last character). 1325 float width; /// Logical width of the row. 1326 float minx, maxx; /// Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending. 1327 /// Get rest of the string. 1328 @property const(CT)[] rest () const pure nothrow @trusted @nogc { pragma(inline, true); return (end <= s.length ? s[end..$] : null); } 1329 /// Get current row. 1330 @property const(CT)[] row () const pure nothrow @trusted @nogc { pragma(inline, true); return s[start..end]; } 1331 @property const(CT)[] string () const pure nothrow @trusted @nogc { pragma(inline, true); return s; } 1332 @property void string(CT) (const(CT)[] v) pure nothrow @trusted @nogc { pragma(inline, true); s = v; } 1333 } 1334 1335 /// Image creation flags. 1336 /// Group: images 1337 public enum NVGImageFlag : uint { 1338 None = 0, /// Nothing special. 1339 GenerateMipmaps = 1<<0, /// Generate mipmaps during creation of the image. 1340 RepeatX = 1<<1, /// Repeat image in X direction. 1341 RepeatY = 1<<2, /// Repeat image in Y direction. 1342 FlipY = 1<<3, /// Flips (inverses) image in Y direction when rendered. 1343 Premultiplied = 1<<4, /// Image data has premultiplied alpha. 1344 ClampToBorderX = 1<<5, /// Clamp image to border (instead of clamping to edge by default) 1345 ClampToBorderY = 1<<6, /// Clamp image to border (instead of clamping to edge by default) 1346 NoFiltering = 1<<8, /// use GL_NEAREST instead of GL_LINEAR. Only affects upscaling if GenerateMipmaps is active. 1347 Nearest = NoFiltering, /// compatibility with original NanoVG 1348 NoDelete = 1<<16,/// Do not delete GL texture handle. 1349 } 1350 1351 alias NVGImageFlags = NVGImageFlag; /// Backwards compatibility for [NVGImageFlag]. 1352 1353 1354 // ////////////////////////////////////////////////////////////////////////// // 1355 private: 1356 1357 static T* xdup(T) (const(T)* ptr, int count) nothrow @trusted @nogc { 1358 import core.stdc.stdlib : malloc; 1359 import core.stdc.string : memcpy; 1360 if (count == 0) return null; 1361 T* res = cast(T*)malloc(T.sizeof*count); 1362 if (res is null) assert(0, "NanoVega: out of memory"); 1363 memcpy(res, ptr, T.sizeof*count); 1364 return res; 1365 } 1366 1367 // Internal Render API 1368 enum NVGtexture { 1369 Alpha = 0x01, 1370 RGBA = 0x02, 1371 } 1372 1373 struct NVGscissor { 1374 NVGMatrix xform; 1375 float[2] extent = -1.0f; 1376 } 1377 1378 /// General NanoVega vertex struct. Contains geometry coordinates, and (sometimes unused) texture coordinates. 1379 public struct NVGVertex { 1380 float x, y, u, v; 1381 } 1382 1383 struct NVGpath { 1384 int first; 1385 int count; 1386 bool closed; 1387 int nbevel; 1388 NVGVertex* fill; 1389 int nfill; 1390 NVGVertex* stroke; 1391 int nstroke; 1392 NVGWinding mWinding; 1393 bool convex; 1394 bool cloned; 1395 1396 @disable this (this); // no copies 1397 void opAssign() (const scope auto ref NVGpath a) { static assert(0, "no copies!"); } 1398 1399 void clear () nothrow @trusted @nogc { 1400 import core.stdc.stdlib : free; 1401 import core.stdc.string : memset; 1402 if (cloned) { 1403 if (stroke !is null && stroke !is fill) free(stroke); 1404 if (fill !is null) free(fill); 1405 } 1406 memset(&this, 0, this.sizeof); 1407 } 1408 1409 // won't clear current path 1410 void copyFrom (const NVGpath* src) nothrow @trusted @nogc { 1411 import core.stdc.string : memcpy; 1412 assert(src !is null); 1413 memcpy(&this, src, NVGpath.sizeof); 1414 this.fill = xdup(src.fill, src.nfill); 1415 if (src.stroke is src.fill) { 1416 this.stroke = this.fill; 1417 } else { 1418 this.stroke = xdup(src.stroke, src.nstroke); 1419 } 1420 this.cloned = true; 1421 } 1422 1423 public @property const(NVGVertex)[] fillVertices () const pure nothrow @trusted @nogc { 1424 pragma(inline, true); 1425 return (nfill > 0 ? fill[0..nfill] : null); 1426 } 1427 1428 public @property const(NVGVertex)[] strokeVertices () const pure nothrow @trusted @nogc { 1429 pragma(inline, true); 1430 return (nstroke > 0 ? stroke[0..nstroke] : null); 1431 } 1432 1433 public @property NVGWinding winding () const pure nothrow @trusted @nogc { pragma(inline, true); return mWinding; } 1434 public @property bool complex () const pure nothrow @trusted @nogc { pragma(inline, true); return !convex; } 1435 } 1436 1437 1438 struct NVGparams { 1439 void* userPtr; 1440 bool edgeAntiAlias; 1441 bool fontAA; 1442 bool function (void* uptr) nothrow @trusted @nogc renderCreate; 1443 int function (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc renderCreateTexture; 1444 bool function (void* uptr, int image) nothrow @trusted @nogc renderTextureIncRef; 1445 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 1446 bool function (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc renderUpdateTexture; 1447 bool function (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc renderGetTextureSize; 1448 void function (void* uptr, int width, int height) nothrow @trusted @nogc renderViewport; // called in [beginFrame] 1449 void function (void* uptr) nothrow @trusted @nogc renderCancel; 1450 void function (void* uptr) nothrow @trusted @nogc renderFlush; 1451 void function (void* uptr) nothrow @trusted @nogc renderPushClip; // backend should support stack of at least [NVG_MAX_STATES] elements 1452 void function (void* uptr) nothrow @trusted @nogc renderPopClip; // backend should support stack of at least [NVG_MAX_STATES] elements 1453 void function (void* uptr) nothrow @trusted @nogc renderResetClip; // reset current clip region to `non-clipped` 1454 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; 1455 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; 1456 void function (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc renderTriangles; 1457 void function (void* uptr, const scope ref NVGMatrix mat) nothrow @trusted @nogc renderSetAffine; 1458 void function (void* uptr) nothrow @trusted @nogc renderDelete; 1459 } 1460 1461 // ////////////////////////////////////////////////////////////////////////// // 1462 private: 1463 1464 enum NVG_INIT_FONTIMAGE_SIZE = 512; 1465 enum NVG_MAX_FONTIMAGE_SIZE = 2048; 1466 enum NVG_MAX_FONTIMAGES = 4; 1467 1468 enum NVG_INIT_COMMANDS_SIZE = 256; 1469 enum NVG_INIT_POINTS_SIZE = 128; 1470 enum NVG_INIT_PATHS_SIZE = 16; 1471 enum NVG_INIT_VERTS_SIZE = 256; 1472 enum NVG_MAX_STATES = 32; 1473 1474 public enum NVG_KAPPA90 = 0.5522847493f; /// Length proportional to radius of a cubic bezier handle for 90deg arcs. 1475 enum NVG_MIN_FEATHER = 0.001f; // it should be greater than zero, 'cause it is used in shader for divisions 1476 1477 enum Command { 1478 MoveTo = 0, 1479 LineTo = 1, 1480 BezierTo = 2, 1481 Close = 3, 1482 Winding = 4, 1483 } 1484 1485 enum PointFlag : int { 1486 Corner = 0x01, 1487 Left = 0x02, 1488 Bevel = 0x04, 1489 InnerBevelPR = 0x08, 1490 } 1491 1492 struct NVGstate { 1493 NVGCompositeOperationState compositeOperation; 1494 bool shapeAntiAlias = true; 1495 NVGPaint fill; 1496 NVGPaint stroke; 1497 float strokeWidth = 1.0f; 1498 float miterLimit = 10.0f; 1499 NVGLineCap lineJoin = NVGLineCap.Miter; 1500 NVGLineCap lineCap = NVGLineCap.Butt; 1501 float alpha = 1.0f; 1502 NVGMatrix xform; 1503 NVGscissor scissor; 1504 float fontSize = 16.0f; 1505 float letterSpacing = 0.0f; 1506 float lineHeight = 1.0f; 1507 float fontBlur = 0.0f; 1508 NVGTextAlign textAlign; 1509 int fontId = 0; 1510 bool evenOddMode = false; // use even-odd filling rule (required for some svgs); otherwise use non-zero fill 1511 // dashing 1512 enum MaxDashes = 32; // max 16 dashes 1513 float[MaxDashes] dashes; 1514 uint dashCount = 0; 1515 uint lastFlattenDashCount = 0; 1516 float dashStart = 0; 1517 float totalDashLen; 1518 bool firstDashIsGap = false; 1519 // dasher state for flattener 1520 bool dasherActive = false; 1521 1522 void clearPaint () nothrow @trusted @nogc { 1523 fill.clear(); 1524 stroke.clear(); 1525 } 1526 } 1527 1528 struct NVGpoint { 1529 float x, y; 1530 float dx, dy; 1531 float len; 1532 float dmx, dmy; 1533 ubyte flags; 1534 } 1535 1536 struct NVGpathCache { 1537 NVGpoint* points; 1538 int npoints; 1539 int cpoints; 1540 NVGpath* paths; 1541 int npaths; 1542 int cpaths; 1543 NVGVertex* verts; 1544 int nverts; 1545 int cverts; 1546 float[4] bounds; 1547 // this is required for saved paths 1548 bool strokeReady; 1549 bool fillReady; 1550 float strokeAlphaMul; 1551 float strokeWidth; 1552 float fringeWidth; 1553 bool evenOddMode; 1554 NVGClipMode clipmode; 1555 // non-saved path will not have this 1556 float* commands; 1557 int ncommands; 1558 1559 @disable this (this); // no copies 1560 void opAssign() (const scope auto ref NVGpathCache a) { static assert(0, "no copies!"); } 1561 1562 // won't clear current path 1563 void copyFrom (const NVGpathCache* src) nothrow @trusted @nogc { 1564 import core.stdc.stdlib : malloc; 1565 import core.stdc.string : memcpy, memset; 1566 assert(src !is null); 1567 memcpy(&this, src, NVGpathCache.sizeof); 1568 this.points = xdup(src.points, src.npoints); 1569 this.cpoints = src.npoints; 1570 this.verts = xdup(src.verts, src.nverts); 1571 this.cverts = src.nverts; 1572 this.commands = xdup(src.commands, src.ncommands); 1573 if (src.npaths > 0) { 1574 this.paths = cast(NVGpath*)malloc(src.npaths*NVGpath.sizeof); 1575 memset(this.paths, 0, npaths*NVGpath.sizeof); 1576 foreach (immutable pidx; 0..npaths) this.paths[pidx].copyFrom(&src.paths[pidx]); 1577 this.cpaths = src.npaths; 1578 } else { 1579 this.npaths = this.cpaths = 0; 1580 } 1581 } 1582 1583 void clear () nothrow @trusted @nogc { 1584 import core.stdc.stdlib : free; 1585 import core.stdc.string : memset; 1586 if (paths !is null) { 1587 foreach (ref p; paths[0..npaths]) p.clear(); 1588 free(paths); 1589 } 1590 if (points !is null) free(points); 1591 if (verts !is null) free(verts); 1592 if (commands !is null) free(commands); 1593 memset(&this, 0, this.sizeof); 1594 } 1595 } 1596 1597 /// Pointer to opaque NanoVega context structure. 1598 /// Group: context_management 1599 public alias NVGContext = NVGcontextinternal*; 1600 1601 /// FontStash context 1602 /// Group: font_stash 1603 public alias FONSContext = FONScontextInternal*; 1604 1605 /// Returns FontStash context of the given NanoVega context. 1606 /// Group: font_stash 1607 public FONSContext fonsContext (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.fs : null); } 1608 1609 /// Returns scale that should be applied to FontStash parameters due to matrix transformations on the context (or 1) 1610 /// Group: font_stash 1611 public float fonsScale (NVGContext ctx) /*pure*/ nothrow @trusted @nogc { 1612 pragma(inline, true); 1613 return (ctx !is null && ctx.contextAlive && ctx.nstates > 0 ? nvg__getFontScale(&ctx.states.ptr[ctx.nstates-1])*ctx.devicePxRatio : 1); 1614 } 1615 1616 /// Setup FontStash from the given NanoVega context font parameters. Note that this will apply transformation scale too. 1617 /// Returns `false` if FontStash or NanoVega context is not active. 1618 /// Group: font_stash 1619 public bool setupFonsFrom (FONSContext stash, NVGContext ctx) nothrow @trusted @nogc { 1620 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false; 1621 NVGstate* state = nvg__getState(ctx); 1622 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 1623 stash.size = state.fontSize*scale; 1624 stash.spacing = state.letterSpacing*scale; 1625 stash.blur = state.fontBlur*scale; 1626 stash.textAlign = state.textAlign; 1627 stash.fontId = state.fontId; 1628 return true; 1629 } 1630 1631 /// Setup NanoVega context font parameters from the given FontStash. Note that NanoVega can apply transformation scale later. 1632 /// Returns `false` if FontStash or NanoVega context is not active. 1633 /// Group: font_stash 1634 public bool setupCtxFrom (NVGContext ctx, FONSContext stash) nothrow @trusted @nogc { 1635 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false; 1636 NVGstate* state = nvg__getState(ctx); 1637 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 1638 state.fontSize = stash.size; 1639 state.letterSpacing = stash.spacing; 1640 state.fontBlur = stash.blur; 1641 state.textAlign = stash.textAlign; 1642 state.fontId = stash.fontId; 1643 return true; 1644 } 1645 1646 /** Bezier curve rasterizer. 1647 * 1648 * De Casteljau Bezier rasterizer is faster, but currently rasterizing curves with cusps sligtly wrong. 1649 * It doesn't really matter in practice. 1650 * 1651 * AFD tesselator is somewhat slower, but does cusps better. 1652 * 1653 * McSeem rasterizer should have the best quality, bit it is the slowest method. Basically, you will 1654 * never notice any visial difference (and this code is not really debugged), so you probably should 1655 * not use it. It is there for further experiments. 1656 */ 1657 public enum NVGTesselation { 1658 DeCasteljau, /// default: standard well-known tesselation algorithm 1659 AFD, /// adaptive forward differencing 1660 DeCasteljauMcSeem, /// standard well-known tesselation algorithm, with improvements from Maxim Shemanarev; slowest one, but should give best results 1661 } 1662 1663 /// Default tesselator for Bezier curves. 1664 public __gshared NVGTesselation NVG_DEFAULT_TESSELATOR = NVGTesselation.DeCasteljau; 1665 1666 1667 // some public info 1668 1669 /// valid only inside [beginFrame]/[endFrame] 1670 /// Group: context_management 1671 public int width (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mWidth : 0); } 1672 1673 /// valid only inside [beginFrame]/[endFrame] 1674 /// Group: context_management 1675 public int height (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mHeight : 0); } 1676 1677 /// valid only inside [beginFrame]/[endFrame] 1678 /// Group: context_management 1679 public float devicePixelRatio (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.devicePxRatio : float.nan); } 1680 1681 /// Returns `true` if [beginFrame] was called, and neither [endFrame], nor [cancelFrame] were. 1682 /// Group: context_management 1683 public bool inFrame (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.nstates > 0 : false); } 1684 1685 // path autoregistration 1686 1687 /// [pickid] to stop autoregistration. 1688 /// Group: context_management 1689 public enum NVGNoPick = -1; 1690 1691 /// >=0: this pickid will be assigned to all filled/stroked paths 1692 /// Group: context_management 1693 public int pickid (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickId : NVGNoPick); } 1694 1695 /// >=0: this pickid will be assigned to all filled/stroked paths 1696 /// Group: context_management 1697 public void pickid (NVGContext ctx, int v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.pathPickId = v; } 1698 1699 /// pick autoregistration mode; see [NVGPickKind] 1700 /// Group: context_management 1701 public uint pickmode (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickRegistered&NVGPickKind.All : 0); } 1702 1703 /// pick autoregistration mode; see [NVGPickKind] 1704 /// Group: context_management 1705 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); } 1706 1707 // tesselator options 1708 1709 /// Get current Bezier tesselation mode. See [NVGTesselation]. 1710 /// Group: context_management 1711 public NVGTesselation tesselation (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.tesselatortype : NVGTesselation.DeCasteljau); } 1712 1713 /// Set current Bezier tesselation mode. See [NVGTesselation]. 1714 /// Group: context_management 1715 public void tesselation (NVGContext ctx, NVGTesselation v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.tesselatortype = v; } 1716 1717 1718 private struct NVGcontextinternal { 1719 private: 1720 NVGparams params; 1721 float* commands; 1722 int ccommands; 1723 int ncommands; 1724 float commandx, commandy; 1725 NVGstate[NVG_MAX_STATES] states; 1726 int nstates; 1727 NVGpathCache* cache; 1728 public float tessTol; 1729 public float angleTol; // 0.0f -- angle tolerance for McSeem Bezier rasterizer 1730 public float cuspLimit; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps) 1731 float distTol; 1732 public float fringeWidth; 1733 float devicePxRatio; 1734 FONSContext fs; 1735 NVGImage[NVG_MAX_FONTIMAGES] fontImages; 1736 int fontImageIdx; 1737 int drawCallCount; 1738 int fillTriCount; 1739 int strokeTriCount; 1740 int textTriCount; 1741 NVGTesselation tesselatortype; 1742 // picking API 1743 NVGpickScene* pickScene; 1744 int pathPickId; // >=0: register all paths for picking using this id 1745 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 1746 // path recording 1747 NVGPathSet recset; 1748 int recstart; // used to cancel recording 1749 bool recblockdraw; 1750 // internals 1751 NVGMatrix gpuAffine; 1752 int mWidth, mHeight; 1753 // image manager 1754 shared int imageCount; // number of alive images in this context 1755 bool contextAlive; // context can be dead, but still contain some images 1756 1757 @disable this (this); // no copies 1758 void opAssign() (const scope auto ref NVGcontextinternal a) { static assert(0, "no copies!"); } 1759 1760 // debug feature 1761 public @property int getImageCount () nothrow @trusted @nogc { 1762 import core.atomic; 1763 return atomicLoad(imageCount); 1764 } 1765 } 1766 1767 /** Returns number of tesselated pathes in context. 1768 * 1769 * Tesselated pathes are either triangle strips (for strokes), or 1770 * triangle fans (for fills). Note that NanoVega can generate some 1771 * surprising pathes (like fringe stroke for antialiasing, for example). 1772 * 1773 * One render path can contain vertices both for fill, and for stroke triangles. 1774 */ 1775 public int renderPathCount (NVGContext ctx) pure nothrow @trusted @nogc { 1776 pragma(inline, true); 1777 return (ctx !is null && ctx.contextAlive ? ctx.cache.npaths : 0); 1778 } 1779 1780 /** Get vertices of "fill" triangle fan for the given render path. Can return empty slice. 1781 * 1782 * $(WARNING Returned slice can be invalidated by any other NanoVega API call 1783 * (except the calls to render path accessors), and using it in such 1784 * case is UB. So copy vertices to freshly allocated array if you want 1785 * to keep them for further processing.) 1786 */ 1787 public const(NVGVertex)[] renderPathFillVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1788 pragma(inline, true); 1789 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].fillVertices : null); 1790 } 1791 1792 /** Get vertices of "stroke" triangle strip for the given render path. Can return empty slice. 1793 * 1794 * $(WARNING Returned slice can be invalidated by any other NanoVega API call 1795 * (except the calls to render path accessors), and using it in such 1796 * case is UB. So copy vertices to freshly allocated array if you want 1797 * to keep them for further processing.) 1798 */ 1799 public const(NVGVertex)[] renderPathStrokeVertices (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].strokeVertices : null); 1802 1803 } 1804 1805 /// Returns winding for the given render path. 1806 public NVGWinding renderPathWinding (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].winding : NVGWinding.CCW); 1809 1810 } 1811 1812 /// Returns "complex path" flag for the given render path. 1813 public bool renderPathComplex (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1814 pragma(inline, true); 1815 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].complex : false); 1816 1817 } 1818 1819 void nvg__imageIncRef (NVGContext ctx, int imgid, bool increfInGL=true) nothrow @trusted @nogc { 1820 if (ctx !is null && imgid > 0) { 1821 import core.atomic : atomicOp; 1822 atomicOp!"+="(ctx.imageCount, 1); 1823 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[++]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); } 1824 if (ctx.contextAlive && increfInGL) ctx.params.renderTextureIncRef(ctx.params.userPtr, imgid); 1825 } 1826 } 1827 1828 void nvg__imageDecRef (NVGContext ctx, int imgid) nothrow @trusted @nogc { 1829 if (ctx !is null && imgid > 0) { 1830 import core.atomic : atomicOp; 1831 int icnt = atomicOp!"-="(ctx.imageCount, 1); 1832 if (icnt < 0) assert(0, "NanoVega: internal image refcounting error"); 1833 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[--]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); } 1834 if (ctx.contextAlive) ctx.params.renderDeleteTexture(ctx.params.userPtr, imgid); 1835 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); } 1836 if (!ctx.contextAlive && icnt == 0) { 1837 // it is finally safe to free context memory 1838 import core.stdc.stdlib : free; 1839 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("killed zombie context %p\n", ctx); } 1840 free(ctx); 1841 } 1842 } 1843 } 1844 1845 1846 public import core.stdc.math : 1847 nvg__sqrtf = sqrtf, 1848 nvg__modf = fmodf, 1849 nvg__sinf = sinf, 1850 nvg__cosf = cosf, 1851 nvg__tanf = tanf, 1852 nvg__atan2f = atan2f, 1853 nvg__acosf = acosf, 1854 nvg__ceilf = ceilf; 1855 1856 version(Windows) { 1857 public int nvg__lrintf (float f) nothrow @trusted @nogc { pragma(inline, true); return cast(int)(f+0.5); } 1858 } else { 1859 public import core.stdc.math : nvg__lrintf = lrintf; 1860 } 1861 1862 public auto nvg__min(T) (T a, T b) { pragma(inline, true); return (a < b ? a : b); } 1863 public auto nvg__max(T) (T a, T b) { pragma(inline, true); return (a > b ? a : b); } 1864 public auto nvg__clamp(T) (T a, T mn, T mx) { pragma(inline, true); return (a < mn ? mn : (a > mx ? mx : a)); } 1865 //float nvg__absf() (float a) { pragma(inline, true); return (a >= 0.0f ? a : -a); } 1866 public auto nvg__sign(T) (T a) { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); } 1867 public float nvg__cross() (float dx0, float dy0, float dx1, float dy1) { pragma(inline, true); return (dx1*dy0-dx0*dy1); } 1868 1869 //public import core.stdc.math : nvg__absf = fabsf; 1870 public import core.math : nvg__absf = fabs; 1871 1872 1873 float nvg__normalize (float* x, float* y) nothrow @safe @nogc { 1874 float d = nvg__sqrtf((*x)*(*x)+(*y)*(*y)); 1875 if (d > 1e-6f) { 1876 immutable float id = 1.0f/d; 1877 *x *= id; 1878 *y *= id; 1879 } 1880 return d; 1881 } 1882 1883 void nvg__deletePathCache (ref NVGpathCache* c) nothrow @trusted @nogc { 1884 if (c !is null) { 1885 c.clear(); 1886 free(c); 1887 } 1888 } 1889 1890 NVGpathCache* nvg__allocPathCache () nothrow @trusted @nogc { 1891 NVGpathCache* c = cast(NVGpathCache*)malloc(NVGpathCache.sizeof); 1892 if (c is null) goto error; 1893 memset(c, 0, NVGpathCache.sizeof); 1894 1895 c.points = cast(NVGpoint*)malloc(NVGpoint.sizeof*NVG_INIT_POINTS_SIZE); 1896 if (c.points is null) goto error; 1897 assert(c.npoints == 0); 1898 c.cpoints = NVG_INIT_POINTS_SIZE; 1899 1900 c.paths = cast(NVGpath*)malloc(NVGpath.sizeof*NVG_INIT_PATHS_SIZE); 1901 if (c.paths is null) goto error; 1902 assert(c.npaths == 0); 1903 c.cpaths = NVG_INIT_PATHS_SIZE; 1904 1905 c.verts = cast(NVGVertex*)malloc(NVGVertex.sizeof*NVG_INIT_VERTS_SIZE); 1906 if (c.verts is null) goto error; 1907 assert(c.nverts == 0); 1908 c.cverts = NVG_INIT_VERTS_SIZE; 1909 1910 return c; 1911 1912 error: 1913 nvg__deletePathCache(c); 1914 return null; 1915 } 1916 1917 void nvg__setDevicePixelRatio (NVGContext ctx, float ratio) pure nothrow @safe @nogc { 1918 ctx.tessTol = 0.25f/ratio; 1919 ctx.distTol = 0.01f/ratio; 1920 ctx.fringeWidth = 1.0f/ratio; 1921 ctx.devicePxRatio = ratio; 1922 } 1923 1924 NVGCompositeOperationState nvg__compositeOperationState (NVGCompositeOperation op) pure nothrow @safe @nogc { 1925 NVGCompositeOperationState state; 1926 NVGBlendFactor sfactor, dfactor; 1927 1928 if (op == NVGCompositeOperation.SourceOver) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha;} 1929 else if (op == NVGCompositeOperation.SourceIn) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.Zero; } 1930 else if (op == NVGCompositeOperation.SourceOut) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.Zero; } 1931 else if (op == NVGCompositeOperation.SourceAtop) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } 1932 else if (op == NVGCompositeOperation.DestinationOver) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.One; } 1933 else if (op == NVGCompositeOperation.DestinationIn) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.SrcAlpha; } 1934 else if (op == NVGCompositeOperation.DestinationOut) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } 1935 else if (op == NVGCompositeOperation.DestinationAtop) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.SrcAlpha; } 1936 else if (op == NVGCompositeOperation.Lighter) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.One; } 1937 else if (op == NVGCompositeOperation.Copy) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.Zero; } 1938 else if (op == NVGCompositeOperation.Xor) { 1939 state.simple = false; 1940 state.srcRGB = NVGBlendFactor.OneMinusDstColor; 1941 state.srcAlpha = NVGBlendFactor.OneMinusDstAlpha; 1942 state.dstRGB = NVGBlendFactor.OneMinusSrcColor; 1943 state.dstAlpha = NVGBlendFactor.OneMinusSrcAlpha; 1944 return state; 1945 } 1946 else { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } // default value for invalid op: SourceOver 1947 1948 state.simple = true; 1949 state.srcAlpha = sfactor; 1950 state.dstAlpha = dfactor; 1951 return state; 1952 } 1953 1954 NVGstate* nvg__getState (NVGContext ctx) pure nothrow @trusted @nogc { 1955 pragma(inline, true); 1956 if (ctx is null || !ctx.contextAlive || ctx.nstates == 0) assert(0, "NanoVega: cannot perform commands on inactive context"); 1957 return &ctx.states.ptr[ctx.nstates-1]; 1958 } 1959 1960 // Constructor called by the render back-end. 1961 NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc { 1962 FONSParams fontParams; 1963 NVGContext ctx = cast(NVGContext)malloc(NVGcontextinternal.sizeof); 1964 if (ctx is null) goto error; 1965 memset(ctx, 0, NVGcontextinternal.sizeof); 1966 1967 ctx.angleTol = 0; // angle tolerance for McSeem Bezier rasterizer 1968 ctx.cuspLimit = 0; // cusp limit for McSeem Bezier rasterizer (0: real cusps) 1969 1970 ctx.contextAlive = true; 1971 1972 ctx.params = *params; 1973 //ctx.fontImages[0..NVG_MAX_FONTIMAGES] = 0; 1974 1975 ctx.commands = cast(float*)malloc(float.sizeof*NVG_INIT_COMMANDS_SIZE); 1976 if (ctx.commands is null) goto error; 1977 ctx.ncommands = 0; 1978 ctx.ccommands = NVG_INIT_COMMANDS_SIZE; 1979 1980 ctx.cache = nvg__allocPathCache(); 1981 if (ctx.cache is null) goto error; 1982 1983 ctx.save(); 1984 ctx.reset(); 1985 1986 nvg__setDevicePixelRatio(ctx, 1.0f); 1987 ctx.mWidth = ctx.mHeight = 0; 1988 1989 if (!ctx.params.renderCreate(ctx.params.userPtr)) goto error; 1990 1991 // init font rendering 1992 memset(&fontParams, 0, fontParams.sizeof); 1993 fontParams.width = NVG_INIT_FONTIMAGE_SIZE; 1994 fontParams.height = NVG_INIT_FONTIMAGE_SIZE; 1995 fontParams.flags = FONSParams.Flag.ZeroTopLeft; 1996 fontParams.renderCreate = null; 1997 fontParams.renderUpdate = null; 1998 fontParams.renderDelete = null; 1999 fontParams.userPtr = null; 2000 ctx.fs = FONSContext.create(fontParams); 2001 if (ctx.fs is null) goto error; 2002 2003 // create font texture 2004 ctx.fontImages[0].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, fontParams.width, fontParams.height, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); 2005 if (ctx.fontImages[0].id == 0) goto error; 2006 ctx.fontImages[0].ctx = ctx; 2007 ctx.nvg__imageIncRef(ctx.fontImages[0].id, false); // don't increment driver refcount 2008 ctx.fontImageIdx = 0; 2009 2010 ctx.pathPickId = -1; 2011 ctx.tesselatortype = NVG_DEFAULT_TESSELATOR; 2012 2013 return ctx; 2014 2015 error: 2016 ctx.deleteInternal(); 2017 return null; 2018 } 2019 2020 // Called by render backend. 2021 NVGparams* internalParams (NVGContext ctx) nothrow @trusted @nogc { 2022 return &ctx.params; 2023 } 2024 2025 // Destructor called by the render back-end. 2026 void deleteInternal (ref NVGContext ctx) nothrow @trusted @nogc { 2027 if (ctx is null) return; 2028 if (ctx.contextAlive) { 2029 if (ctx.commands !is null) free(ctx.commands); 2030 nvg__deletePathCache(ctx.cache); 2031 2032 if (ctx.fs) ctx.fs.kill(); 2033 2034 foreach (uint i; 0..NVG_MAX_FONTIMAGES) ctx.fontImages[i].clear(); 2035 2036 if (ctx.params.renderDelete !is null) ctx.params.renderDelete(ctx.params.userPtr); 2037 2038 if (ctx.pickScene !is null) nvg__deletePickScene(ctx.pickScene); 2039 2040 ctx.contextAlive = false; 2041 2042 import core.atomic : atomicLoad; 2043 if (atomicLoad(ctx.imageCount) == 0) { 2044 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("destroyed context %p\n", ctx); } 2045 free(ctx); 2046 } else { 2047 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("context %p is zombie now (%d image refs)\n", ctx, ctx.imageCount); } 2048 } 2049 } 2050 } 2051 2052 /// Delete NanoVega context. 2053 /// Group: context_management 2054 public void kill (ref NVGContext ctx) nothrow @trusted @nogc { 2055 if (ctx !is null) { 2056 ctx.deleteInternal(); 2057 ctx = null; 2058 } 2059 } 2060 2061 /// Returns `true` if the given context is not `null` and can be used for painting. 2062 /// Group: context_management 2063 public bool valid (in NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive); } 2064 2065 2066 // ////////////////////////////////////////////////////////////////////////// // 2067 // Frame Management 2068 2069 /** Begin drawing a new frame. 2070 * 2071 * Calls to NanoVega drawing API should be wrapped in [beginFrame] and [endFrame] 2072 * 2073 * [beginFrame] defines the size of the window to render to in relation currently 2074 * set viewport (i.e. glViewport on GL backends). Device pixel ration allows to 2075 * control the rendering on Hi-DPI devices. 2076 * 2077 * For example, GLFW returns two dimension for an opened window: window size and 2078 * frame buffer size. In that case you would set windowWidth/windowHeight to the window size, 2079 * devicePixelRatio to: `windowWidth/windowHeight`. 2080 * 2081 * Default ratio is `1`. 2082 * 2083 * Note that fractional ratio can (and will) distort your fonts and images. 2084 * 2085 * This call also resets pick marks (see picking API for non-rasterized paths), 2086 * path recording, and GPU affine transformatin matrix. 2087 * 2088 * see also [glNVGClearFlags], which returns necessary flags for [glClear]. 2089 * 2090 * Group: frame_management 2091 */ 2092 public void beginFrame (NVGContext ctx, int windowWidth, int windowHeight, float devicePixelRatio=1.0f) nothrow @trusted @nogc { 2093 import std.math : isNaN; 2094 /* 2095 printf("Tris: draws:%d fill:%d stroke:%d text:%d TOT:%d\n", 2096 ctx.drawCallCount, ctx.fillTriCount, ctx.strokeTriCount, ctx.textTriCount, 2097 ctx.fillTriCount+ctx.strokeTriCount+ctx.textTriCount); 2098 */ 2099 if (ctx.nstates > 0) ctx.cancelFrame(); 2100 2101 if (windowWidth < 1) windowWidth = 1; 2102 if (windowHeight < 1) windowHeight = 1; 2103 2104 if (isNaN(devicePixelRatio)) devicePixelRatio = (windowHeight > 0 ? cast(float)windowWidth/cast(float)windowHeight : 1024.0/768.0); 2105 2106 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2107 ctx.nstates = 0; 2108 ctx.save(); 2109 ctx.reset(); 2110 2111 nvg__setDevicePixelRatio(ctx, devicePixelRatio); 2112 2113 ctx.params.renderViewport(ctx.params.userPtr, windowWidth, windowHeight); 2114 ctx.mWidth = windowWidth; 2115 ctx.mHeight = windowHeight; 2116 2117 ctx.recset = null; 2118 ctx.recstart = -1; 2119 2120 ctx.pathPickId = NVGNoPick; 2121 ctx.pathPickRegistered = 0; 2122 2123 ctx.drawCallCount = 0; 2124 ctx.fillTriCount = 0; 2125 ctx.strokeTriCount = 0; 2126 ctx.textTriCount = 0; 2127 2128 ctx.ncommands = 0; 2129 ctx.pathPickRegistered = 0; 2130 nvg__clearPathCache(ctx); 2131 2132 ctx.gpuAffine = NVGMatrix.Identity; 2133 2134 nvg__pickBeginFrame(ctx, windowWidth, windowHeight); 2135 } 2136 2137 /// Cancels drawing the current frame. Cancels path recording. 2138 /// Group: frame_management 2139 public void cancelFrame (NVGContext ctx) nothrow @trusted @nogc { 2140 ctx.cancelRecording(); 2141 //ctx.mWidth = 0; 2142 //ctx.mHeight = 0; 2143 // cancel render queue 2144 ctx.params.renderCancel(ctx.params.userPtr); 2145 // clear saved states (this may free some textures) 2146 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2147 ctx.nstates = 0; 2148 } 2149 2150 /// Ends drawing the current frame (flushing remaining render state). Commits recorded paths. 2151 /// Group: frame_management 2152 public void endFrame (NVGContext ctx) nothrow @trusted @nogc { 2153 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx); 2154 ctx.stopRecording(); 2155 //ctx.mWidth = 0; 2156 //ctx.mHeight = 0; 2157 // flush render queue 2158 NVGstate* state = nvg__getState(ctx); 2159 ctx.params.renderFlush(ctx.params.userPtr); 2160 if (ctx.fontImageIdx != 0) { 2161 auto fontImage = ctx.fontImages[ctx.fontImageIdx]; 2162 int j = 0, iw, ih; 2163 // delete images that smaller than current one 2164 if (!fontImage.valid) return; 2165 ctx.imageSize(fontImage, iw, ih); 2166 foreach (int i; 0..ctx.fontImageIdx) { 2167 if (ctx.fontImages[i].valid) { 2168 int nw, nh; 2169 ctx.imageSize(ctx.fontImages[i], nw, nh); 2170 if (nw < iw || nh < ih) { 2171 ctx.deleteImage(ctx.fontImages[i]); 2172 } else { 2173 ctx.fontImages[j++] = ctx.fontImages[i]; 2174 } 2175 } 2176 } 2177 // make current font image to first 2178 ctx.fontImages[j++] = ctx.fontImages[0]; 2179 ctx.fontImages[0] = fontImage; 2180 ctx.fontImageIdx = 0; 2181 // clear all images after j 2182 ctx.fontImages[j..NVG_MAX_FONTIMAGES] = NVGImage.init; 2183 } 2184 // clear saved states (this may free some textures) 2185 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2186 ctx.nstates = 0; 2187 } 2188 2189 2190 // ////////////////////////////////////////////////////////////////////////// // 2191 // Recording and Replaying Pathes 2192 2193 // Saved path set. 2194 // Group: path_recording 2195 public alias NVGPathSet = NVGPathSetS*; 2196 2197 2198 //TODO: save scissor info? 2199 struct NVGPathSetS { 2200 private: 2201 // either path cache, or text item 2202 static struct Node { 2203 NVGPaint paint; 2204 NVGpathCache* path; 2205 } 2206 2207 private: 2208 Node* nodes; 2209 int nnodes, cnodes; 2210 NVGpickScene* pickscene; 2211 //int npickscenes, cpickscenes; 2212 NVGContext svctx; // used to do some sanity checks, and to free resources 2213 2214 private: 2215 Node* allocNode () nothrow @trusted @nogc { 2216 import core.stdc.string : memset; 2217 // grow buffer if necessary 2218 if (nnodes+1 > cnodes) { 2219 import core.stdc.stdlib : realloc; 2220 int newsz = (cnodes == 0 ? 8 : cnodes <= 1024 ? cnodes*2 : cnodes+1024); 2221 nodes = cast(Node*)realloc(nodes, newsz*Node.sizeof); 2222 if (nodes is null) assert(0, "NanoVega: out of memory"); 2223 //memset(svp.caches+svp.ccaches, 0, (newsz-svp.ccaches)*NVGpathCache.sizeof); 2224 cnodes = newsz; 2225 } 2226 assert(nnodes < cnodes); 2227 memset(nodes+nnodes, 0, Node.sizeof); 2228 return &nodes[nnodes++]; 2229 } 2230 2231 Node* allocPathNode () nothrow @trusted @nogc { 2232 import core.stdc.stdlib : malloc; 2233 import core.stdc.string : memset; 2234 auto node = allocNode(); 2235 // allocate path cache 2236 auto pc = cast(NVGpathCache*)malloc(NVGpathCache.sizeof); 2237 if (pc is null) assert(0, "NanoVega: out of memory"); 2238 node.path = pc; 2239 return node; 2240 } 2241 2242 void clearNode (int idx) nothrow @trusted @nogc { 2243 if (idx < 0 || idx >= nnodes) return; 2244 Node* node = &nodes[idx]; 2245 if (svctx !is null && node.paint.image.valid) node.paint.image.clear(); 2246 if (node.path !is null) node.path.clear(); 2247 } 2248 2249 private: 2250 void takeCurrentPickScene (NVGContext ctx) nothrow @trusted @nogc { 2251 NVGpickScene* ps = ctx.pickScene; 2252 if (ps is null) return; // nothing to do 2253 if (ps.npaths == 0) return; // pick scene is empty 2254 ctx.pickScene = null; 2255 pickscene = ps; 2256 } 2257 2258 void replay (NVGContext ctx, const scope ref NVGColor fillTint, const scope ref NVGColor strokeTint) nothrow @trusted @nogc { 2259 NVGstate* state = nvg__getState(ctx); 2260 foreach (ref node; nodes[0..nnodes]) { 2261 if (auto cc = node.path) { 2262 if (cc.npaths <= 0) continue; 2263 2264 if (cc.fillReady) { 2265 NVGPaint fillPaint = node.paint; 2266 2267 // apply global alpha 2268 fillPaint.innerColor.a *= state.alpha; 2269 fillPaint.middleColor.a *= state.alpha; 2270 fillPaint.outerColor.a *= state.alpha; 2271 2272 fillPaint.innerColor.applyTint(fillTint); 2273 fillPaint.middleColor.applyTint(fillTint); 2274 fillPaint.outerColor.applyTint(fillTint); 2275 2276 ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &fillPaint, &state.scissor, cc.fringeWidth, cc.bounds.ptr, cc.paths, cc.npaths, cc.evenOddMode); 2277 2278 // count triangles 2279 foreach (int i; 0..cc.npaths) { 2280 NVGpath* path = &cc.paths[i]; 2281 ctx.fillTriCount += path.nfill-2; 2282 ctx.fillTriCount += path.nstroke-2; 2283 ctx.drawCallCount += 2; 2284 } 2285 } 2286 2287 if (cc.strokeReady) { 2288 NVGPaint strokePaint = node.paint; 2289 2290 strokePaint.innerColor.a *= cc.strokeAlphaMul; 2291 strokePaint.middleColor.a *= cc.strokeAlphaMul; 2292 strokePaint.outerColor.a *= cc.strokeAlphaMul; 2293 2294 // apply global alpha 2295 strokePaint.innerColor.a *= state.alpha; 2296 strokePaint.middleColor.a *= state.alpha; 2297 strokePaint.outerColor.a *= state.alpha; 2298 2299 strokePaint.innerColor.applyTint(strokeTint); 2300 strokePaint.middleColor.applyTint(strokeTint); 2301 strokePaint.outerColor.applyTint(strokeTint); 2302 2303 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &strokePaint, &state.scissor, cc.fringeWidth, cc.strokeWidth, cc.paths, cc.npaths); 2304 2305 // count triangles 2306 foreach (int i; 0..cc.npaths) { 2307 NVGpath* path = &cc.paths[i]; 2308 ctx.strokeTriCount += path.nstroke-2; 2309 ++ctx.drawCallCount; 2310 } 2311 } 2312 } 2313 } 2314 } 2315 2316 public: 2317 @disable this (this); // no copies 2318 void opAssign() (const scope auto ref NVGPathSetS a) { static assert(0, "no copies!"); } 2319 2320 // pick test 2321 // Call delegate [dg] for each path under the specified position (in no particular order). 2322 // Returns the id of the path for which delegate [dg] returned true or -1. 2323 // dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending). 2324 int hitTestDG(bool bestOrder=false, DG) (in float x, in float y, NVGPickKind kind, scope DG dg) if (IsGoodHitTestDG!DG || IsGoodHitTestInternalDG!DG) { 2325 if (pickscene is null) return -1; 2326 2327 NVGpickScene* ps = pickscene; 2328 int levelwidth = 1<<(ps.nlevels-1); 2329 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth); 2330 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth); 2331 int npicked = 0; 2332 2333 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) { 2334 NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; 2335 while (pp !is null) { 2336 if (nvg__pickPathTestBounds(svctx, ps, pp, x, y)) { 2337 int hit = 0; 2338 if ((kind&NVGPickKind.Stroke) && (pp.flags&NVGPathFlags.Stroke)) hit = nvg__pickPathStroke(ps, pp, x, y); 2339 if (!hit && (kind&NVGPickKind.Fill) && (pp.flags&NVGPathFlags.Fill)) hit = nvg__pickPath(ps, pp, x, y); 2340 if (hit) { 2341 static if (IsGoodHitTestDG!DG) { 2342 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) { 2343 if (dg(pp.id, cast(int)pp.order)) return pp.id; 2344 } else { 2345 dg(pp.id, cast(int)pp.order); 2346 } 2347 } else { 2348 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) { 2349 if (dg(pp)) return pp.id; 2350 } else { 2351 dg(pp); 2352 } 2353 } 2354 } 2355 } 2356 pp = pp.next; 2357 } 2358 cellx >>= 1; 2359 celly >>= 1; 2360 levelwidth >>= 1; 2361 } 2362 2363 return -1; 2364 } 2365 2366 // Fills ids with a list of the top most hit ids under the specified position. 2367 // Returns the slice of [ids]. 2368 int[] hitTestAll (in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc { 2369 if (pickscene is null || ids.length == 0) return ids[0..0]; 2370 2371 int npicked = 0; 2372 NVGpickScene* ps = pickscene; 2373 2374 hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 2375 if (npicked == ps.cpicked) { 2376 int cpicked = ps.cpicked+ps.cpicked; 2377 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked); 2378 if (picked is null) return true; // abort 2379 ps.cpicked = cpicked; 2380 ps.picked = picked; 2381 } 2382 ps.picked[npicked] = pp; 2383 ++npicked; 2384 return false; // go on 2385 }); 2386 2387 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths); 2388 2389 assert(npicked >= 0); 2390 if (npicked > ids.length) npicked = cast(int)ids.length; 2391 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id; 2392 2393 return ids[0..npicked]; 2394 } 2395 2396 // Returns the id of the pickable shape containing x,y or -1 if no shape was found. 2397 int hitTest (in float x, in float y, NVGPickKind kind) nothrow @trusted @nogc { 2398 if (pickscene is null) return -1; 2399 2400 int bestOrder = -1; 2401 int bestID = -1; 2402 2403 hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 2404 if (pp.order > bestOrder) { 2405 bestOrder = pp.order; 2406 bestID = pp.id; 2407 } 2408 }); 2409 2410 return bestID; 2411 } 2412 } 2413 2414 // Append current path to existing path set. Is is safe to call this with `null` [svp]. 2415 void appendCurrentPathToCache (NVGContext ctx, NVGPathSet svp, const scope ref NVGPaint paint) nothrow @trusted @nogc { 2416 if (ctx is null || svp is null) return; 2417 if (ctx !is svp.svctx) assert(0, "NanoVega: cannot save paths from different contexts"); 2418 if (ctx.ncommands == 0) { 2419 assert(ctx.cache.npaths == 0); 2420 return; 2421 } 2422 if (!ctx.cache.fillReady && !ctx.cache.strokeReady) return; 2423 2424 // tesselate current path 2425 //if (!ctx.cache.fillReady) nvg__prepareFill(ctx); 2426 //if (!ctx.cache.strokeReady) nvg__prepareStroke(ctx); 2427 2428 auto node = svp.allocPathNode(); 2429 NVGpathCache* cc = node.path; 2430 cc.copyFrom(ctx.cache); 2431 node.paint = paint; 2432 // copy path commands (we may need 'em for picking) 2433 version(all) { 2434 cc.ncommands = ctx.ncommands; 2435 if (cc.ncommands) { 2436 import core.stdc.stdlib : malloc; 2437 import core.stdc.string : memcpy; 2438 cc.commands = cast(float*)malloc(ctx.ncommands*float.sizeof); 2439 if (cc.commands is null) assert(0, "NanoVega: out of memory"); 2440 memcpy(cc.commands, ctx.commands, ctx.ncommands*float.sizeof); 2441 } else { 2442 cc.commands = null; 2443 } 2444 } 2445 } 2446 2447 // Create new empty path set. 2448 // Group: path_recording 2449 public NVGPathSet newPathSet (NVGContext ctx) nothrow @trusted @nogc { 2450 import core.stdc.stdlib : malloc; 2451 import core.stdc.string : memset; 2452 if (ctx is null) return null; 2453 NVGPathSet res = cast(NVGPathSet)malloc(NVGPathSetS.sizeof); 2454 if (res is null) assert(0, "NanoVega: out of memory"); 2455 memset(res, 0, NVGPathSetS.sizeof); 2456 res.svctx = ctx; 2457 return res; 2458 } 2459 2460 // Is the given path set empty? Empty path set can be `null`. 2461 // Group: path_recording 2462 public bool empty (NVGPathSet svp) pure nothrow @safe @nogc { pragma(inline, true); return (svp is null || svp.nnodes == 0); } 2463 2464 // Clear path set contents. Will release $(B some) allocated memory (this function is meant to clear something that will be reused). 2465 // Group: path_recording 2466 public void clear (NVGPathSet svp) nothrow @trusted @nogc { 2467 if (svp !is null) { 2468 import core.stdc.stdlib : free; 2469 foreach (immutable idx; 0.. svp.nnodes) svp.clearNode(idx); 2470 svp.nnodes = 0; 2471 } 2472 } 2473 2474 // Destroy path set (frees all allocated memory). 2475 // Group: path_recording 2476 public void kill (ref NVGPathSet svp) nothrow @trusted @nogc { 2477 if (svp !is null) { 2478 import core.stdc.stdlib : free; 2479 svp.clear(); 2480 if (svp.nodes !is null) free(svp.nodes); 2481 free(svp); 2482 if (svp.pickscene !is null) nvg__deletePickScene(svp.pickscene); 2483 svp = null; 2484 } 2485 } 2486 2487 // Start path recording. [svp] should be alive until recording is cancelled or stopped. 2488 // Group: path_recording 2489 public void startRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { 2490 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2491 ctx.stopRecording(); 2492 ctx.recset = svp; 2493 ctx.recstart = (svp !is null ? svp.nnodes : -1); 2494 ctx.recblockdraw = false; 2495 } 2496 2497 /* Start path recording. [svp] should be alive until recording is cancelled or stopped. 2498 * 2499 * This will block all rendering, so you can call your rendering functions to record paths without actual drawing. 2500 * Commiting or cancelling will re-enable rendering. 2501 * You can call this with `null` svp to block rendering without recording any paths. 2502 * 2503 * Group: path_recording 2504 */ 2505 public void startBlockingRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { 2506 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2507 ctx.stopRecording(); 2508 ctx.recset = svp; 2509 ctx.recstart = (svp !is null ? svp.nnodes : -1); 2510 ctx.recblockdraw = true; 2511 } 2512 2513 // Commit recorded paths. It is safe to call this when recording is not started. 2514 // Group: path_recording 2515 public void stopRecording (NVGContext ctx) nothrow @trusted @nogc { 2516 if (ctx.recset !is null && ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2517 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx); 2518 ctx.recset = null; 2519 ctx.recstart = -1; 2520 ctx.recblockdraw = false; 2521 } 2522 2523 // Cancel path recording. 2524 // Group: path_recording 2525 public void cancelRecording (NVGContext ctx) nothrow @trusted @nogc { 2526 if (ctx.recset !is null) { 2527 if (ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2528 assert(ctx.recstart >= 0 && ctx.recstart <= ctx.recset.nnodes); 2529 foreach (immutable idx; ctx.recstart..ctx.recset.nnodes) ctx.recset.clearNode(idx); 2530 ctx.recset.nnodes = ctx.recstart; 2531 ctx.recset = null; 2532 ctx.recstart = -1; 2533 } 2534 ctx.recblockdraw = false; 2535 } 2536 2537 /* Replay saved path set. 2538 * 2539 * Replaying record while you're recording another one is undefined behavior. 2540 * 2541 * Group: path_recording 2542 */ 2543 public void replayRecording() (NVGContext ctx, NVGPathSet svp, const scope auto ref NVGColor fillTint, const scope auto ref NVGColor strokeTint) nothrow @trusted @nogc { 2544 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2545 svp.replay(ctx, fillTint, strokeTint); 2546 } 2547 2548 /// Ditto. 2549 public void replayRecording() (NVGContext ctx, NVGPathSet svp, const scope auto ref NVGColor fillTint) nothrow @trusted @nogc { ctx.replayRecording(svp, fillTint, NVGColor.transparent); } 2550 2551 /// Ditto. 2552 public void replayRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { ctx.replayRecording(svp, NVGColor.transparent, NVGColor.transparent); } 2553 2554 2555 // ////////////////////////////////////////////////////////////////////////// // 2556 // Composite operation 2557 2558 /// Sets the composite operation. 2559 /// Group: composite_operation 2560 public void globalCompositeOperation (NVGContext ctx, NVGCompositeOperation op) nothrow @trusted @nogc { 2561 NVGstate* state = nvg__getState(ctx); 2562 state.compositeOperation = nvg__compositeOperationState(op); 2563 } 2564 2565 /// Sets the composite operation with custom pixel arithmetic. 2566 /// Group: composite_operation 2567 public void globalCompositeBlendFunc (NVGContext ctx, NVGBlendFactor sfactor, NVGBlendFactor dfactor) nothrow @trusted @nogc { 2568 ctx.globalCompositeBlendFuncSeparate(sfactor, dfactor, sfactor, dfactor); 2569 } 2570 2571 /// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. 2572 /// Group: composite_operation 2573 public void globalCompositeBlendFuncSeparate (NVGContext ctx, NVGBlendFactor srcRGB, NVGBlendFactor dstRGB, NVGBlendFactor srcAlpha, NVGBlendFactor dstAlpha) nothrow @trusted @nogc { 2574 NVGCompositeOperationState op; 2575 op.simple = false; 2576 op.srcRGB = srcRGB; 2577 op.dstRGB = dstRGB; 2578 op.srcAlpha = srcAlpha; 2579 op.dstAlpha = dstAlpha; 2580 NVGstate* state = nvg__getState(ctx); 2581 state.compositeOperation = op; 2582 } 2583 2584 2585 // ////////////////////////////////////////////////////////////////////////// // 2586 // Color utils 2587 2588 /// Returns a color value from string form. 2589 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb" 2590 /// Group: color_utils 2591 public NVGColor nvgRGB (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); } 2592 2593 /// Ditto. 2594 public NVGColor nvgRGBA (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); } 2595 2596 /// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f). 2597 /// Group: color_utils 2598 public NVGColor nvgRGB (int r, int g, int b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(nvgClampToByte(r), nvgClampToByte(g), nvgClampToByte(b), 255); } 2599 2600 /// Returns a color value from red, green, blue values. Alpha will be set to 1.0f. 2601 /// Group: color_utils 2602 public NVGColor nvgRGBf (float r, float g, float b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(r, g, b, 1.0f); } 2603 2604 /// Returns a color value from red, green, blue and alpha values. 2605 /// Group: color_utils 2606 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)); } 2607 2608 /// Returns a color value from red, green, blue and alpha values. 2609 /// Group: color_utils 2610 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); } 2611 2612 /// Returns new color with transparency (alpha) set to [a]. 2613 /// Group: color_utils 2614 public NVGColor nvgTransRGBA (NVGColor c, ubyte a) nothrow @trusted @nogc { 2615 pragma(inline, true); 2616 c.a = a/255.0f; 2617 return c; 2618 } 2619 2620 /// Ditto. 2621 public NVGColor nvgTransRGBAf (NVGColor c, float a) nothrow @trusted @nogc { 2622 pragma(inline, true); 2623 c.a = a; 2624 return c; 2625 } 2626 2627 /// Linearly interpolates from color c0 to c1, and returns resulting color value. 2628 /// Group: color_utils 2629 public NVGColor nvgLerpRGBA() (const scope auto ref NVGColor c0, const scope auto ref NVGColor c1, float u) nothrow @trusted @nogc { 2630 NVGColor cint = void; 2631 u = nvg__clamp(u, 0.0f, 1.0f); 2632 float oneminu = 1.0f-u; 2633 foreach (uint i; 0..4) cint.rgba.ptr[i] = c0.rgba.ptr[i]*oneminu+c1.rgba.ptr[i]*u; 2634 return cint; 2635 } 2636 2637 /* see below 2638 public NVGColor nvgHSL() (float h, float s, float l) { 2639 //pragma(inline, true); // alas 2640 return nvgHSLA(h, s, l, 255); 2641 } 2642 */ 2643 2644 float nvg__hue (float h, float m1, float m2) pure nothrow @safe @nogc { 2645 if (h < 0) h += 1; 2646 if (h > 1) h -= 1; 2647 if (h < 1.0f/6.0f) return m1+(m2-m1)*h*6.0f; 2648 if (h < 3.0f/6.0f) return m2; 2649 if (h < 4.0f/6.0f) return m1+(m2-m1)*(2.0f/3.0f-h)*6.0f; 2650 return m1; 2651 } 2652 2653 /// Returns color value specified by hue, saturation and lightness. 2654 /// HSL values are all in range [0..1], alpha will be set to 255. 2655 /// Group: color_utils 2656 public alias nvgHSL = nvgHSLA; // trick to allow inlining 2657 2658 /// Returns color value specified by hue, saturation and lightness and alpha. 2659 /// HSL values are all in range [0..1], alpha in range [0..255]. 2660 /// Group: color_utils 2661 public NVGColor nvgHSLA (float h, float s, float l, ubyte a=255) nothrow @trusted @nogc { 2662 pragma(inline, true); 2663 NVGColor col = void; 2664 h = nvg__modf(h, 1.0f); 2665 if (h < 0.0f) h += 1.0f; 2666 s = nvg__clamp(s, 0.0f, 1.0f); 2667 l = nvg__clamp(l, 0.0f, 1.0f); 2668 immutable float m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s); 2669 immutable float m1 = 2*l-m2; 2670 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2671 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f); 2672 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2673 col.a = a/255.0f; 2674 return col; 2675 } 2676 2677 /// Returns color value specified by hue, saturation and lightness and alpha. 2678 /// HSL values and alpha are all in range [0..1]. 2679 /// Group: color_utils 2680 public NVGColor nvgHSLA (float h, float s, float l, float a) nothrow @trusted @nogc { 2681 // sorry for copypasta, it is for inliner 2682 static if (__VERSION__ >= 2072) pragma(inline, true); 2683 NVGColor col = void; 2684 h = nvg__modf(h, 1.0f); 2685 if (h < 0.0f) h += 1.0f; 2686 s = nvg__clamp(s, 0.0f, 1.0f); 2687 l = nvg__clamp(l, 0.0f, 1.0f); 2688 immutable m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s); 2689 immutable m1 = 2*l-m2; 2690 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2691 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f); 2692 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2693 col.a = a; 2694 return col; 2695 } 2696 2697 2698 // ////////////////////////////////////////////////////////////////////////// // 2699 // Matrices and Transformations 2700 2701 /** Matrix class. 2702 * 2703 * Group: matrices 2704 */ 2705 public align(1) struct NVGMatrix { 2706 align(1): 2707 private: 2708 static immutable float[6] IdentityMat = [ 2709 1.0f, 0.0f, 2710 0.0f, 1.0f, 2711 0.0f, 0.0f, 2712 ]; 2713 2714 public: 2715 /// Matrix values. Initial value is identity matrix. 2716 float[6] mat = [ 2717 1.0f, 0.0f, 2718 0.0f, 1.0f, 2719 0.0f, 0.0f, 2720 ]; 2721 2722 public nothrow @trusted @nogc: 2723 /// Create Matrix with the given values. 2724 this (const(float)[] amat...) { 2725 pragma(inline, true); 2726 if (amat.length >= 6) { 2727 mat.ptr[0..6] = amat.ptr[0..6]; 2728 } else { 2729 mat.ptr[0..6] = 0; 2730 mat.ptr[0..amat.length] = amat[]; 2731 } 2732 } 2733 2734 /// Can be used to check validity of [inverted] result 2735 @property bool valid () const { import core.stdc.math : isfinite; return (isfinite(mat.ptr[0]) != 0); } 2736 2737 /// Returns `true` if this matrix is identity matrix. 2738 @property bool isIdentity () const { version(aliced) pragma(inline, true); return (mat[] == IdentityMat[]); } 2739 2740 /// Returns new inverse matrix. 2741 /// If inverted matrix cannot be calculated, `res.valid` fill be `false`. 2742 NVGMatrix inverted () const { 2743 NVGMatrix res = this; 2744 res.invert; 2745 return res; 2746 } 2747 2748 /// Inverts this matrix. 2749 /// If inverted matrix cannot be calculated, `this.valid` fill be `false`. 2750 ref NVGMatrix invert () return { 2751 float[6] inv = void; 2752 immutable double det = cast(double)mat.ptr[0]*mat.ptr[3]-cast(double)mat.ptr[2]*mat.ptr[1]; 2753 if (det > -1e-6 && det < 1e-6) { 2754 inv[] = float.nan; 2755 } else { 2756 immutable double invdet = 1.0/det; 2757 inv.ptr[0] = cast(float)(mat.ptr[3]*invdet); 2758 inv.ptr[2] = cast(float)(-mat.ptr[2]*invdet); 2759 inv.ptr[4] = cast(float)((cast(double)mat.ptr[2]*mat.ptr[5]-cast(double)mat.ptr[3]*mat.ptr[4])*invdet); 2760 inv.ptr[1] = cast(float)(-mat.ptr[1]*invdet); 2761 inv.ptr[3] = cast(float)(mat.ptr[0]*invdet); 2762 inv.ptr[5] = cast(float)((cast(double)mat.ptr[1]*mat.ptr[4]-cast(double)mat.ptr[0]*mat.ptr[5])*invdet); 2763 } 2764 mat.ptr[0..6] = inv.ptr[0..6]; 2765 return this; 2766 } 2767 2768 /// Sets this matrix to identity matrix. 2769 ref NVGMatrix identity () return { version(aliced) pragma(inline, true); mat[] = IdentityMat[]; return this; } 2770 2771 /// Translate this matrix. 2772 ref NVGMatrix translate (in float tx, in float ty) return { 2773 version(aliced) pragma(inline, true); 2774 return this.mul(Translated(tx, ty)); 2775 } 2776 2777 /// Scale this matrix. 2778 ref NVGMatrix scale (in float sx, in float sy) return { 2779 version(aliced) pragma(inline, true); 2780 return this.mul(Scaled(sx, sy)); 2781 } 2782 2783 /// Rotate this matrix. 2784 ref NVGMatrix rotate (in float a) return { 2785 version(aliced) pragma(inline, true); 2786 return this.mul(Rotated(a)); 2787 } 2788 2789 /// Skew this matrix by X axis. 2790 ref NVGMatrix skewX (in float a) return { 2791 version(aliced) pragma(inline, true); 2792 return this.mul(SkewedX(a)); 2793 } 2794 2795 /// Skew this matrix by Y axis. 2796 ref NVGMatrix skewY (in float a) return { 2797 version(aliced) pragma(inline, true); 2798 return this.mul(SkewedY(a)); 2799 } 2800 2801 /// Skew this matrix by both axes. 2802 ref NVGMatrix skewY (in float ax, in float ay) return { 2803 version(aliced) pragma(inline, true); 2804 return this.mul(SkewedXY(ax, ay)); 2805 } 2806 2807 /// Transform point with this matrix. `null` destinations are allowed. 2808 /// [sx] and [sy] is the source point. [dx] and [dy] may point to the same variables. 2809 void point (float* dx, float* dy, float sx, float sy) nothrow @trusted @nogc { 2810 version(aliced) pragma(inline, true); 2811 if (dx !is null) *dx = sx*mat.ptr[0]+sy*mat.ptr[2]+mat.ptr[4]; 2812 if (dy !is null) *dy = sx*mat.ptr[1]+sy*mat.ptr[3]+mat.ptr[5]; 2813 } 2814 2815 /// Transform point with this matrix. 2816 void point (ref float x, ref float y) nothrow @trusted @nogc { 2817 version(aliced) pragma(inline, true); 2818 immutable float nx = x*mat.ptr[0]+y*mat.ptr[2]+mat.ptr[4]; 2819 immutable float ny = x*mat.ptr[1]+y*mat.ptr[3]+mat.ptr[5]; 2820 x = nx; 2821 y = ny; 2822 } 2823 2824 /// Sets this matrix to the result of multiplication of `this` and [s] (this * S). 2825 ref NVGMatrix mul() (const scope auto ref NVGMatrix s) { 2826 immutable float t0 = mat.ptr[0]*s.mat.ptr[0]+mat.ptr[1]*s.mat.ptr[2]; 2827 immutable float t2 = mat.ptr[2]*s.mat.ptr[0]+mat.ptr[3]*s.mat.ptr[2]; 2828 immutable float t4 = mat.ptr[4]*s.mat.ptr[0]+mat.ptr[5]*s.mat.ptr[2]+s.mat.ptr[4]; 2829 mat.ptr[1] = mat.ptr[0]*s.mat.ptr[1]+mat.ptr[1]*s.mat.ptr[3]; 2830 mat.ptr[3] = mat.ptr[2]*s.mat.ptr[1]+mat.ptr[3]*s.mat.ptr[3]; 2831 mat.ptr[5] = mat.ptr[4]*s.mat.ptr[1]+mat.ptr[5]*s.mat.ptr[3]+s.mat.ptr[5]; 2832 mat.ptr[0] = t0; 2833 mat.ptr[2] = t2; 2834 mat.ptr[4] = t4; 2835 return this; 2836 } 2837 2838 /// Sets this matrix to the result of multiplication of [s] and `this` (S * this). 2839 /// Sets the transform to the result of multiplication of two transforms, of A = B*A. 2840 /// Group: matrices 2841 ref NVGMatrix premul() (const scope auto ref NVGMatrix s) { 2842 NVGMatrix s2 = s; 2843 s2.mul(this); 2844 mat[] = s2.mat[]; 2845 return this; 2846 } 2847 2848 /// Multiply this matrix by [s], return result as new matrix. 2849 /// Performs operations in this left-to-right order. 2850 NVGMatrix opBinary(string op="*") (const scope auto ref NVGMatrix s) const { 2851 version(aliced) pragma(inline, true); 2852 NVGMatrix res = this; 2853 res.mul(s); 2854 return res; 2855 } 2856 2857 /// Multiply this matrix by [s]. 2858 /// Performs operations in this left-to-right order. 2859 ref NVGMatrix opOpAssign(string op="*") (const scope auto ref NVGMatrix s) { 2860 version(aliced) pragma(inline, true); 2861 return this.mul(s); 2862 } 2863 2864 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. 2865 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. 2866 float rotation () const { pragma(inline, true); return nvg__atan2f(mat.ptr[1], mat.ptr[0]); } /// Returns rotation of this matrix. 2867 float tx () const { pragma(inline, true); return mat.ptr[4]; } /// Returns x translation of this matrix. 2868 float ty () const { pragma(inline, true); return mat.ptr[5]; } /// Returns y translation of this matrix. 2869 2870 ref NVGMatrix scaleX (in float v) return { pragma(inline, true); return scaleRotateTransform(v, scaleY, rotation, tx, ty); } /// Sets x scaling of this matrix. 2871 ref NVGMatrix scaleY (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, v, rotation, tx, ty); } /// Sets y scaling of this matrix. 2872 ref NVGMatrix rotation (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, scaleY, v, tx, ty); } /// Sets rotation of this matrix. 2873 ref NVGMatrix tx (in float v) return { pragma(inline, true); mat.ptr[4] = v; return this; } /// Sets x translation of this matrix. 2874 ref NVGMatrix ty (in float v) return { pragma(inline, true); mat.ptr[5] = v; return this; } /// Sets y translation of this matrix. 2875 2876 /// Utility function to be used in `setXXX()`. 2877 /// This is the same as doing: `mat.identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster 2878 ref NVGMatrix scaleRotateTransform (in float xscale, in float yscale, in float a, in float tx, in float ty) return { 2879 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2880 mat.ptr[0] = xscale*cs; mat.ptr[1] = yscale*sn; 2881 mat.ptr[2] = xscale*-sn; mat.ptr[3] = yscale*cs; 2882 mat.ptr[4] = tx; mat.ptr[5] = ty; 2883 return this; 2884 } 2885 2886 /// This is the same as doing: `mat.identity.rotate(a).translate(tx, ty)`, only faster 2887 ref NVGMatrix rotateTransform (in float a, in float tx, in float ty) return { 2888 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2889 mat.ptr[0] = cs; mat.ptr[1] = sn; 2890 mat.ptr[2] = -sn; mat.ptr[3] = cs; 2891 mat.ptr[4] = tx; mat.ptr[5] = ty; 2892 return this; 2893 } 2894 2895 /// Returns new identity matrix. 2896 static NVGMatrix Identity () { pragma(inline, true); return NVGMatrix.init; } 2897 2898 /// Returns new translation matrix. 2899 static NVGMatrix Translated (in float tx, in float ty) { 2900 version(aliced) pragma(inline, true); 2901 NVGMatrix res = void; 2902 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f; 2903 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f; 2904 res.mat.ptr[4] = tx; res.mat.ptr[5] = ty; 2905 return res; 2906 } 2907 2908 /// Returns new scaling matrix. 2909 static NVGMatrix Scaled (in float sx, in float sy) { 2910 version(aliced) pragma(inline, true); 2911 NVGMatrix res = void; 2912 res.mat.ptr[0] = sx; res.mat.ptr[1] = 0.0f; 2913 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = sy; 2914 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2915 return res; 2916 } 2917 2918 /// Returns new rotation matrix. Angle is specified in radians. 2919 static NVGMatrix Rotated (in float a) { 2920 version(aliced) pragma(inline, true); 2921 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2922 NVGMatrix res = void; 2923 res.mat.ptr[0] = cs; res.mat.ptr[1] = sn; 2924 res.mat.ptr[2] = -sn; res.mat.ptr[3] = cs; 2925 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2926 return res; 2927 } 2928 2929 /// Returns new x-skewing matrix. Angle is specified in radians. 2930 static NVGMatrix SkewedX (in float a) { 2931 version(aliced) pragma(inline, true); 2932 NVGMatrix res = void; 2933 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f; 2934 res.mat.ptr[2] = nvg__tanf(a); res.mat.ptr[3] = 1.0f; 2935 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2936 return res; 2937 } 2938 2939 /// Returns new y-skewing matrix. Angle is specified in radians. 2940 static NVGMatrix SkewedY (in float a) { 2941 version(aliced) pragma(inline, true); 2942 NVGMatrix res = void; 2943 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(a); 2944 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f; 2945 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2946 return res; 2947 } 2948 2949 /// Returns new xy-skewing matrix. Angles are specified in radians. 2950 static NVGMatrix SkewedXY (in float ax, in float ay) { 2951 version(aliced) pragma(inline, true); 2952 NVGMatrix res = void; 2953 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(ay); 2954 res.mat.ptr[2] = nvg__tanf(ax); res.mat.ptr[3] = 1.0f; 2955 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2956 return res; 2957 } 2958 2959 /// Utility function to be used in `setXXX()`. 2960 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster 2961 static NVGMatrix ScaledRotatedTransformed (in float xscale, in float yscale, in float a, in float tx, in float ty) { 2962 NVGMatrix res = void; 2963 res.scaleRotateTransform(xscale, yscale, a, tx, ty); 2964 return res; 2965 } 2966 2967 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).translate(tx, ty)`, only faster 2968 static NVGMatrix RotatedTransformed (in float a, in float tx, in float ty) { 2969 NVGMatrix res = void; 2970 res.rotateTransform(a, tx, ty); 2971 return res; 2972 } 2973 } 2974 2975 2976 /// Converts degrees to radians. 2977 /// Group: matrices 2978 public float nvgDegToRad() (in float deg) pure nothrow @safe @nogc { pragma(inline, true); return deg/180.0f*NVG_PI; } 2979 2980 /// Converts radians to degrees. 2981 /// Group: matrices 2982 public float nvgRadToDeg() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad/NVG_PI*180.0f; } 2983 2984 public alias nvgDegrees = nvgDegToRad; /// Use this like `42.nvgDegrees` 2985 public float nvgRadians() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad; } /// Use this like `0.1.nvgRadians` 2986 2987 2988 // ////////////////////////////////////////////////////////////////////////// // 2989 void nvg__setPaintColor() (ref NVGPaint p, const scope auto ref NVGColor color) nothrow @trusted @nogc { 2990 p.clear(); 2991 p.xform.identity; 2992 p.radius = 0.0f; 2993 p.feather = 1.0f; 2994 p.innerColor = p.middleColor = p.outerColor = color; 2995 p.midp = -1; 2996 p.simpleColor = true; 2997 } 2998 2999 3000 // ////////////////////////////////////////////////////////////////////////// // 3001 // State handling 3002 3003 version(nanovega_debug_clipping) { 3004 public void nvgClipDumpOn (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, true); } 3005 public void nvgClipDumpOff (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, false); } 3006 } 3007 3008 /** Pushes and saves the current render state into a state stack. 3009 * A matching [restore] must be used to restore the state. 3010 * Returns `false` if state stack overflowed. 3011 * 3012 * Group: state_handling 3013 */ 3014 public bool save (NVGContext ctx) nothrow @trusted @nogc { 3015 if (ctx.nstates >= NVG_MAX_STATES) return false; 3016 if (ctx.nstates > 0) { 3017 //memcpy(&ctx.states[ctx.nstates], &ctx.states[ctx.nstates-1], NVGstate.sizeof); 3018 ctx.states[ctx.nstates] = ctx.states[ctx.nstates-1]; 3019 ctx.params.renderPushClip(ctx.params.userPtr); 3020 } 3021 ++ctx.nstates; 3022 return true; 3023 } 3024 3025 /// Pops and restores current render state. 3026 /// Group: state_handling 3027 public bool restore (NVGContext ctx) nothrow @trusted @nogc { 3028 if (ctx.nstates <= 1) return false; 3029 ctx.states[ctx.nstates-1].clearPaint(); 3030 ctx.params.renderPopClip(ctx.params.userPtr); 3031 --ctx.nstates; 3032 return true; 3033 } 3034 3035 /// Resets current render state to default values. Does not affect the render state stack. 3036 /// Group: state_handling 3037 public void reset (NVGContext ctx) nothrow @trusted @nogc { 3038 NVGstate* state = nvg__getState(ctx); 3039 state.clearPaint(); 3040 3041 nvg__setPaintColor(state.fill, nvgRGBA(255, 255, 255, 255)); 3042 nvg__setPaintColor(state.stroke, nvgRGBA(0, 0, 0, 255)); 3043 state.compositeOperation = nvg__compositeOperationState(NVGCompositeOperation.SourceOver); 3044 state.shapeAntiAlias = true; 3045 state.strokeWidth = 1.0f; 3046 state.miterLimit = 10.0f; 3047 state.lineCap = NVGLineCap.Butt; 3048 state.lineJoin = NVGLineCap.Miter; 3049 state.alpha = 1.0f; 3050 state.xform.identity; 3051 3052 state.scissor.extent[] = -1.0f; 3053 3054 state.fontSize = 16.0f; 3055 state.letterSpacing = 0.0f; 3056 state.lineHeight = 1.0f; 3057 state.fontBlur = 0.0f; 3058 state.textAlign.reset; 3059 state.fontId = 0; 3060 state.evenOddMode = false; 3061 state.dashCount = 0; 3062 state.lastFlattenDashCount = 0; 3063 state.dashStart = 0; 3064 state.firstDashIsGap = false; 3065 state.dasherActive = false; 3066 3067 ctx.params.renderResetClip(ctx.params.userPtr); 3068 } 3069 3070 /** Returns `true` if we have any room in state stack. 3071 * It is guaranteed to have at least 32 stack slots. 3072 * 3073 * Group: state_handling 3074 */ 3075 public bool canSave (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates < NVG_MAX_STATES); } 3076 3077 /** Returns `true` if we have any saved state. 3078 * 3079 * Group: state_handling 3080 */ 3081 public bool canRestore (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates > 1); } 3082 3083 /// Returns `true` if rendering is currently blocked. 3084 /// Group: state_handling 3085 public bool renderBlocked (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.recblockdraw : false); } 3086 3087 /// Blocks/unblocks rendering 3088 /// Group: state_handling 3089 public void renderBlocked (NVGContext ctx, bool v) pure nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null && ctx.contextAlive) ctx.recblockdraw = v; } 3090 3091 /// Blocks/unblocks rendering; returns previous state. 3092 /// Group: state_handling 3093 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; } 3094 3095 3096 // ////////////////////////////////////////////////////////////////////////// // 3097 // Render styles 3098 3099 /// Sets filling mode to "even-odd". 3100 /// Group: render_styles 3101 public void evenOddFill (NVGContext ctx) nothrow @trusted @nogc { 3102 NVGstate* state = nvg__getState(ctx); 3103 state.evenOddMode = true; 3104 } 3105 3106 /// Sets filling mode to "non-zero" (this is default mode). 3107 /// Group: render_styles 3108 public void nonZeroFill (NVGContext ctx) nothrow @trusted @nogc { 3109 NVGstate* state = nvg__getState(ctx); 3110 state.evenOddMode = false; 3111 } 3112 3113 /// Sets whether to draw antialias for [stroke] and [fill]. It's enabled by default. 3114 /// Group: render_styles 3115 public void shapeAntiAlias (NVGContext ctx, bool enabled) { 3116 NVGstate* state = nvg__getState(ctx); 3117 state.shapeAntiAlias = enabled; 3118 } 3119 3120 /// Sets the stroke width of the stroke style. 3121 /// Group: render_styles 3122 @scriptable 3123 public void strokeWidth (NVGContext ctx, float width) nothrow @trusted @nogc { 3124 NVGstate* state = nvg__getState(ctx); 3125 state.strokeWidth = width; 3126 } 3127 3128 /// Sets the miter limit of the stroke style. Miter limit controls when a sharp corner is beveled. 3129 /// Group: render_styles 3130 public void miterLimit (NVGContext ctx, float limit) nothrow @trusted @nogc { 3131 NVGstate* state = nvg__getState(ctx); 3132 state.miterLimit = limit; 3133 } 3134 3135 /// Sets how the end of the line (cap) is drawn, 3136 /// Can be one of: NVGLineCap.Butt (default), NVGLineCap.Round, NVGLineCap.Square. 3137 /// Group: render_styles 3138 public void lineCap (NVGContext ctx, NVGLineCap cap) nothrow @trusted @nogc { 3139 NVGstate* state = nvg__getState(ctx); 3140 state.lineCap = cap; 3141 } 3142 3143 /// Sets how sharp path corners are drawn. 3144 /// Can be one of NVGLineCap.Miter (default), NVGLineCap.Round, NVGLineCap.Bevel. 3145 /// Group: render_styles 3146 public void lineJoin (NVGContext ctx, NVGLineCap join) nothrow @trusted @nogc { 3147 NVGstate* state = nvg__getState(ctx); 3148 state.lineJoin = join; 3149 } 3150 3151 /// Sets stroke dashing, using (dash_length, gap_length) pairs. 3152 /// Current limit is 16 pairs. 3153 /// Resets dash start to zero. 3154 /// Group: render_styles 3155 public void setLineDash (NVGContext ctx, const(float)[] dashdata) nothrow @trusted @nogc { 3156 NVGstate* state = nvg__getState(ctx); 3157 state.dashCount = 0; 3158 state.dashStart = 0; 3159 state.firstDashIsGap = false; 3160 if (dashdata.length >= 2) { 3161 bool curFIsGap = true; // trick 3162 foreach (immutable idx, float f; dashdata) { 3163 curFIsGap = !curFIsGap; 3164 if (f < 0.01f) continue; // skip it 3165 if (idx == 0) { 3166 // register first dash 3167 state.firstDashIsGap = curFIsGap; 3168 state.dashes.ptr[state.dashCount++] = f; 3169 } else { 3170 if ((idx&1) != ((state.dashCount&1)^cast(uint)state.firstDashIsGap)) { 3171 // oops, continuation 3172 state.dashes[state.dashCount-1] += f; 3173 } else { 3174 if (state.dashCount == state.dashes.length) break; 3175 state.dashes[state.dashCount++] = f; 3176 } 3177 } 3178 } 3179 if (state.dashCount&1) { 3180 if (state.dashCount == 1) { 3181 state.dashCount = 0; 3182 } else { 3183 assert(state.dashCount < state.dashes.length); 3184 state.dashes[state.dashCount++] = 0; 3185 } 3186 } 3187 // calculate total dash path length 3188 state.totalDashLen = 0; 3189 foreach (float f; state.dashes.ptr[0..state.dashCount]) state.totalDashLen += f; 3190 if (state.totalDashLen < 0.01f) { 3191 state.dashCount = 0; // nothing to do 3192 } else { 3193 if (state.lastFlattenDashCount != 0) state.lastFlattenDashCount = uint.max; // force re-flattening 3194 } 3195 } 3196 } 3197 3198 public alias lineDash = setLineDash; /// Ditto. 3199 3200 /// Sets stroke dashing, using (dash_length, gap_length) pairs. 3201 /// Current limit is 16 pairs. 3202 /// Group: render_styles 3203 public void setLineDashStart (NVGContext ctx, in float dashStart) nothrow @trusted @nogc { 3204 NVGstate* state = nvg__getState(ctx); 3205 if (state.lastFlattenDashCount != 0 && state.dashStart != dashStart) { 3206 state.lastFlattenDashCount = uint.max; // force re-flattening 3207 } 3208 state.dashStart = dashStart; 3209 } 3210 3211 public alias lineDashStart = setLineDashStart; /// Ditto. 3212 3213 /// Sets the transparency applied to all rendered shapes. 3214 /// Already transparent paths will get proportionally more transparent as well. 3215 /// Group: render_styles 3216 public void globalAlpha (NVGContext ctx, float alpha) nothrow @trusted @nogc { 3217 NVGstate* state = nvg__getState(ctx); 3218 state.alpha = alpha; 3219 } 3220 3221 private void strokeColor() {} 3222 3223 static if (NanoVegaHasArsdColor) { 3224 /// Sets current stroke style to a solid color. 3225 /// Group: render_styles 3226 @scriptable 3227 public void strokeColor (NVGContext ctx, Color color) nothrow @trusted @nogc { 3228 NVGstate* state = nvg__getState(ctx); 3229 nvg__setPaintColor(state.stroke, NVGColor(color)); 3230 } 3231 } 3232 3233 /// Sets current stroke style to a solid color. 3234 /// Group: render_styles 3235 public void strokeColor() (NVGContext ctx, const scope auto ref NVGColor color) nothrow @trusted @nogc { 3236 NVGstate* state = nvg__getState(ctx); 3237 nvg__setPaintColor(state.stroke, color); 3238 } 3239 3240 @scriptable 3241 public void strokePaint(NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc { 3242 strokePaint(ctx, *paint); 3243 } 3244 3245 /// Sets current stroke style to a paint, which can be a one of the gradients or a pattern. 3246 /// Group: render_styles 3247 @scriptable 3248 public void strokePaint() (NVGContext ctx, const scope auto ref NVGPaint paint) nothrow @trusted @nogc { 3249 NVGstate* state = nvg__getState(ctx); 3250 state.stroke = paint; 3251 //nvgTransformMultiply(state.stroke.xform[], state.xform[]); 3252 state.stroke.xform.mul(state.xform); 3253 } 3254 3255 // this is a hack to work around https://issues.dlang.org/show_bug.cgi?id=16206 3256 // for scriptable reflection. it just needs to be declared first among the overloads 3257 private void fillColor (NVGContext ctx) nothrow @trusted @nogc { } 3258 3259 static if (NanoVegaHasArsdColor) { 3260 /// Sets current fill style to a solid color. 3261 /// Group: render_styles 3262 @scriptable 3263 public void fillColor (NVGContext ctx, Color color) nothrow @trusted @nogc { 3264 NVGstate* state = nvg__getState(ctx); 3265 nvg__setPaintColor(state.fill, NVGColor(color)); 3266 } 3267 } 3268 3269 /// Sets current fill style to a solid color. 3270 /// Group: render_styles 3271 public void fillColor() (NVGContext ctx, const scope auto ref NVGColor color) nothrow @trusted @nogc { 3272 NVGstate* state = nvg__getState(ctx); 3273 nvg__setPaintColor(state.fill, color); 3274 3275 } 3276 3277 @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) 3278 public void fillPaint (NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc { 3279 fillPaint(ctx, *paint); 3280 } 3281 3282 /// Sets current fill style to a paint, which can be a one of the gradients or a pattern. 3283 /// Group: render_styles 3284 @scriptable 3285 public void fillPaint() (NVGContext ctx, const scope auto ref NVGPaint paint) nothrow @trusted @nogc { 3286 NVGstate* state = nvg__getState(ctx); 3287 state.fill = paint; 3288 //nvgTransformMultiply(state.fill.xform[], state.xform[]); 3289 state.fill.xform.mul(state.xform); 3290 } 3291 3292 /// Sets current fill style to a multistop linear gradient. 3293 /// Group: render_styles 3294 public void fillPaint() (NVGContext ctx, const scope auto ref NVGLGS lgs) nothrow @trusted @nogc { 3295 if (!lgs.valid) { 3296 NVGPaint p = void; 3297 memset(&p, 0, p.sizeof); 3298 nvg__setPaintColor(p, NVGColor.red); 3299 ctx.fillPaint = p; 3300 } else if (lgs.midp >= -1) { 3301 //{ import core.stdc.stdio; printf("SIMPLE! midp=%f\n", cast(double)lgs.midp); } 3302 ctx.fillPaint = ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc); 3303 } else { 3304 ctx.fillPaint = ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid); 3305 } 3306 } 3307 3308 /// Returns current transformation matrix. 3309 /// Group: render_transformations 3310 public NVGMatrix currTransform (NVGContext ctx) pure nothrow @trusted @nogc { 3311 NVGstate* state = nvg__getState(ctx); 3312 return state.xform; 3313 } 3314 3315 /// Sets current transformation matrix. 3316 /// Group: render_transformations 3317 public void currTransform() (NVGContext ctx, const scope auto ref NVGMatrix m) nothrow @trusted @nogc { 3318 NVGstate* state = nvg__getState(ctx); 3319 state.xform = m; 3320 } 3321 3322 /// Resets current transform to an identity matrix. 3323 /// Group: render_transformations 3324 @scriptable 3325 public void resetTransform (NVGContext ctx) nothrow @trusted @nogc { 3326 NVGstate* state = nvg__getState(ctx); 3327 state.xform.identity; 3328 } 3329 3330 /// Premultiplies current coordinate system by specified matrix. 3331 /// Group: render_transformations 3332 public void transform() (NVGContext ctx, const scope auto ref NVGMatrix mt) nothrow @trusted @nogc { 3333 NVGstate* state = nvg__getState(ctx); 3334 //nvgTransformPremultiply(state.xform[], t[]); 3335 state.xform *= mt; 3336 } 3337 3338 /// Translates current coordinate system. 3339 /// Group: render_transformations 3340 @scriptable 3341 public void translate (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 3342 NVGstate* state = nvg__getState(ctx); 3343 //NVGMatrix t = void; 3344 //nvgTransformTranslate(t[], x, y); 3345 //nvgTransformPremultiply(state.xform[], t[]); 3346 state.xform.premul(NVGMatrix.Translated(x, y)); 3347 } 3348 3349 /// Rotates current coordinate system. Angle is specified in radians. 3350 /// Group: render_transformations 3351 @scriptable 3352 public void rotate (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3353 NVGstate* state = nvg__getState(ctx); 3354 //NVGMatrix t = void; 3355 //nvgTransformRotate(t[], angle); 3356 //nvgTransformPremultiply(state.xform[], t[]); 3357 state.xform.premul(NVGMatrix.Rotated(angle)); 3358 } 3359 3360 /// Skews the current coordinate system along X axis. Angle is specified in radians. 3361 /// Group: render_transformations 3362 @scriptable 3363 public void skewX (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3364 NVGstate* state = nvg__getState(ctx); 3365 //NVGMatrix t = void; 3366 //nvgTransformSkewX(t[], angle); 3367 //nvgTransformPremultiply(state.xform[], t[]); 3368 state.xform.premul(NVGMatrix.SkewedX(angle)); 3369 } 3370 3371 /// Skews the current coordinate system along Y axis. Angle is specified in radians. 3372 /// Group: render_transformations 3373 @scriptable 3374 public void skewY (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3375 NVGstate* state = nvg__getState(ctx); 3376 //NVGMatrix t = void; 3377 //nvgTransformSkewY(t[], angle); 3378 //nvgTransformPremultiply(state.xform[], t[]); 3379 state.xform.premul(NVGMatrix.SkewedY(angle)); 3380 } 3381 3382 /// Scales the current coordinate system. 3383 /// Group: render_transformations 3384 @scriptable 3385 public void scale (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 3386 NVGstate* state = nvg__getState(ctx); 3387 //NVGMatrix t = void; 3388 //nvgTransformScale(t[], x, y); 3389 //nvgTransformPremultiply(state.xform[], t[]); 3390 state.xform.premul(NVGMatrix.Scaled(x, y)); 3391 } 3392 3393 3394 // ////////////////////////////////////////////////////////////////////////// // 3395 // Images 3396 3397 /// Creates image by loading it from the disk from specified file name. 3398 /// Returns handle to the image or 0 on error. 3399 /// Group: images 3400 public NVGImage createImage() (NVGContext ctx, const(char)[] filename, const(NVGImageFlag)[] imageFlagsList...) { 3401 static if (NanoVegaHasArsdImage) { 3402 import arsd.image; 3403 // do we have new arsd API to load images? 3404 static if (!is(typeof(MemoryImage.fromImageFile)) || !is(typeof(MemoryImage.clearInternal))) { 3405 static assert(0, "Sorry, your ARSD is too old. Please, update it."); 3406 } 3407 try { 3408 auto oimg = MemoryImage.fromImageFile(filename); 3409 if (auto img = cast(TrueColorImage)oimg) { 3410 scope(exit) oimg.clearInternal(); 3411 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList); 3412 } else { 3413 TrueColorImage img = oimg.getAsTrueColorImage; 3414 scope(exit) img.clearInternal(); 3415 oimg.clearInternal(); // drop original image, as `getAsTrueColorImage()` MUST create a new one here 3416 oimg = null; 3417 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList); 3418 } 3419 } catch (Exception) {} 3420 return NVGImage.init; 3421 } else { 3422 import std.internal.cstring; 3423 ubyte* img; 3424 int w, h, n; 3425 stbi_set_unpremultiply_on_load(1); 3426 stbi_convert_iphone_png_to_rgb(1); 3427 img = stbi_load(filename.tempCString, &w, &h, &n, 4); 3428 if (img is null) { 3429 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); 3430 return NVGImage.init; 3431 } 3432 auto image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList); 3433 stbi_image_free(img); 3434 return image; 3435 } 3436 } 3437 3438 static if (NanoVegaHasArsdImage) { 3439 /// Creates image by loading it from the specified memory image. 3440 /// Returns handle to the image or 0 on error. 3441 /// Group: images 3442 public NVGImage createImageFromMemoryImage() (NVGContext ctx, MemoryImage img, const(NVGImageFlag)[] imageFlagsList...) { 3443 if (img is null) return NVGImage.init; 3444 if (auto tc = cast(TrueColorImage)img) { 3445 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList); 3446 } else { 3447 auto tc = img.getAsTrueColorImage; 3448 scope(exit) tc.clearInternal(); // here, it is guaranteed that `tc` is newly allocated image, so it is safe to kill it 3449 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList); 3450 } 3451 } 3452 } else { 3453 /// Creates image by loading it from the specified chunk of memory. 3454 /// Returns handle to the image or 0 on error. 3455 /// Group: images 3456 public NVGImage createImageMem() (NVGContext ctx, const(ubyte)* data, int ndata, const(NVGImageFlag)[] imageFlagsList...) { 3457 int w, h, n, image; 3458 ubyte* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4); 3459 if (img is null) { 3460 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); 3461 return NVGImage.init; 3462 } 3463 image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList); 3464 stbi_image_free(img); 3465 return image; 3466 } 3467 } 3468 3469 /// Creates image from specified image data. 3470 /// Returns handle to the image or 0 on error. 3471 /// Group: images 3472 public NVGImage createImageRGBA (NVGContext ctx, int w, int h, const(void)[] data, const(NVGImageFlag)[] imageFlagsList...) nothrow @trusted @nogc { 3473 if (w < 1 || h < 1 || data.length < w*h*4) return NVGImage.init; 3474 uint imageFlags = 0; 3475 foreach (immutable uint flag; imageFlagsList) imageFlags |= flag; 3476 NVGImage res; 3477 res.id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.RGBA, w, h, imageFlags, cast(const(ubyte)*)data.ptr); 3478 if (res.id > 0) { 3479 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); } 3480 res.ctx = ctx; 3481 ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount 3482 } 3483 return res; 3484 } 3485 3486 /// Updates image data specified by image handle. 3487 /// Group: images 3488 public void updateImage() (NVGContext ctx, auto ref NVGImage image, const(void)[] data) nothrow @trusted @nogc { 3489 if (image.valid) { 3490 int w, h; 3491 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3492 ctx.params.renderGetTextureSize(ctx.params.userPtr, image.id, &w, &h); 3493 ctx.params.renderUpdateTexture(ctx.params.userPtr, image.id, 0, 0, w, h, cast(const(ubyte)*)data.ptr); 3494 } 3495 } 3496 3497 /// Returns the dimensions of a created image. 3498 /// Group: images 3499 public void imageSize() (NVGContext ctx, const scope auto ref NVGImage image, out int w, out int h) nothrow @trusted @nogc { 3500 if (image.valid) { 3501 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3502 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, image.id, &w, &h); 3503 } 3504 } 3505 3506 /// Deletes created image. 3507 /// Group: images 3508 public void deleteImage() (NVGContext ctx, ref NVGImage image) nothrow @trusted @nogc { 3509 if (ctx is null || !image.valid) return; 3510 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3511 image.clear(); 3512 } 3513 3514 3515 // ////////////////////////////////////////////////////////////////////////// // 3516 // Paints 3517 3518 private void linearGradient() {} // hack for dmd bug 3519 3520 static if (NanoVegaHasArsdColor) { 3521 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates 3522 * of the linear gradient, icol specifies the start color and ocol the end color. 3523 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3524 * 3525 * Group: paints 3526 */ 3527 @scriptable 3528 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 { 3529 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), NVGColor(ocol)); 3530 } 3531 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start 3532 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in 3533 * range `(0..1)`, and ocol the end color. 3534 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3535 * 3536 * Group: paints 3537 */ 3538 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 { 3539 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), midp, NVGColor(mcol), NVGColor(ocol)); 3540 } 3541 } 3542 3543 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates 3544 * of the linear gradient, icol specifies the start color and ocol the end color. 3545 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3546 * 3547 * Group: paints 3548 */ 3549 public NVGPaint linearGradient() (NVGContext ctx, float sx, float sy, float ex, float ey, const scope auto ref NVGColor icol, const scope auto ref NVGColor ocol) nothrow @trusted @nogc { 3550 enum large = 1e5f; 3551 3552 NVGPaint p = void; 3553 memset(&p, 0, p.sizeof); 3554 p.simpleColor = false; 3555 3556 // Calculate transform aligned to the line 3557 float dx = ex-sx; 3558 float dy = ey-sy; 3559 immutable float d = nvg__sqrtf(dx*dx+dy*dy); 3560 if (d > 0.0001f) { 3561 dx /= d; 3562 dy /= d; 3563 } else { 3564 dx = 0; 3565 dy = 1; 3566 } 3567 3568 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx; 3569 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy; 3570 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large; 3571 3572 p.extent.ptr[0] = large; 3573 p.extent.ptr[1] = large+d*0.5f; 3574 3575 p.radius = 0.0f; 3576 3577 p.feather = nvg__max(NVG_MIN_FEATHER, d); 3578 3579 p.innerColor = p.middleColor = icol; 3580 p.outerColor = ocol; 3581 p.midp = -1; 3582 3583 return p; 3584 } 3585 3586 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start 3587 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in 3588 * range `(0..1)`, and ocol the end color. 3589 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3590 * 3591 * Group: paints 3592 */ 3593 public NVGPaint linearGradient() (NVGContext ctx, float sx, float sy, float ex, float ey, const scope auto ref NVGColor icol, in float midp, const scope auto ref NVGColor mcol, const scope auto ref NVGColor ocol) nothrow @trusted @nogc { 3594 enum large = 1e5f; 3595 3596 NVGPaint p = void; 3597 memset(&p, 0, p.sizeof); 3598 p.simpleColor = false; 3599 3600 // Calculate transform aligned to the line 3601 float dx = ex-sx; 3602 float dy = ey-sy; 3603 immutable float d = nvg__sqrtf(dx*dx+dy*dy); 3604 if (d > 0.0001f) { 3605 dx /= d; 3606 dy /= d; 3607 } else { 3608 dx = 0; 3609 dy = 1; 3610 } 3611 3612 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx; 3613 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy; 3614 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large; 3615 3616 p.extent.ptr[0] = large; 3617 p.extent.ptr[1] = large+d*0.5f; 3618 3619 p.radius = 0.0f; 3620 3621 p.feather = nvg__max(NVG_MIN_FEATHER, d); 3622 3623 if (midp <= 0) { 3624 p.innerColor = p.middleColor = mcol; 3625 p.midp = -1; 3626 } else if (midp > 1) { 3627 p.innerColor = p.middleColor = icol; 3628 p.midp = -1; 3629 } else { 3630 p.innerColor = icol; 3631 p.middleColor = mcol; 3632 p.midp = midp; 3633 } 3634 p.outerColor = ocol; 3635 3636 return p; 3637 } 3638 3639 static if (NanoVegaHasArsdColor) { 3640 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify 3641 * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. 3642 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3643 * 3644 * Group: paints 3645 */ 3646 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 { 3647 return ctx.radialGradient(cx, cy, inr, outr, NVGColor(icol), NVGColor(ocol)); 3648 } 3649 } 3650 3651 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify 3652 * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. 3653 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3654 * 3655 * Group: paints 3656 */ 3657 public NVGPaint radialGradient() (NVGContext ctx, float cx, float cy, float inr, float outr, const scope auto ref NVGColor icol, const scope auto ref NVGColor ocol) nothrow @trusted @nogc { 3658 immutable float r = (inr+outr)*0.5f; 3659 immutable float f = (outr-inr); 3660 3661 NVGPaint p = void; 3662 memset(&p, 0, p.sizeof); 3663 p.simpleColor = false; 3664 3665 p.xform.identity; 3666 p.xform.mat.ptr[4] = cx; 3667 p.xform.mat.ptr[5] = cy; 3668 3669 p.extent.ptr[0] = r; 3670 p.extent.ptr[1] = r; 3671 3672 p.radius = r; 3673 3674 p.feather = nvg__max(NVG_MIN_FEATHER, f); 3675 3676 p.innerColor = p.middleColor = icol; 3677 p.outerColor = ocol; 3678 p.midp = -1; 3679 3680 return p; 3681 } 3682 3683 static if (NanoVegaHasArsdColor) { 3684 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering 3685 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle, 3686 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry 3687 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. 3688 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3689 * 3690 * Group: paints 3691 */ 3692 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 { 3693 return ctx.boxGradient(x, y, w, h, r, f, NVGColor(icol), NVGColor(ocol)); 3694 } 3695 } 3696 3697 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering 3698 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle, 3699 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry 3700 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. 3701 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3702 * 3703 * Group: paints 3704 */ 3705 public NVGPaint boxGradient() (NVGContext ctx, float x, float y, float w, float h, float r, float f, const scope auto ref NVGColor icol, const scope auto ref NVGColor ocol) nothrow @trusted @nogc { 3706 NVGPaint p = void; 3707 memset(&p, 0, p.sizeof); 3708 p.simpleColor = false; 3709 3710 p.xform.identity; 3711 p.xform.mat.ptr[4] = x+w*0.5f; 3712 p.xform.mat.ptr[5] = y+h*0.5f; 3713 3714 p.extent.ptr[0] = w*0.5f; 3715 p.extent.ptr[1] = h*0.5f; 3716 3717 p.radius = r; 3718 3719 p.feather = nvg__max(NVG_MIN_FEATHER, f); 3720 3721 p.innerColor = p.middleColor = icol; 3722 p.outerColor = ocol; 3723 p.midp = -1; 3724 3725 return p; 3726 } 3727 3728 /** Creates and returns an image pattern. Parameters `(cx, cy)` specify the left-top location of the image pattern, 3729 * `(w, h)` the size of one image, [angle] rotation around the top-left corner, [image] is handle to the image to render. 3730 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3731 * 3732 * Group: paints 3733 */ 3734 public NVGPaint imagePattern() (NVGContext ctx, float cx, float cy, float w, float h, float angle, const scope auto ref NVGImage image, float alpha=1) nothrow @trusted @nogc { 3735 NVGPaint p = void; 3736 memset(&p, 0, p.sizeof); 3737 p.simpleColor = false; 3738 3739 p.xform.identity.rotate(angle); 3740 p.xform.mat.ptr[4] = cx; 3741 p.xform.mat.ptr[5] = cy; 3742 3743 p.extent.ptr[0] = w; 3744 p.extent.ptr[1] = h; 3745 3746 p.image = image; 3747 3748 p.innerColor = p.middleColor = p.outerColor = nvgRGBAf(1, 1, 1, alpha); 3749 p.midp = -1; 3750 3751 return p; 3752 } 3753 3754 /// Linear gradient with multiple stops. 3755 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3756 /// Group: paints 3757 public struct NVGLGS { 3758 private: 3759 NVGColor ic, mc, oc; // inner, middle, out 3760 float midp; 3761 NVGImage imgid; 3762 // [imagePattern] arguments 3763 float cx, cy, dimx, dimy; // dimx and dimy are ex and ey for simple gradients 3764 public float angle; /// 3765 3766 public: 3767 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (imgid.valid || midp >= -1); } /// 3768 void clear () nothrow @safe @nogc { pragma(inline, true); imgid.clear(); midp = float.nan; } /// 3769 } 3770 3771 /** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops]. 3772 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3773 * 3774 * $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3775 * Group: paints 3776 */ 3777 public NVGPaint asPaint() (NVGContext ctx, const scope auto ref NVGLGS lgs) nothrow @trusted @nogc { 3778 if (!lgs.valid) { 3779 NVGPaint p = void; 3780 memset(&p, 0, p.sizeof); 3781 nvg__setPaintColor(p, NVGColor.red); 3782 return p; 3783 } else if (lgs.midp >= -1) { 3784 return ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc); 3785 } else { 3786 return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid); 3787 } 3788 } 3789 3790 /// Gradient Stop Point. 3791 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3792 /// Group: paints 3793 public struct NVGGradientStop { 3794 float offset = 0; /// [0..1] 3795 NVGColor color; /// 3796 3797 this() (in float aofs, const scope auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } /// 3798 static if (NanoVegaHasArsdColor) { 3799 this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } /// 3800 } 3801 } 3802 3803 /// Create linear gradient data suitable to use with `linearGradient(res)`. 3804 /// Don't forget to destroy the result when you don't need it anymore with `ctx.kill(res);`. 3805 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3806 /// Group: paints 3807 public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, const(NVGGradientStop)[] stops...) nothrow @trusted @nogc { 3808 // based on the code by Jorge Acereda <jacereda@gmail.com> 3809 enum NVG_GRADIENT_SAMPLES = 1024; 3810 static void gradientSpan (uint* dst, const(NVGGradientStop)* s0, const(NVGGradientStop)* s1) nothrow @trusted @nogc { 3811 immutable float s0o = nvg__clamp(s0.offset, 0.0f, 1.0f); 3812 immutable float s1o = nvg__clamp(s1.offset, 0.0f, 1.0f); 3813 uint s = cast(uint)(s0o*NVG_GRADIENT_SAMPLES); 3814 uint e = cast(uint)(s1o*NVG_GRADIENT_SAMPLES); 3815 uint sc = 0xffffffffU; 3816 uint sh = 24; 3817 uint r = cast(uint)(s0.color.rgba[0]*sc); 3818 uint g = cast(uint)(s0.color.rgba[1]*sc); 3819 uint b = cast(uint)(s0.color.rgba[2]*sc); 3820 uint a = cast(uint)(s0.color.rgba[3]*sc); 3821 uint dr = cast(uint)((s1.color.rgba[0]*sc-r)/(e-s)); 3822 uint dg = cast(uint)((s1.color.rgba[1]*sc-g)/(e-s)); 3823 uint db = cast(uint)((s1.color.rgba[2]*sc-b)/(e-s)); 3824 uint da = cast(uint)((s1.color.rgba[3]*sc-a)/(e-s)); 3825 dst += s; 3826 foreach (immutable _; s..e) { 3827 version(BigEndian) { 3828 *dst++ = ((r>>sh)<<24)+((g>>sh)<<16)+((b>>sh)<<8)+((a>>sh)<<0); 3829 } else { 3830 *dst++ = ((a>>sh)<<24)+((b>>sh)<<16)+((g>>sh)<<8)+((r>>sh)<<0); 3831 } 3832 r += dr; 3833 g += dg; 3834 b += db; 3835 a += da; 3836 } 3837 } 3838 3839 NVGLGS res; 3840 res.cx = sx; 3841 res.cy = sy; 3842 3843 if (stops.length == 2 && stops.ptr[0].offset <= 0 && stops.ptr[1].offset >= 1) { 3844 // create simple linear gradient 3845 res.ic = res.mc = stops.ptr[0].color; 3846 res.oc = stops.ptr[1].color; 3847 res.midp = -1; 3848 res.dimx = ex; 3849 res.dimy = ey; 3850 } else if (stops.length == 3 && stops.ptr[0].offset <= 0 && stops.ptr[2].offset >= 1) { 3851 // create simple linear gradient with middle stop 3852 res.ic = stops.ptr[0].color; 3853 res.mc = stops.ptr[1].color; 3854 res.oc = stops.ptr[2].color; 3855 res.midp = stops.ptr[1].offset; 3856 res.dimx = ex; 3857 res.dimy = ey; 3858 } else { 3859 // create image gradient 3860 uint[NVG_GRADIENT_SAMPLES] data = void; 3861 immutable float w = ex-sx; 3862 immutable float h = ey-sy; 3863 res.dimx = nvg__sqrtf(w*w+h*h); 3864 res.dimy = 1; //??? 3865 3866 res.angle = 3867 (/*nvg__absf(h) < 0.0001 ? 0 : 3868 nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/ 3869 nvg__atan2f(h/*ey-sy*/, w/*ex-sx*/)); 3870 3871 if (stops.length > 0) { 3872 auto s0 = NVGGradientStop(0, nvgRGBAf(0, 0, 0, 1)); 3873 auto s1 = NVGGradientStop(1, nvgRGBAf(1, 1, 1, 1)); 3874 if (stops.length > 64) stops = stops[0..64]; 3875 if (stops.length) { 3876 s0.color = stops[0].color; 3877 s1.color = stops[$-1].color; 3878 } 3879 gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1)); 3880 foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1); 3881 gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1); 3882 res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[]/*, NVGImageFlag.RepeatX, NVGImageFlag.RepeatY*/); 3883 } 3884 } 3885 return res; 3886 } 3887 3888 3889 // ////////////////////////////////////////////////////////////////////////// // 3890 // Scissoring 3891 3892 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform. 3893 /// Group: scissoring 3894 public void scissor (NVGContext ctx, in float x, in float y, float w, float h) nothrow @trusted @nogc { 3895 NVGstate* state = nvg__getState(ctx); 3896 3897 w = nvg__max(0.0f, w); 3898 h = nvg__max(0.0f, h); 3899 3900 state.scissor.xform.identity; 3901 state.scissor.xform.mat.ptr[4] = x+w*0.5f; 3902 state.scissor.xform.mat.ptr[5] = y+h*0.5f; 3903 //nvgTransformMultiply(state.scissor.xform[], state.xform[]); 3904 state.scissor.xform.mul(state.xform); 3905 3906 state.scissor.extent.ptr[0] = w*0.5f; 3907 state.scissor.extent.ptr[1] = h*0.5f; 3908 } 3909 3910 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform. 3911 /// Arguments: [x, y, w, h]* 3912 /// Group: scissoring 3913 public void scissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 3914 enum ArgC = 4; 3915 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [scissor] call"); 3916 if (args.length < ArgC) return; 3917 NVGstate* state = nvg__getState(ctx); 3918 const(float)* aptr = args.ptr; 3919 foreach (immutable idx; 0..args.length/ArgC) { 3920 immutable x = *aptr++; 3921 immutable y = *aptr++; 3922 immutable w = nvg__max(0.0f, *aptr++); 3923 immutable h = nvg__max(0.0f, *aptr++); 3924 3925 state.scissor.xform.identity; 3926 state.scissor.xform.mat.ptr[4] = x+w*0.5f; 3927 state.scissor.xform.mat.ptr[5] = y+h*0.5f; 3928 //nvgTransformMultiply(state.scissor.xform[], state.xform[]); 3929 state.scissor.xform.mul(state.xform); 3930 3931 state.scissor.extent.ptr[0] = w*0.5f; 3932 state.scissor.extent.ptr[1] = h*0.5f; 3933 } 3934 } 3935 3936 void nvg__isectRects (float* dst, float ax, float ay, float aw, float ah, float bx, float by, float bw, float bh) nothrow @trusted @nogc { 3937 immutable float minx = nvg__max(ax, bx); 3938 immutable float miny = nvg__max(ay, by); 3939 immutable float maxx = nvg__min(ax+aw, bx+bw); 3940 immutable float maxy = nvg__min(ay+ah, by+bh); 3941 dst[0] = minx; 3942 dst[1] = miny; 3943 dst[2] = nvg__max(0.0f, maxx-minx); 3944 dst[3] = nvg__max(0.0f, maxy-miny); 3945 } 3946 3947 /** Intersects current scissor rectangle with the specified rectangle. 3948 * The scissor rectangle is transformed by the current transform. 3949 * Note: in case the rotation of previous scissor rect differs from 3950 * the current one, the intersection will be done between the specified 3951 * rectangle and the previous scissor rectangle transformed in the current 3952 * transform space. The resulting shape is always rectangle. 3953 * 3954 * Group: scissoring 3955 */ 3956 public void intersectScissor (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc { 3957 NVGstate* state = nvg__getState(ctx); 3958 3959 // If no previous scissor has been set, set the scissor as current scissor. 3960 if (state.scissor.extent.ptr[0] < 0) { 3961 ctx.scissor(x, y, w, h); 3962 return; 3963 } 3964 3965 NVGMatrix pxform = void; 3966 NVGMatrix invxorm = void; 3967 float[4] rect = void; 3968 3969 // Transform the current scissor rect into current transform space. 3970 // If there is difference in rotation, this will be approximation. 3971 //memcpy(pxform.mat.ptr, state.scissor.xform.ptr, float.sizeof*6); 3972 pxform = state.scissor.xform; 3973 immutable float ex = state.scissor.extent.ptr[0]; 3974 immutable float ey = state.scissor.extent.ptr[1]; 3975 //nvgTransformInverse(invxorm[], state.xform[]); 3976 invxorm = state.xform.inverted; 3977 //nvgTransformMultiply(pxform[], invxorm[]); 3978 pxform.mul(invxorm); 3979 immutable float tex = ex*nvg__absf(pxform.mat.ptr[0])+ey*nvg__absf(pxform.mat.ptr[2]); 3980 immutable float tey = ex*nvg__absf(pxform.mat.ptr[1])+ey*nvg__absf(pxform.mat.ptr[3]); 3981 3982 // Intersect rects. 3983 nvg__isectRects(rect.ptr, pxform.mat.ptr[4]-tex, pxform.mat.ptr[5]-tey, tex*2, tey*2, x, y, w, h); 3984 3985 //ctx.scissor(rect.ptr[0], rect.ptr[1], rect.ptr[2], rect.ptr[3]); 3986 ctx.scissor(rect.ptr[0..4]); 3987 } 3988 3989 /** Intersects current scissor rectangle with the specified rectangle. 3990 * The scissor rectangle is transformed by the current transform. 3991 * Note: in case the rotation of previous scissor rect differs from 3992 * the current one, the intersection will be done between the specified 3993 * rectangle and the previous scissor rectangle transformed in the current 3994 * transform space. The resulting shape is always rectangle. 3995 * 3996 * Arguments: [x, y, w, h]* 3997 * 3998 * Group: scissoring 3999 */ 4000 public void intersectScissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 4001 enum ArgC = 4; 4002 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [intersectScissor] call"); 4003 if (args.length < ArgC) return; 4004 const(float)* aptr = args.ptr; 4005 foreach (immutable idx; 0..args.length/ArgC) { 4006 immutable x = *aptr++; 4007 immutable y = *aptr++; 4008 immutable w = *aptr++; 4009 immutable h = *aptr++; 4010 ctx.intersectScissor(x, y, w, h); 4011 } 4012 } 4013 4014 /// Reset and disables scissoring. 4015 /// Group: scissoring 4016 public void resetScissor (NVGContext ctx) nothrow @trusted @nogc { 4017 NVGstate* state = nvg__getState(ctx); 4018 state.scissor.xform.mat[] = 0.0f; 4019 state.scissor.extent[] = -1.0f; 4020 } 4021 4022 4023 // ////////////////////////////////////////////////////////////////////////// // 4024 // Render-Time Affine Transformations 4025 4026 /// Sets GPU affine transformatin matrix. Don't do scaling or skewing here. 4027 /// This matrix won't be saved/restored with context state save/restore operations, as it is not a part of that state. 4028 /// Group: gpu_affine 4029 public void affineGPU() (NVGContext ctx, const scope auto ref NVGMatrix mat) nothrow @trusted @nogc { 4030 ctx.gpuAffine = mat; 4031 ctx.params.renderSetAffine(ctx.params.userPtr, ctx.gpuAffine); 4032 } 4033 4034 /// Get current GPU affine transformatin matrix. 4035 /// Group: gpu_affine 4036 public NVGMatrix affineGPU (NVGContext ctx) nothrow @safe @nogc { 4037 pragma(inline, true); 4038 return ctx.gpuAffine; 4039 } 4040 4041 /// "Untransform" point using current GPU affine matrix. 4042 /// Group: gpu_affine 4043 public void gpuUntransformPoint (NVGContext ctx, float *dx, float *dy, in float x, in float y) nothrow @safe @nogc { 4044 if (ctx.gpuAffine.isIdentity) { 4045 if (dx !is null) *dx = x; 4046 if (dy !is null) *dy = y; 4047 } else { 4048 // inverse GPU transformation 4049 NVGMatrix igpu = ctx.gpuAffine.inverted; 4050 igpu.point(dx, dy, x, y); 4051 } 4052 } 4053 4054 4055 // ////////////////////////////////////////////////////////////////////////// // 4056 // rasterization (tesselation) code 4057 4058 int nvg__ptEquals (float x1, float y1, float x2, float y2, float tol) pure nothrow @safe @nogc { 4059 //pragma(inline, true); 4060 immutable float dx = x2-x1; 4061 immutable float dy = y2-y1; 4062 return dx*dx+dy*dy < tol*tol; 4063 } 4064 4065 float nvg__distPtSeg (float x, float y, float px, float py, float qx, float qy) pure nothrow @safe @nogc { 4066 immutable float pqx = qx-px; 4067 immutable float pqy = qy-py; 4068 float dx = x-px; 4069 float dy = y-py; 4070 immutable float d = pqx*pqx+pqy*pqy; 4071 float t = pqx*dx+pqy*dy; 4072 if (d > 0) t /= d; 4073 if (t < 0) t = 0; else if (t > 1) t = 1; 4074 dx = px+t*pqx-x; 4075 dy = py+t*pqy-y; 4076 return dx*dx+dy*dy; 4077 } 4078 4079 void nvg__appendCommands(bool useCommand=true) (NVGContext ctx, Command acmd, const(float)[] vals...) nothrow @trusted @nogc { 4080 int nvals = cast(int)vals.length; 4081 static if (useCommand) { 4082 enum addon = 1; 4083 } else { 4084 enum addon = 0; 4085 if (nvals == 0) return; // nothing to do 4086 } 4087 4088 NVGstate* state = nvg__getState(ctx); 4089 4090 if (ctx.ncommands+nvals+addon > ctx.ccommands) { 4091 //int ccommands = ctx.ncommands+nvals+ctx.ccommands/2; 4092 int ccommands = ((ctx.ncommands+(nvals+addon))|0xfff)+1; 4093 float* commands = cast(float*)realloc(ctx.commands, float.sizeof*ccommands); 4094 if (commands is null) assert(0, "NanoVega: out of memory"); 4095 ctx.commands = commands; 4096 ctx.ccommands = ccommands; 4097 assert(ctx.ncommands+(nvals+addon) <= ctx.ccommands); 4098 } 4099 4100 static if (!useCommand) acmd = cast(Command)vals.ptr[0]; 4101 4102 if (acmd != Command.Close && acmd != Command.Winding) { 4103 //assert(nvals+addon >= 3); 4104 ctx.commandx = vals.ptr[nvals-2]; 4105 ctx.commandy = vals.ptr[nvals-1]; 4106 } 4107 4108 // copy commands 4109 float* vp = ctx.commands+ctx.ncommands; 4110 static if (useCommand) { 4111 vp[0] = cast(float)acmd; 4112 if (nvals > 0) memcpy(vp+1, vals.ptr, nvals*float.sizeof); 4113 } else { 4114 memcpy(vp, vals.ptr, nvals*float.sizeof); 4115 } 4116 ctx.ncommands += nvals+addon; 4117 4118 // transform commands 4119 int i = nvals+addon; 4120 while (i > 0) { 4121 int nlen = 1; 4122 final switch (cast(Command)(*vp)) { 4123 case Command.MoveTo: 4124 case Command.LineTo: 4125 assert(i >= 3); 4126 state.xform.point(vp+1, vp+2, vp[1], vp[2]); 4127 nlen = 3; 4128 break; 4129 case Command.BezierTo: 4130 assert(i >= 7); 4131 state.xform.point(vp+1, vp+2, vp[1], vp[2]); 4132 state.xform.point(vp+3, vp+4, vp[3], vp[4]); 4133 state.xform.point(vp+5, vp+6, vp[5], vp[6]); 4134 nlen = 7; 4135 break; 4136 case Command.Close: 4137 nlen = 1; 4138 break; 4139 case Command.Winding: 4140 nlen = 2; 4141 break; 4142 } 4143 assert(nlen > 0 && nlen <= i); 4144 i -= nlen; 4145 vp += nlen; 4146 } 4147 } 4148 4149 void nvg__clearPathCache (NVGContext ctx) nothrow @trusted @nogc { 4150 // no need to clear paths, as data is not copied there 4151 //foreach (ref p; ctx.cache.paths[0..ctx.cache.npaths]) p.clear(); 4152 ctx.cache.npoints = 0; 4153 ctx.cache.npaths = 0; 4154 ctx.cache.fillReady = ctx.cache.strokeReady = false; 4155 ctx.cache.clipmode = NVGClipMode.None; 4156 } 4157 4158 NVGpath* nvg__lastPath (NVGContext ctx) nothrow @trusted @nogc { 4159 return (ctx.cache.npaths > 0 ? &ctx.cache.paths[ctx.cache.npaths-1] : null); 4160 } 4161 4162 void nvg__addPath (NVGContext ctx) nothrow @trusted @nogc { 4163 import core.stdc.stdlib : realloc; 4164 import core.stdc.string : memset; 4165 4166 if (ctx.cache.npaths+1 > ctx.cache.cpaths) { 4167 int cpaths = ctx.cache.npaths+1+ctx.cache.cpaths/2; 4168 NVGpath* paths = cast(NVGpath*)realloc(ctx.cache.paths, NVGpath.sizeof*cpaths); 4169 if (paths is null) assert(0, "NanoVega: out of memory"); 4170 ctx.cache.paths = paths; 4171 ctx.cache.cpaths = cpaths; 4172 } 4173 4174 NVGpath* path = &ctx.cache.paths[ctx.cache.npaths++]; 4175 memset(path, 0, NVGpath.sizeof); 4176 path.first = ctx.cache.npoints; 4177 path.mWinding = NVGWinding.CCW; 4178 } 4179 4180 NVGpoint* nvg__lastPoint (NVGContext ctx) nothrow @trusted @nogc { 4181 return (ctx.cache.npoints > 0 ? &ctx.cache.points[ctx.cache.npoints-1] : null); 4182 } 4183 4184 void nvg__addPoint (NVGContext ctx, float x, float y, int flags) nothrow @trusted @nogc { 4185 NVGpath* path = nvg__lastPath(ctx); 4186 if (path is null) return; 4187 4188 if (path.count > 0 && ctx.cache.npoints > 0) { 4189 NVGpoint* pt = nvg__lastPoint(ctx); 4190 if (nvg__ptEquals(pt.x, pt.y, x, y, ctx.distTol)) { 4191 pt.flags |= flags; 4192 return; 4193 } 4194 } 4195 4196 if (ctx.cache.npoints+1 > ctx.cache.cpoints) { 4197 int cpoints = ctx.cache.npoints+1+ctx.cache.cpoints/2; 4198 NVGpoint* points = cast(NVGpoint*)realloc(ctx.cache.points, NVGpoint.sizeof*cpoints); 4199 if (points is null) return; 4200 ctx.cache.points = points; 4201 ctx.cache.cpoints = cpoints; 4202 } 4203 4204 NVGpoint* pt = &ctx.cache.points[ctx.cache.npoints]; 4205 memset(pt, 0, (*pt).sizeof); 4206 pt.x = x; 4207 pt.y = y; 4208 pt.flags = cast(ubyte)flags; 4209 4210 ++ctx.cache.npoints; 4211 ++path.count; 4212 } 4213 4214 void nvg__closePath (NVGContext ctx) nothrow @trusted @nogc { 4215 NVGpath* path = nvg__lastPath(ctx); 4216 if (path is null) return; 4217 path.closed = true; 4218 } 4219 4220 void nvg__pathWinding (NVGContext ctx, NVGWinding winding) nothrow @trusted @nogc { 4221 NVGpath* path = nvg__lastPath(ctx); 4222 if (path is null) return; 4223 path.mWinding = winding; 4224 } 4225 4226 float nvg__getAverageScale() (const scope auto ref NVGMatrix t) /*pure*/ nothrow @trusted @nogc { 4227 immutable float sx = nvg__sqrtf(t.mat.ptr[0]*t.mat.ptr[0]+t.mat.ptr[2]*t.mat.ptr[2]); 4228 immutable float sy = nvg__sqrtf(t.mat.ptr[1]*t.mat.ptr[1]+t.mat.ptr[3]*t.mat.ptr[3]); 4229 return (sx+sy)*0.5f; 4230 } 4231 4232 NVGVertex* nvg__allocTempVerts (NVGContext ctx, int nverts) nothrow @trusted @nogc { 4233 if (nverts > ctx.cache.cverts) { 4234 int cverts = (nverts+0xff)&~0xff; // Round up to prevent allocations when things change just slightly. 4235 NVGVertex* verts = cast(NVGVertex*)realloc(ctx.cache.verts, NVGVertex.sizeof*cverts); 4236 if (verts is null) return null; 4237 ctx.cache.verts = verts; 4238 ctx.cache.cverts = cverts; 4239 } 4240 4241 return ctx.cache.verts; 4242 } 4243 4244 float nvg__triarea2 (float ax, float ay, float bx, float by, float cx, float cy) pure nothrow @safe @nogc { 4245 immutable float abx = bx-ax; 4246 immutable float aby = by-ay; 4247 immutable float acx = cx-ax; 4248 immutable float acy = cy-ay; 4249 return acx*aby-abx*acy; 4250 } 4251 4252 float nvg__polyArea (NVGpoint* pts, int npts) nothrow @trusted @nogc { 4253 float area = 0; 4254 foreach (int i; 2..npts) { 4255 NVGpoint* a = &pts[0]; 4256 NVGpoint* b = &pts[i-1]; 4257 NVGpoint* c = &pts[i]; 4258 area += nvg__triarea2(a.x, a.y, b.x, b.y, c.x, c.y); 4259 } 4260 return area*0.5f; 4261 } 4262 4263 void nvg__polyReverse (NVGpoint* pts, int npts) nothrow @trusted @nogc { 4264 NVGpoint tmp = void; 4265 int i = 0, j = npts-1; 4266 while (i < j) { 4267 tmp = pts[i]; 4268 pts[i] = pts[j]; 4269 pts[j] = tmp; 4270 ++i; 4271 --j; 4272 } 4273 } 4274 4275 void nvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc { 4276 vtx.x = x; 4277 vtx.y = y; 4278 vtx.u = u; 4279 vtx.v = v; 4280 } 4281 4282 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 { 4283 if (level > 10) return; 4284 4285 // check for collinear points, and use AFD tesselator on such curves (it is WAY faster for this case) 4286 /* 4287 if (level == 0 && ctx.tesselatortype == NVGTesselation.Combined) { 4288 static bool collinear (in float v0x, in float v0y, in float v1x, in float v1y, in float v2x, in float v2y) nothrow @trusted @nogc { 4289 immutable float cz = (v1x-v0x)*(v2y-v0y)-(v2x-v0x)*(v1y-v0y); 4290 return (nvg__absf(cz*cz) <= 0.01f); // arbitrary number, seems to work ok with NanoSVG output 4291 } 4292 if (collinear(x1, y1, x2, y2, x3, y3) && collinear(x2, y2, x3, y3, x3, y4)) { 4293 //{ import core.stdc.stdio; printf("AFD fallback!\n"); } 4294 ctx.nvg__tesselateBezierAFD(x1, y1, x2, y2, x3, y3, x4, y4, type); 4295 return; 4296 } 4297 } 4298 */ 4299 4300 immutable float x12 = (x1+x2)*0.5f; 4301 immutable float y12 = (y1+y2)*0.5f; 4302 immutable float x23 = (x2+x3)*0.5f; 4303 immutable float y23 = (y2+y3)*0.5f; 4304 immutable float x34 = (x3+x4)*0.5f; 4305 immutable float y34 = (y3+y4)*0.5f; 4306 immutable float x123 = (x12+x23)*0.5f; 4307 immutable float y123 = (y12+y23)*0.5f; 4308 4309 immutable float dx = x4-x1; 4310 immutable float dy = y4-y1; 4311 immutable float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx)); 4312 immutable float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx)); 4313 4314 if ((d2+d3)*(d2+d3) < ctx.tessTol*(dx*dx+dy*dy)) { 4315 nvg__addPoint(ctx, x4, y4, type); 4316 return; 4317 } 4318 4319 immutable float x234 = (x23+x34)*0.5f; 4320 immutable float y234 = (y23+y34)*0.5f; 4321 immutable float x1234 = (x123+x234)*0.5f; 4322 immutable float y1234 = (y123+y234)*0.5f; 4323 4324 // "taxicab" / "manhattan" check for flat curves 4325 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) { 4326 nvg__addPoint(ctx, x1234, y1234, type); 4327 return; 4328 } 4329 4330 nvg__tesselateBezier(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0); 4331 nvg__tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type); 4332 } 4333 4334 // based on the ideas and code of Maxim Shemanarev. Rest in Peace, bro! 4335 // see http://www.antigrain.com/research/adaptive_bezier/index.html 4336 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 { 4337 enum CollinearEPS = 0.00000001f; // 0.00001f; 4338 enum AngleTolEPS = 0.01f; 4339 4340 static float distSquared (in float x1, in float y1, in float x2, in float y2) pure nothrow @safe @nogc { 4341 pragma(inline, true); 4342 immutable float dx = x2-x1; 4343 immutable float dy = y2-y1; 4344 return dx*dx+dy*dy; 4345 } 4346 4347 if (level == 0) { 4348 nvg__addPoint(ctx, x1, y1, 0); 4349 nvg__tesselateBezierMcSeem(ctx, x1, y1, x2, y2, x3, y3, x4, y4, 1, type); 4350 nvg__addPoint(ctx, x4, y4, type); 4351 return; 4352 } 4353 4354 if (level >= 32) return; // recurse limit; practically, it should be never reached, but... 4355 4356 // calculate all the mid-points of the line segments 4357 immutable float x12 = (x1+x2)*0.5f; 4358 immutable float y12 = (y1+y2)*0.5f; 4359 immutable float x23 = (x2+x3)*0.5f; 4360 immutable float y23 = (y2+y3)*0.5f; 4361 immutable float x34 = (x3+x4)*0.5f; 4362 immutable float y34 = (y3+y4)*0.5f; 4363 immutable float x123 = (x12+x23)*0.5f; 4364 immutable float y123 = (y12+y23)*0.5f; 4365 immutable float x234 = (x23+x34)*0.5f; 4366 immutable float y234 = (y23+y34)*0.5f; 4367 immutable float x1234 = (x123+x234)*0.5f; 4368 immutable float y1234 = (y123+y234)*0.5f; 4369 4370 // try to approximate the full cubic curve by a single straight line 4371 immutable float dx = x4-x1; 4372 immutable float dy = y4-y1; 4373 4374 float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx)); 4375 float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx)); 4376 //immutable float da1, da2, k; 4377 4378 final switch ((cast(int)(d2 > CollinearEPS)<<1)+cast(int)(d3 > CollinearEPS)) { 4379 case 0: 4380 // all collinear or p1 == p4 4381 float k = dx*dx+dy*dy; 4382 if (k == 0) { 4383 d2 = distSquared(x1, y1, x2, y2); 4384 d3 = distSquared(x4, y4, x3, y3); 4385 } else { 4386 k = 1.0f/k; 4387 float da1 = x2-x1; 4388 float da2 = y2-y1; 4389 d2 = k*(da1*dx+da2*dy); 4390 da1 = x3-x1; 4391 da2 = y3-y1; 4392 d3 = k*(da1*dx+da2*dy); 4393 if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) { 4394 // Simple collinear case, 1---2---3---4 4395 // We can leave just two endpoints 4396 return; 4397 } 4398 if (d2 <= 0) d2 = distSquared(x2, y2, x1, y1); 4399 else if (d2 >= 1) d2 = distSquared(x2, y2, x4, y4); 4400 else d2 = distSquared(x2, y2, x1+d2*dx, y1+d2*dy); 4401 4402 if (d3 <= 0) d3 = distSquared(x3, y3, x1, y1); 4403 else if (d3 >= 1) d3 = distSquared(x3, y3, x4, y4); 4404 else d3 = distSquared(x3, y3, x1+d3*dx, y1+d3*dy); 4405 } 4406 if (d2 > d3) { 4407 if (d2 < ctx.tessTol) { 4408 nvg__addPoint(ctx, x2, y2, type); 4409 return; 4410 } 4411 } if (d3 < ctx.tessTol) { 4412 nvg__addPoint(ctx, x3, y3, type); 4413 return; 4414 } 4415 break; 4416 case 1: 4417 // p1,p2,p4 are collinear, p3 is significant 4418 if (d3*d3 <= ctx.tessTol*(dx*dx+dy*dy)) { 4419 if (ctx.angleTol < AngleTolEPS) { 4420 nvg__addPoint(ctx, x23, y23, type); 4421 return; 4422 } else { 4423 // angle condition 4424 float da1 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-nvg__atan2f(y3-y2, x3-x2)); 4425 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4426 if (da1 < ctx.angleTol) { 4427 nvg__addPoint(ctx, x2, y2, type); 4428 nvg__addPoint(ctx, x3, y3, type); 4429 return; 4430 } 4431 if (ctx.cuspLimit != 0.0) { 4432 if (da1 > ctx.cuspLimit) { 4433 nvg__addPoint(ctx, x3, y3, type); 4434 return; 4435 } 4436 } 4437 } 4438 } 4439 break; 4440 case 2: 4441 // p1,p3,p4 are collinear, p2 is significant 4442 if (d2*d2 <= ctx.tessTol*(dx*dx+dy*dy)) { 4443 if (ctx.angleTol < AngleTolEPS) { 4444 nvg__addPoint(ctx, x23, y23, type); 4445 return; 4446 } else { 4447 // angle condition 4448 float da1 = nvg__absf(nvg__atan2f(y3-y2, x3-x2)-nvg__atan2f(y2-y1, x2-x1)); 4449 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4450 if (da1 < ctx.angleTol) { 4451 nvg__addPoint(ctx, x2, y2, type); 4452 nvg__addPoint(ctx, x3, y3, type); 4453 return; 4454 } 4455 if (ctx.cuspLimit != 0.0) { 4456 if (da1 > ctx.cuspLimit) { 4457 nvg__addPoint(ctx, x2, y2, type); 4458 return; 4459 } 4460 } 4461 } 4462 } 4463 break; 4464 case 3: 4465 // regular case 4466 if ((d2+d3)*(d2+d3) <= ctx.tessTol*(dx*dx+dy*dy)) { 4467 // if the curvature doesn't exceed the distance tolerance value, we tend to finish subdivisions 4468 if (ctx.angleTol < AngleTolEPS) { 4469 nvg__addPoint(ctx, x23, y23, type); 4470 return; 4471 } else { 4472 // angle and cusp condition 4473 immutable float k = nvg__atan2f(y3-y2, x3-x2); 4474 float da1 = nvg__absf(k-nvg__atan2f(y2-y1, x2-x1)); 4475 float da2 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-k); 4476 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4477 if (da2 >= NVG_PI) da2 = 2*NVG_PI-da2; 4478 if (da1+da2 < ctx.angleTol) { 4479 // finally we can stop the recursion 4480 nvg__addPoint(ctx, x23, y23, type); 4481 return; 4482 } 4483 if (ctx.cuspLimit != 0.0) { 4484 if (da1 > ctx.cuspLimit) { 4485 nvg__addPoint(ctx, x2, y2, type); 4486 return; 4487 } 4488 if (da2 > ctx.cuspLimit) { 4489 nvg__addPoint(ctx, x3, y3, type); 4490 return; 4491 } 4492 } 4493 } 4494 } 4495 break; 4496 } 4497 4498 // continue subdivision 4499 nvg__tesselateBezierMcSeem(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0); 4500 nvg__tesselateBezierMcSeem(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type); 4501 } 4502 4503 4504 // Adaptive forward differencing for bezier tesselation. 4505 // See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt. 4506 // "Adaptive forward differencing for rendering curves and surfaces." 4507 // ACM SIGGRAPH Computer Graphics. Vol. 21. No. 4. ACM, 1987. 4508 // original code by Taylor Holliday <taylor@audulus.com> 4509 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 { 4510 enum AFD_ONE = (1<<10); 4511 4512 // power basis 4513 immutable float ax = -x1+3*x2-3*x3+x4; 4514 immutable float ay = -y1+3*y2-3*y3+y4; 4515 immutable float bx = 3*x1-6*x2+3*x3; 4516 immutable float by = 3*y1-6*y2+3*y3; 4517 immutable float cx = -3*x1+3*x2; 4518 immutable float cy = -3*y1+3*y2; 4519 4520 // Transform to forward difference basis (stepsize 1) 4521 float px = x1; 4522 float py = y1; 4523 float dx = ax+bx+cx; 4524 float dy = ay+by+cy; 4525 float ddx = 6*ax+2*bx; 4526 float ddy = 6*ay+2*by; 4527 float dddx = 6*ax; 4528 float dddy = 6*ay; 4529 4530 //printf("dx: %f, dy: %f\n", dx, dy); 4531 //printf("ddx: %f, ddy: %f\n", ddx, ddy); 4532 //printf("dddx: %f, dddy: %f\n", dddx, dddy); 4533 4534 int t = 0; 4535 int dt = AFD_ONE; 4536 4537 immutable float tol = ctx.tessTol*4; 4538 4539 while (t < AFD_ONE) { 4540 // Flatness measure. 4541 float d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4542 4543 // printf("d: %f, th: %f\n", d, th); 4544 4545 // Go to higher resolution if we're moving a lot or overshooting the end. 4546 while ((d > tol && dt > 1) || (t+dt > AFD_ONE)) { 4547 // printf("up\n"); 4548 4549 // Apply L to the curve. Increase curve resolution. 4550 dx = 0.5f*dx-(1.0f/8.0f)*ddx+(1.0f/16.0f)*dddx; 4551 dy = 0.5f*dy-(1.0f/8.0f)*ddy+(1.0f/16.0f)*dddy; 4552 ddx = (1.0f/4.0f)*ddx-(1.0f/8.0f)*dddx; 4553 ddy = (1.0f/4.0f)*ddy-(1.0f/8.0f)*dddy; 4554 dddx = (1.0f/8.0f)*dddx; 4555 dddy = (1.0f/8.0f)*dddy; 4556 4557 // Half the stepsize. 4558 dt >>= 1; 4559 4560 // Recompute d 4561 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4562 } 4563 4564 // Go to lower resolution if we're really flat 4565 // and we aren't going to overshoot the end. 4566 // XXX: tol/32 is just a guess for when we are too flat. 4567 while ((d > 0 && d < tol/32.0f && dt < AFD_ONE) && (t+2*dt <= AFD_ONE)) { 4568 // printf("down\n"); 4569 4570 // Apply L^(-1) to the curve. Decrease curve resolution. 4571 dx = 2*dx+ddx; 4572 dy = 2*dy+ddy; 4573 ddx = 4*ddx+4*dddx; 4574 ddy = 4*ddy+4*dddy; 4575 dddx = 8*dddx; 4576 dddy = 8*dddy; 4577 4578 // Double the stepsize. 4579 dt <<= 1; 4580 4581 // Recompute d 4582 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4583 } 4584 4585 // Forward differencing. 4586 px += dx; 4587 py += dy; 4588 dx += ddx; 4589 dy += ddy; 4590 ddx += dddx; 4591 ddy += dddy; 4592 4593 // Output a point. 4594 nvg__addPoint(ctx, px, py, (t > 0 ? type : 0)); 4595 4596 // Advance along the curve. 4597 t += dt; 4598 4599 // Ensure we don't overshoot. 4600 assert(t <= AFD_ONE); 4601 } 4602 } 4603 4604 4605 void nvg__dashLastPath (NVGContext ctx) nothrow @trusted @nogc { 4606 import core.stdc.stdlib : realloc; 4607 import core.stdc.string : memcpy; 4608 4609 NVGpathCache* cache = ctx.cache; 4610 if (cache.npaths == 0) return; 4611 4612 NVGpath* path = nvg__lastPath(ctx); 4613 if (path is null) return; 4614 4615 NVGstate* state = nvg__getState(ctx); 4616 if (!state.dasherActive) return; 4617 4618 static NVGpoint* pts = null; 4619 static uint ptsCount = 0; 4620 static uint ptsSize = 0; 4621 4622 if (path.count < 2) return; // just in case 4623 4624 // copy path points (reserve one point for closed pathes) 4625 if (ptsSize < path.count+1) { 4626 ptsSize = cast(uint)(path.count+1); 4627 pts = cast(NVGpoint*)realloc(pts, ptsSize*NVGpoint.sizeof); 4628 if (pts is null) assert(0, "NanoVega: out of memory"); 4629 } 4630 ptsCount = cast(uint)path.count; 4631 memcpy(pts, &cache.points[path.first], ptsCount*NVGpoint.sizeof); 4632 // add closing point for closed pathes 4633 if (path.closed && !nvg__ptEquals(pts[0].x, pts[0].y, pts[ptsCount-1].x, pts[ptsCount-1].y, ctx.distTol)) { 4634 pts[ptsCount++] = pts[0]; 4635 } 4636 4637 // remove last path (with its points) 4638 --cache.npaths; 4639 cache.npoints -= path.count; 4640 4641 // add stroked pathes 4642 const(float)* dashes = state.dashes.ptr; 4643 immutable uint dashCount = state.dashCount; 4644 float currDashStart = 0; 4645 uint currDashIdx = 0; 4646 immutable bool firstIsGap = state.firstDashIsGap; 4647 4648 // calculate lengthes 4649 { 4650 NVGpoint* v1 = &pts[0]; 4651 NVGpoint* v2 = &pts[1]; 4652 foreach (immutable _; 0..ptsCount) { 4653 float dx = v2.x-v1.x; 4654 float dy = v2.y-v1.y; 4655 v1.len = nvg__normalize(&dx, &dy); 4656 v1 = v2++; 4657 } 4658 } 4659 4660 void calcDashStart (float ds) { 4661 if (ds < 0) { 4662 ds = ds%state.totalDashLen; 4663 while (ds < 0) ds += state.totalDashLen; 4664 } 4665 currDashIdx = 0; 4666 currDashStart = 0; 4667 while (ds > 0) { 4668 if (ds > dashes[currDashIdx]) { 4669 ds -= dashes[currDashIdx]; 4670 ++currDashIdx; 4671 currDashStart = 0; 4672 if (currDashIdx >= dashCount) currDashIdx = 0; 4673 } else { 4674 currDashStart = ds; 4675 ds = 0; 4676 } 4677 } 4678 } 4679 4680 calcDashStart(state.dashStart); 4681 4682 uint srcPointIdx = 1; 4683 const(NVGpoint)* v1 = &pts[0]; 4684 const(NVGpoint)* v2 = &pts[1]; 4685 float currRest = v1.len; 4686 nvg__addPath(ctx); 4687 nvg__addPoint(ctx, v1.x, v1.y, PointFlag.Corner); 4688 4689 void fixLastPoint () { 4690 auto lpt = nvg__lastPath(ctx); 4691 if (lpt !is null && lpt.count > 0) { 4692 // fix last point 4693 if (auto lps = nvg__lastPoint(ctx)) lps.flags = PointFlag.Corner; 4694 // fix first point 4695 NVGpathCache* cache = ctx.cache; 4696 cache.points[lpt.first].flags = PointFlag.Corner; 4697 } 4698 } 4699 4700 for (;;) { 4701 immutable float dlen = dashes[currDashIdx]; 4702 if (dlen == 0) { 4703 ++currDashIdx; 4704 if (currDashIdx >= dashCount) currDashIdx = 0; 4705 continue; 4706 } 4707 immutable float dashRest = dlen-currDashStart; 4708 if ((currDashIdx&1) != firstIsGap) { 4709 // this is "moveto" command, so create new path 4710 fixLastPoint(); 4711 nvg__addPath(ctx); 4712 } 4713 if (currRest > dashRest) { 4714 currRest -= dashRest; 4715 ++currDashIdx; 4716 if (currDashIdx >= dashCount) currDashIdx = 0; 4717 currDashStart = 0; 4718 nvg__addPoint(ctx, 4719 v2.x-(v2.x-v1.x)*currRest/v1.len, 4720 v2.y-(v2.y-v1.y)*currRest/v1.len, 4721 PointFlag.Corner 4722 ); 4723 } else { 4724 currDashStart += currRest; 4725 nvg__addPoint(ctx, v2.x, v2.y, v1.flags); //k8:fix flags here? 4726 ++srcPointIdx; 4727 v1 = v2; 4728 currRest = v1.len; 4729 if (srcPointIdx >= ptsCount) break; 4730 v2 = &pts[srcPointIdx]; 4731 } 4732 } 4733 fixLastPoint(); 4734 } 4735 4736 4737 version(nanovg_bench_flatten) import iv.timer : Timer; 4738 4739 void nvg__flattenPaths(bool asStroke) (NVGContext ctx) nothrow @trusted @nogc { 4740 version(nanovg_bench_flatten) { 4741 Timer timer; 4742 char[128] tmbuf; 4743 int bzcount; 4744 } 4745 NVGpathCache* cache = ctx.cache; 4746 NVGstate* state = nvg__getState(ctx); 4747 4748 // check if we already did flattening 4749 static if (asStroke) { 4750 if (state.dashCount >= 2) { 4751 if (cache.npaths > 0 && state.lastFlattenDashCount == state.dashCount) return; // already flattened 4752 state.dasherActive = true; 4753 state.lastFlattenDashCount = state.dashCount; 4754 } else { 4755 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened 4756 state.dasherActive = false; 4757 state.lastFlattenDashCount = 0; 4758 } 4759 } else { 4760 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened 4761 state.lastFlattenDashCount = 0; // so next stroke flattening will redo it 4762 state.dasherActive = false; 4763 } 4764 4765 // clear path cache 4766 cache.npaths = 0; 4767 cache.npoints = 0; 4768 4769 // flatten 4770 version(nanovg_bench_flatten) timer.restart(); 4771 int i = 0; 4772 while (i < ctx.ncommands) { 4773 final switch (cast(Command)ctx.commands[i]) { 4774 case Command.MoveTo: 4775 //assert(i+3 <= ctx.ncommands); 4776 static if (asStroke) { 4777 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx); 4778 } 4779 nvg__addPath(ctx); 4780 const p = &ctx.commands[i+1]; 4781 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner); 4782 i += 3; 4783 break; 4784 case Command.LineTo: 4785 //assert(i+3 <= ctx.ncommands); 4786 const p = &ctx.commands[i+1]; 4787 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner); 4788 i += 3; 4789 break; 4790 case Command.BezierTo: 4791 //assert(i+7 <= ctx.ncommands); 4792 const last = nvg__lastPoint(ctx); 4793 if (last !is null) { 4794 const cp1 = &ctx.commands[i+1]; 4795 const cp2 = &ctx.commands[i+3]; 4796 const p = &ctx.commands[i+5]; 4797 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) { 4798 nvg__tesselateBezier(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner); 4799 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) { 4800 nvg__tesselateBezierMcSeem(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner); 4801 } else { 4802 nvg__tesselateBezierAFD(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], PointFlag.Corner); 4803 } 4804 version(nanovg_bench_flatten) ++bzcount; 4805 } 4806 i += 7; 4807 break; 4808 case Command.Close: 4809 //assert(i+1 <= ctx.ncommands); 4810 nvg__closePath(ctx); 4811 i += 1; 4812 break; 4813 case Command.Winding: 4814 //assert(i+2 <= ctx.ncommands); 4815 nvg__pathWinding(ctx, cast(NVGWinding)ctx.commands[i+1]); 4816 i += 2; 4817 break; 4818 } 4819 } 4820 static if (asStroke) { 4821 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx); 4822 } 4823 version(nanovg_bench_flatten) {{ 4824 timer.stop(); 4825 auto xb = timer.toBuffer(tmbuf[]); 4826 import core.stdc.stdio : printf; 4827 printf("flattening time: [%.*s] (%d beziers)\n", cast(uint)xb.length, xb.ptr, bzcount); 4828 }} 4829 4830 cache.bounds.ptr[0] = cache.bounds.ptr[1] = float.max; 4831 cache.bounds.ptr[2] = cache.bounds.ptr[3] = -float.max; 4832 4833 // calculate the direction and length of line segments 4834 version(nanovg_bench_flatten) timer.restart(); 4835 foreach (int j; 0..cache.npaths) { 4836 NVGpath* path = &cache.paths[j]; 4837 NVGpoint* pts = &cache.points[path.first]; 4838 4839 // if the first and last points are the same, remove the last, mark as closed path 4840 NVGpoint* p0 = &pts[path.count-1]; 4841 NVGpoint* p1 = &pts[0]; 4842 if (nvg__ptEquals(p0.x, p0.y, p1.x, p1.y, ctx.distTol)) { 4843 --path.count; 4844 p0 = &pts[path.count-1]; 4845 path.closed = true; 4846 } 4847 4848 // enforce winding 4849 if (path.count > 2) { 4850 immutable float area = nvg__polyArea(pts, path.count); 4851 if (path.mWinding == NVGWinding.CCW && area < 0.0f) nvg__polyReverse(pts, path.count); 4852 if (path.mWinding == NVGWinding.CW && area > 0.0f) nvg__polyReverse(pts, path.count); 4853 } 4854 4855 foreach (immutable _; 0..path.count) { 4856 // calculate segment direction and length 4857 p0.dx = p1.x-p0.x; 4858 p0.dy = p1.y-p0.y; 4859 p0.len = nvg__normalize(&p0.dx, &p0.dy); 4860 // update bounds 4861 cache.bounds.ptr[0] = nvg__min(cache.bounds.ptr[0], p0.x); 4862 cache.bounds.ptr[1] = nvg__min(cache.bounds.ptr[1], p0.y); 4863 cache.bounds.ptr[2] = nvg__max(cache.bounds.ptr[2], p0.x); 4864 cache.bounds.ptr[3] = nvg__max(cache.bounds.ptr[3], p0.y); 4865 // advance 4866 p0 = p1++; 4867 } 4868 } 4869 version(nanovg_bench_flatten) {{ 4870 timer.stop(); 4871 auto xb = timer.toBuffer(tmbuf[]); 4872 import core.stdc.stdio : printf; 4873 printf("segment calculation time: [%.*s]\n", cast(uint)xb.length, xb.ptr); 4874 }} 4875 } 4876 4877 int nvg__curveDivs (float r, float arc, float tol) nothrow @trusted @nogc { 4878 immutable float da = nvg__acosf(r/(r+tol))*2.0f; 4879 return nvg__max(2, cast(int)nvg__ceilf(arc/da)); 4880 } 4881 4882 void nvg__chooseBevel (int bevel, NVGpoint* p0, NVGpoint* p1, float w, float* x0, float* y0, float* x1, float* y1) nothrow @trusted @nogc { 4883 if (bevel) { 4884 *x0 = p1.x+p0.dy*w; 4885 *y0 = p1.y-p0.dx*w; 4886 *x1 = p1.x+p1.dy*w; 4887 *y1 = p1.y-p1.dx*w; 4888 } else { 4889 *x0 = p1.x+p1.dmx*w; 4890 *y0 = p1.y+p1.dmy*w; 4891 *x1 = p1.x+p1.dmx*w; 4892 *y1 = p1.y+p1.dmy*w; 4893 } 4894 } 4895 4896 NVGVertex* nvg__roundJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, int ncap, float fringe) nothrow @trusted @nogc { 4897 float dlx0 = p0.dy; 4898 float dly0 = -p0.dx; 4899 float dlx1 = p1.dy; 4900 float dly1 = -p1.dx; 4901 //NVG_NOTUSED(fringe); 4902 4903 if (p1.flags&PointFlag.Left) { 4904 float lx0 = void, ly0 = void, lx1 = void, ly1 = void; 4905 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1); 4906 immutable float a0 = nvg__atan2f(-dly0, -dlx0); 4907 float a1 = nvg__atan2f(-dly1, -dlx1); 4908 if (a1 > a0) a1 -= NVG_PI*2; 4909 4910 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4911 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4912 4913 int n = nvg__clamp(cast(int)nvg__ceilf(((a0-a1)/NVG_PI)*ncap), 2, ncap); 4914 for (int i = 0; i < n; ++i) { 4915 float u = i/cast(float)(n-1); 4916 float a = a0+u*(a1-a0); 4917 float rx = p1.x+nvg__cosf(a)*rw; 4918 float ry = p1.y+nvg__sinf(a)*rw; 4919 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4920 nvg__vset(dst, rx, ry, ru, 1); ++dst; 4921 } 4922 4923 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4924 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4925 4926 } else { 4927 float rx0 = void, ry0 = void, rx1 = void, ry1 = void; 4928 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1); 4929 immutable float a0 = nvg__atan2f(dly0, dlx0); 4930 float a1 = nvg__atan2f(dly1, dlx1); 4931 if (a1 < a0) a1 += NVG_PI*2; 4932 4933 nvg__vset(dst, p1.x+dlx0*rw, p1.y+dly0*rw, lu, 1); ++dst; 4934 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4935 4936 int n = nvg__clamp(cast(int)nvg__ceilf(((a1-a0)/NVG_PI)*ncap), 2, ncap); 4937 for (int i = 0; i < n; i++) { 4938 float u = i/cast(float)(n-1); 4939 float a = a0+u*(a1-a0); 4940 float lx = p1.x+nvg__cosf(a)*lw; 4941 float ly = p1.y+nvg__sinf(a)*lw; 4942 nvg__vset(dst, lx, ly, lu, 1); ++dst; 4943 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4944 } 4945 4946 nvg__vset(dst, p1.x+dlx1*rw, p1.y+dly1*rw, lu, 1); ++dst; 4947 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 4948 4949 } 4950 return dst; 4951 } 4952 4953 NVGVertex* nvg__bevelJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, float fringe) nothrow @trusted @nogc { 4954 float rx0, ry0, rx1, ry1; 4955 float lx0, ly0, lx1, ly1; 4956 float dlx0 = p0.dy; 4957 float dly0 = -p0.dx; 4958 float dlx1 = p1.dy; 4959 float dly1 = -p1.dx; 4960 //NVG_NOTUSED(fringe); 4961 4962 if (p1.flags&PointFlag.Left) { 4963 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1); 4964 4965 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4966 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4967 4968 if (p1.flags&PointFlag.Bevel) { 4969 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4970 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4971 4972 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4973 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4974 } else { 4975 rx0 = p1.x-p1.dmx*rw; 4976 ry0 = p1.y-p1.dmy*rw; 4977 4978 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4979 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4980 4981 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4982 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4983 4984 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4985 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4986 } 4987 4988 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4989 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4990 4991 } else { 4992 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1); 4993 4994 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 4995 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4996 4997 if (p1.flags&PointFlag.Bevel) { 4998 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 4999 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 5000 5001 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 5002 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 5003 } else { 5004 lx0 = p1.x+p1.dmx*lw; 5005 ly0 = p1.y+p1.dmy*lw; 5006 5007 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 5008 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 5009 5010 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 5011 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 5012 5013 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 5014 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 5015 } 5016 5017 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 5018 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 5019 } 5020 5021 return dst; 5022 } 5023 5024 NVGVertex* nvg__buttCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc { 5025 immutable float px = p.x-dx*d; 5026 immutable float py = p.y-dy*d; 5027 immutable float dlx = dy; 5028 immutable float dly = -dx; 5029 nvg__vset(dst, px+dlx*w-dx*aa, py+dly*w-dy*aa, 0, 0); ++dst; 5030 nvg__vset(dst, px-dlx*w-dx*aa, py-dly*w-dy*aa, 1, 0); ++dst; 5031 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5032 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5033 return dst; 5034 } 5035 5036 NVGVertex* nvg__buttCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc { 5037 immutable float px = p.x+dx*d; 5038 immutable float py = p.y+dy*d; 5039 immutable float dlx = dy; 5040 immutable float dly = -dx; 5041 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5042 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5043 nvg__vset(dst, px+dlx*w+dx*aa, py+dly*w+dy*aa, 0, 0); ++dst; 5044 nvg__vset(dst, px-dlx*w+dx*aa, py-dly*w+dy*aa, 1, 0); ++dst; 5045 return dst; 5046 } 5047 5048 NVGVertex* nvg__roundCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc { 5049 immutable float px = p.x; 5050 immutable float py = p.y; 5051 immutable float dlx = dy; 5052 immutable float dly = -dx; 5053 //NVG_NOTUSED(aa); 5054 immutable float ncpf = cast(float)(ncap-1); 5055 foreach (int i; 0..ncap) { 5056 float a = i/*/cast(float)(ncap-1)*//ncpf*NVG_PI; 5057 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w; 5058 nvg__vset(dst, px-dlx*ax-dx*ay, py-dly*ax-dy*ay, 0, 1); ++dst; 5059 nvg__vset(dst, px, py, 0.5f, 1); ++dst; 5060 } 5061 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5062 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5063 return dst; 5064 } 5065 5066 NVGVertex* nvg__roundCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc { 5067 immutable float px = p.x; 5068 immutable float py = p.y; 5069 immutable float dlx = dy; 5070 immutable float dly = -dx; 5071 //NVG_NOTUSED(aa); 5072 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5073 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5074 immutable float ncpf = cast(float)(ncap-1); 5075 foreach (int i; 0..ncap) { 5076 float a = i/*cast(float)(ncap-1)*//ncpf*NVG_PI; 5077 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w; 5078 nvg__vset(dst, px, py, 0.5f, 1); ++dst; 5079 nvg__vset(dst, px-dlx*ax+dx*ay, py-dly*ax+dy*ay, 0, 1); ++dst; 5080 } 5081 return dst; 5082 } 5083 5084 void nvg__calculateJoins (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5085 NVGpathCache* cache = ctx.cache; 5086 float iw = 0.0f; 5087 5088 if (w > 0.0f) iw = 1.0f/w; 5089 5090 // Calculate which joins needs extra vertices to append, and gather vertex count. 5091 foreach (int i; 0..cache.npaths) { 5092 NVGpath* path = &cache.paths[i]; 5093 NVGpoint* pts = &cache.points[path.first]; 5094 NVGpoint* p0 = &pts[path.count-1]; 5095 NVGpoint* p1 = &pts[0]; 5096 int nleft = 0; 5097 5098 path.nbevel = 0; 5099 5100 foreach (int j; 0..path.count) { 5101 //float dlx0, dly0, dlx1, dly1, dmr2, cross, limit; 5102 immutable float dlx0 = p0.dy; 5103 immutable float dly0 = -p0.dx; 5104 immutable float dlx1 = p1.dy; 5105 immutable float dly1 = -p1.dx; 5106 // Calculate extrusions 5107 p1.dmx = (dlx0+dlx1)*0.5f; 5108 p1.dmy = (dly0+dly1)*0.5f; 5109 immutable float dmr2 = p1.dmx*p1.dmx+p1.dmy*p1.dmy; 5110 if (dmr2 > 0.000001f) { 5111 float scale = 1.0f/dmr2; 5112 if (scale > 600.0f) scale = 600.0f; 5113 p1.dmx *= scale; 5114 p1.dmy *= scale; 5115 } 5116 5117 // Clear flags, but keep the corner. 5118 p1.flags = (p1.flags&PointFlag.Corner) ? PointFlag.Corner : 0; 5119 5120 // Keep track of left turns. 5121 immutable float cross = p1.dx*p0.dy-p0.dx*p1.dy; 5122 if (cross > 0.0f) { 5123 nleft++; 5124 p1.flags |= PointFlag.Left; 5125 } 5126 5127 // Calculate if we should use bevel or miter for inner join. 5128 immutable float limit = nvg__max(1.01f, nvg__min(p0.len, p1.len)*iw); 5129 if ((dmr2*limit*limit) < 1.0f) p1.flags |= PointFlag.InnerBevelPR; 5130 5131 // Check to see if the corner needs to be beveled. 5132 if (p1.flags&PointFlag.Corner) { 5133 if ((dmr2*miterLimit*miterLimit) < 1.0f || lineJoin == NVGLineCap.Bevel || lineJoin == NVGLineCap.Round) { 5134 p1.flags |= PointFlag.Bevel; 5135 } 5136 } 5137 5138 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) path.nbevel++; 5139 5140 p0 = p1++; 5141 } 5142 5143 path.convex = (nleft == path.count); 5144 } 5145 } 5146 5147 void nvg__expandStroke (NVGContext ctx, float w, int lineCap, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5148 NVGpathCache* cache = ctx.cache; 5149 immutable float aa = ctx.fringeWidth; 5150 int ncap = nvg__curveDivs(w, NVG_PI, ctx.tessTol); // Calculate divisions per half circle. 5151 5152 nvg__calculateJoins(ctx, w, lineJoin, miterLimit); 5153 5154 // Calculate max vertex usage. 5155 int cverts = 0; 5156 foreach (int i; 0..cache.npaths) { 5157 NVGpath* path = &cache.paths[i]; 5158 immutable bool loop = path.closed; 5159 if (lineJoin == NVGLineCap.Round) { 5160 cverts += (path.count+path.nbevel*(ncap+2)+1)*2; // plus one for loop 5161 } else { 5162 cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop 5163 } 5164 if (!loop) { 5165 // space for caps 5166 if (lineCap == NVGLineCap.Round) { 5167 cverts += (ncap*2+2)*2; 5168 } else { 5169 cverts += (3+3)*2; 5170 } 5171 } 5172 } 5173 5174 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts); 5175 if (verts is null) return; 5176 5177 foreach (int i; 0..cache.npaths) { 5178 NVGpath* path = &cache.paths[i]; 5179 NVGpoint* pts = &cache.points[path.first]; 5180 NVGpoint* p0; 5181 NVGpoint* p1; 5182 int s, e; 5183 5184 path.fill = null; 5185 path.nfill = 0; 5186 5187 // Calculate fringe or stroke 5188 immutable bool loop = path.closed; 5189 NVGVertex* dst = verts; 5190 path.stroke = dst; 5191 5192 if (loop) { 5193 // Looping 5194 p0 = &pts[path.count-1]; 5195 p1 = &pts[0]; 5196 s = 0; 5197 e = path.count; 5198 } else { 5199 // Add cap 5200 p0 = &pts[0]; 5201 p1 = &pts[1]; 5202 s = 1; 5203 e = path.count-1; 5204 } 5205 5206 if (!loop) { 5207 // Add cap 5208 float dx = p1.x-p0.x; 5209 float dy = p1.y-p0.y; 5210 nvg__normalize(&dx, &dy); 5211 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa); 5212 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa); 5213 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa); 5214 } 5215 5216 foreach (int j; s..e) { 5217 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) { 5218 if (lineJoin == NVGLineCap.Round) { 5219 dst = nvg__roundJoin(dst, p0, p1, w, w, 0, 1, ncap, aa); 5220 } else { 5221 dst = nvg__bevelJoin(dst, p0, p1, w, w, 0, 1, aa); 5222 } 5223 } else { 5224 nvg__vset(dst, p1.x+(p1.dmx*w), p1.y+(p1.dmy*w), 0, 1); ++dst; 5225 nvg__vset(dst, p1.x-(p1.dmx*w), p1.y-(p1.dmy*w), 1, 1); ++dst; 5226 } 5227 p0 = p1++; 5228 } 5229 5230 if (loop) { 5231 // Loop it 5232 nvg__vset(dst, verts[0].x, verts[0].y, 0, 1); ++dst; 5233 nvg__vset(dst, verts[1].x, verts[1].y, 1, 1); ++dst; 5234 } else { 5235 // Add cap 5236 float dx = p1.x-p0.x; 5237 float dy = p1.y-p0.y; 5238 nvg__normalize(&dx, &dy); 5239 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa); 5240 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa); 5241 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa); 5242 } 5243 5244 path.nstroke = cast(int)(dst-verts); 5245 5246 verts = dst; 5247 } 5248 } 5249 5250 void nvg__expandFill (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5251 NVGpathCache* cache = ctx.cache; 5252 immutable float aa = ctx.fringeWidth; 5253 bool fringe = (w > 0.0f); 5254 5255 nvg__calculateJoins(ctx, w, lineJoin, miterLimit); 5256 5257 // Calculate max vertex usage. 5258 int cverts = 0; 5259 foreach (int i; 0..cache.npaths) { 5260 NVGpath* path = &cache.paths[i]; 5261 cverts += path.count+path.nbevel+1; 5262 if (fringe) cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop 5263 } 5264 5265 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts); 5266 if (verts is null) return; 5267 5268 bool convex = (cache.npaths == 1 && cache.paths[0].convex); 5269 5270 foreach (int i; 0..cache.npaths) { 5271 NVGpath* path = &cache.paths[i]; 5272 NVGpoint* pts = &cache.points[path.first]; 5273 5274 // Calculate shape vertices. 5275 immutable float woff = 0.5f*aa; 5276 NVGVertex* dst = verts; 5277 path.fill = dst; 5278 5279 if (fringe) { 5280 // Looping 5281 NVGpoint* p0 = &pts[path.count-1]; 5282 NVGpoint* p1 = &pts[0]; 5283 foreach (int j; 0..path.count) { 5284 if (p1.flags&PointFlag.Bevel) { 5285 immutable float dlx0 = p0.dy; 5286 immutable float dly0 = -p0.dx; 5287 immutable float dlx1 = p1.dy; 5288 immutable float dly1 = -p1.dx; 5289 if (p1.flags&PointFlag.Left) { 5290 immutable float lx = p1.x+p1.dmx*woff; 5291 immutable float ly = p1.y+p1.dmy*woff; 5292 nvg__vset(dst, lx, ly, 0.5f, 1); ++dst; 5293 } else { 5294 immutable float lx0 = p1.x+dlx0*woff; 5295 immutable float ly0 = p1.y+dly0*woff; 5296 immutable float lx1 = p1.x+dlx1*woff; 5297 immutable float ly1 = p1.y+dly1*woff; 5298 nvg__vset(dst, lx0, ly0, 0.5f, 1); ++dst; 5299 nvg__vset(dst, lx1, ly1, 0.5f, 1); ++dst; 5300 } 5301 } else { 5302 nvg__vset(dst, p1.x+(p1.dmx*woff), p1.y+(p1.dmy*woff), 0.5f, 1); ++dst; 5303 } 5304 p0 = p1++; 5305 } 5306 } else { 5307 foreach (int j; 0..path.count) { 5308 nvg__vset(dst, pts[j].x, pts[j].y, 0.5f, 1); 5309 ++dst; 5310 } 5311 } 5312 5313 path.nfill = cast(int)(dst-verts); 5314 verts = dst; 5315 5316 // Calculate fringe 5317 if (fringe) { 5318 float lw = w+woff; 5319 immutable float rw = w-woff; 5320 float lu = 0; 5321 immutable float ru = 1; 5322 dst = verts; 5323 path.stroke = dst; 5324 5325 // Create only half a fringe for convex shapes so that 5326 // the shape can be rendered without stenciling. 5327 if (convex) { 5328 lw = woff; // This should generate the same vertex as fill inset above. 5329 lu = 0.5f; // Set outline fade at middle. 5330 } 5331 5332 // Looping 5333 NVGpoint* p0 = &pts[path.count-1]; 5334 NVGpoint* p1 = &pts[0]; 5335 5336 foreach (int j; 0..path.count) { 5337 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) { 5338 dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx.fringeWidth); 5339 } else { 5340 nvg__vset(dst, p1.x+(p1.dmx*lw), p1.y+(p1.dmy*lw), lu, 1); ++dst; 5341 nvg__vset(dst, p1.x-(p1.dmx*rw), p1.y-(p1.dmy*rw), ru, 1); ++dst; 5342 } 5343 p0 = p1++; 5344 } 5345 5346 // Loop it 5347 nvg__vset(dst, verts[0].x, verts[0].y, lu, 1); ++dst; 5348 nvg__vset(dst, verts[1].x, verts[1].y, ru, 1); ++dst; 5349 5350 path.nstroke = cast(int)(dst-verts); 5351 verts = dst; 5352 } else { 5353 path.stroke = null; 5354 path.nstroke = 0; 5355 } 5356 } 5357 } 5358 5359 5360 // ////////////////////////////////////////////////////////////////////////// // 5361 // Paths 5362 5363 /// Clears the current path and sub-paths. 5364 /// Group: paths 5365 @scriptable 5366 public void beginPath (NVGContext ctx) nothrow @trusted @nogc { 5367 ctx.ncommands = 0; 5368 ctx.pathPickRegistered &= NVGPickKind.All; // reset "registered" flags 5369 nvg__clearPathCache(ctx); 5370 } 5371 5372 public alias newPath = beginPath; /// Ditto. 5373 5374 /// Starts new sub-path with specified point as first point. 5375 /// Group: paths 5376 @scriptable 5377 public void moveTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 5378 nvg__appendCommands(ctx, Command.MoveTo, x, y); 5379 } 5380 5381 /// Starts new sub-path with specified point as first point. 5382 /// Arguments: [x, y]* 5383 /// Group: paths 5384 public void moveTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5385 enum ArgC = 2; 5386 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [moveTo] call"); 5387 if (args.length < ArgC) return; 5388 nvg__appendCommands(ctx, Command.MoveTo, args[$-2..$]); 5389 } 5390 5391 /// Adds line segment from the last point in the path to the specified point. 5392 /// Group: paths 5393 @scriptable 5394 public void lineTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 5395 nvg__appendCommands(ctx, Command.LineTo, x, y); 5396 } 5397 5398 /// Adds line segment from the last point in the path to the specified point. 5399 /// Arguments: [x, y]* 5400 /// Group: paths 5401 public void lineTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5402 enum ArgC = 2; 5403 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [lineTo] call"); 5404 if (args.length < ArgC) return; 5405 foreach (immutable idx; 0..args.length/ArgC) { 5406 nvg__appendCommands(ctx, Command.LineTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]); 5407 } 5408 } 5409 5410 /// Adds cubic bezier segment from last point in the path via two control points to the specified point. 5411 /// Group: paths 5412 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 { 5413 nvg__appendCommands(ctx, Command.BezierTo, c1x, c1y, c2x, c2y, x, y); 5414 } 5415 5416 /// Adds cubic bezier segment from last point in the path via two control points to the specified point. 5417 /// Arguments: [c1x, c1y, c2x, c2y, x, y]* 5418 /// Group: paths 5419 public void bezierTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5420 enum ArgC = 6; 5421 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [bezierTo] call"); 5422 if (args.length < ArgC) return; 5423 foreach (immutable idx; 0..args.length/ArgC) { 5424 nvg__appendCommands(ctx, Command.BezierTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]); 5425 } 5426 } 5427 5428 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point. 5429 /// Group: paths 5430 public void quadTo (NVGContext ctx, in float cx, in float cy, in float x, in float y) nothrow @trusted @nogc { 5431 immutable float x0 = ctx.commandx; 5432 immutable float y0 = ctx.commandy; 5433 nvg__appendCommands(ctx, 5434 Command.BezierTo, 5435 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 5436 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 5437 x, y, 5438 ); 5439 } 5440 5441 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point. 5442 /// Arguments: [cx, cy, x, y]* 5443 /// Group: paths 5444 public void quadTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5445 enum ArgC = 4; 5446 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [quadTo] call"); 5447 if (args.length < ArgC) return; 5448 const(float)* aptr = args.ptr; 5449 foreach (immutable idx; 0..args.length/ArgC) { 5450 immutable float x0 = ctx.commandx; 5451 immutable float y0 = ctx.commandy; 5452 immutable float cx = *aptr++; 5453 immutable float cy = *aptr++; 5454 immutable float x = *aptr++; 5455 immutable float y = *aptr++; 5456 nvg__appendCommands(ctx, 5457 Command.BezierTo, 5458 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 5459 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 5460 x, y, 5461 ); 5462 } 5463 } 5464 5465 /// Adds an arc segment at the corner defined by the last path point, and two specified points. 5466 /// Group: paths 5467 public void arcTo (NVGContext ctx, in float x1, in float y1, in float x2, in float y2, in float radius) nothrow @trusted @nogc { 5468 if (ctx.ncommands == 0) return; 5469 5470 immutable float x0 = ctx.commandx; 5471 immutable float y0 = ctx.commandy; 5472 5473 // handle degenerate cases 5474 if (nvg__ptEquals(x0, y0, x1, y1, ctx.distTol) || 5475 nvg__ptEquals(x1, y1, x2, y2, ctx.distTol) || 5476 nvg__distPtSeg(x1, y1, x0, y0, x2, y2) < ctx.distTol*ctx.distTol || 5477 radius < ctx.distTol) 5478 { 5479 ctx.lineTo(x1, y1); 5480 return; 5481 } 5482 5483 // calculate tangential circle to lines (x0, y0)-(x1, y1) and (x1, y1)-(x2, y2) 5484 float dx0 = x0-x1; 5485 float dy0 = y0-y1; 5486 float dx1 = x2-x1; 5487 float dy1 = y2-y1; 5488 nvg__normalize(&dx0, &dy0); 5489 nvg__normalize(&dx1, &dy1); 5490 immutable float a = nvg__acosf(dx0*dx1+dy0*dy1); 5491 immutable float d = radius/nvg__tanf(a/2.0f); 5492 5493 //printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d); 5494 5495 if (d > 10000.0f) { 5496 ctx.lineTo(x1, y1); 5497 return; 5498 } 5499 5500 float cx = void, cy = void, a0 = void, a1 = void; 5501 NVGWinding dir; 5502 if (nvg__cross(dx0, dy0, dx1, dy1) > 0.0f) { 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.CW; 5508 //printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); 5509 } else { 5510 cx = x1+dx0*d+-dy0*radius; 5511 cy = y1+dy0*d+dx0*radius; 5512 a0 = nvg__atan2f(-dx0, dy0); 5513 a1 = nvg__atan2f(dx1, -dy1); 5514 dir = NVGWinding.CCW; 5515 //printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); 5516 } 5517 5518 ctx.arc(dir, cx, cy, radius, a0, a1); // first is line 5519 } 5520 5521 5522 /// Adds an arc segment at the corner defined by the last path point, and two specified points. 5523 /// Arguments: [x1, y1, x2, y2, radius]* 5524 /// Group: paths 5525 public void arcTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5526 enum ArgC = 5; 5527 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arcTo] call"); 5528 if (args.length < ArgC) return; 5529 if (ctx.ncommands == 0) return; 5530 const(float)* aptr = args.ptr; 5531 foreach (immutable idx; 0..args.length/ArgC) { 5532 immutable float x0 = ctx.commandx; 5533 immutable float y0 = ctx.commandy; 5534 immutable float x1 = *aptr++; 5535 immutable float y1 = *aptr++; 5536 immutable float x2 = *aptr++; 5537 immutable float y2 = *aptr++; 5538 immutable float radius = *aptr++; 5539 ctx.arcTo(x1, y1, x2, y2, radius); 5540 } 5541 } 5542 5543 /// Closes current sub-path with a line segment. 5544 /// Group: paths 5545 @scriptable 5546 public void closePath (NVGContext ctx) nothrow @trusted @nogc { 5547 nvg__appendCommands(ctx, Command.Close); 5548 } 5549 5550 /// Sets the current sub-path winding, see NVGWinding and NVGSolidity. 5551 /// Group: paths 5552 public void pathWinding (NVGContext ctx, NVGWinding dir) nothrow @trusted @nogc { 5553 nvg__appendCommands(ctx, Command.Winding, cast(float)dir); 5554 } 5555 5556 /// Ditto. 5557 public void pathWinding (NVGContext ctx, NVGSolidity dir) nothrow @trusted @nogc { 5558 nvg__appendCommands(ctx, Command.Winding, cast(float)dir); 5559 } 5560 5561 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r, 5562 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW). 5563 * Angles are specified in radians. 5564 * 5565 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo 5566 * 5567 * Group: paths 5568 */ 5569 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 { 5570 static assert(mode == "original" || mode == "move" || mode == "line"); 5571 5572 float[3+5*7+100] vals = void; 5573 //int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo); 5574 static if (mode == "original") { 5575 immutable int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo); 5576 } else static if (mode == "move") { 5577 enum move = Command.MoveTo; 5578 } else static if (mode == "line") { 5579 enum move = Command.LineTo; 5580 } else { 5581 static assert(0, "wtf?!"); 5582 } 5583 5584 // Clamp angles 5585 float da = a1-a0; 5586 if (dir == NVGWinding.CW) { 5587 if (nvg__absf(da) >= NVG_PI*2) { 5588 da = NVG_PI*2; 5589 } else { 5590 while (da < 0.0f) da += NVG_PI*2; 5591 } 5592 } else { 5593 if (nvg__absf(da) >= NVG_PI*2) { 5594 da = -NVG_PI*2; 5595 } else { 5596 while (da > 0.0f) da -= NVG_PI*2; 5597 } 5598 } 5599 5600 // Split arc into max 90 degree segments. 5601 immutable int ndivs = nvg__max(1, nvg__min(cast(int)(nvg__absf(da)/(NVG_PI*0.5f)+0.5f), 5)); 5602 immutable float hda = (da/cast(float)ndivs)/2.0f; 5603 float kappa = nvg__absf(4.0f/3.0f*(1.0f-nvg__cosf(hda))/nvg__sinf(hda)); 5604 5605 if (dir == NVGWinding.CCW) kappa = -kappa; 5606 5607 int nvals = 0; 5608 float px = 0, py = 0, ptanx = 0, ptany = 0; 5609 foreach (int i; 0..ndivs+1) { 5610 immutable float a = a0+da*(i/cast(float)ndivs); 5611 immutable float dx = nvg__cosf(a); 5612 immutable float dy = nvg__sinf(a); 5613 immutable float x = cx+dx*r; 5614 immutable float y = cy+dy*r; 5615 immutable float tanx = -dy*r*kappa; 5616 immutable float tany = dx*r*kappa; 5617 5618 if (i == 0) { 5619 if (vals.length-nvals < 3) { 5620 // flush 5621 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5622 nvals = 0; 5623 } 5624 vals.ptr[nvals++] = cast(float)move; 5625 vals.ptr[nvals++] = x; 5626 vals.ptr[nvals++] = y; 5627 } else { 5628 if (vals.length-nvals < 7) { 5629 // flush 5630 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5631 nvals = 0; 5632 } 5633 vals.ptr[nvals++] = Command.BezierTo; 5634 vals.ptr[nvals++] = px+ptanx; 5635 vals.ptr[nvals++] = py+ptany; 5636 vals.ptr[nvals++] = x-tanx; 5637 vals.ptr[nvals++] = y-tany; 5638 vals.ptr[nvals++] = x; 5639 vals.ptr[nvals++] = y; 5640 } 5641 px = x; 5642 py = y; 5643 ptanx = tanx; 5644 ptany = tany; 5645 } 5646 5647 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5648 } 5649 5650 5651 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r, 5652 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW). 5653 * Angles are specified in radians. 5654 * 5655 * Arguments: [cx, cy, r, a0, a1]* 5656 * 5657 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo 5658 * 5659 * Group: paths 5660 */ 5661 public void arc(string mode="original") (NVGContext ctx, NVGWinding dir, in float[] args) nothrow @trusted @nogc { 5662 static assert(mode == "original" || mode == "move" || mode == "line"); 5663 enum ArgC = 5; 5664 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arc] call"); 5665 if (args.length < ArgC) return; 5666 const(float)* aptr = args.ptr; 5667 foreach (immutable idx; 0..args.length/ArgC) { 5668 immutable cx = *aptr++; 5669 immutable cy = *aptr++; 5670 immutable r = *aptr++; 5671 immutable a0 = *aptr++; 5672 immutable a1 = *aptr++; 5673 ctx.arc!mode(dir, cx, cy, r, a0, a1); 5674 } 5675 } 5676 5677 /// Creates new rectangle shaped sub-path. 5678 /// Group: paths 5679 @scriptable 5680 public void rect (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc { 5681 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5682 Command.MoveTo, x, y, 5683 Command.LineTo, x, y+h, 5684 Command.LineTo, x+w, y+h, 5685 Command.LineTo, x+w, y, 5686 Command.Close, 5687 ); 5688 } 5689 5690 /// Creates new rectangle shaped sub-path. 5691 /// Arguments: [x, y, w, h]* 5692 /// Group: paths 5693 public void rect (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5694 enum ArgC = 4; 5695 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [rect] call"); 5696 if (args.length < ArgC) return; 5697 const(float)* aptr = args.ptr; 5698 foreach (immutable idx; 0..args.length/ArgC) { 5699 immutable x = *aptr++; 5700 immutable y = *aptr++; 5701 immutable w = *aptr++; 5702 immutable h = *aptr++; 5703 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5704 Command.MoveTo, x, y, 5705 Command.LineTo, x, y+h, 5706 Command.LineTo, x+w, y+h, 5707 Command.LineTo, x+w, y, 5708 Command.Close, 5709 ); 5710 } 5711 } 5712 5713 /// Creates new rounded rectangle shaped sub-path. 5714 /// Group: paths 5715 @scriptable 5716 public void roundedRect (NVGContext ctx, in float x, in float y, in float w, in float h, in float radius) nothrow @trusted @nogc { 5717 ctx.roundedRectVarying(x, y, w, h, radius, radius, radius, radius); 5718 } 5719 5720 /// Creates new rounded rectangle shaped sub-path. 5721 /// Arguments: [x, y, w, h, radius]* 5722 /// Group: paths 5723 public void roundedRect (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5724 enum ArgC = 5; 5725 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRect] call"); 5726 if (args.length < ArgC) return; 5727 const(float)* aptr = args.ptr; 5728 foreach (immutable idx; 0..args.length/ArgC) { 5729 immutable x = *aptr++; 5730 immutable y = *aptr++; 5731 immutable w = *aptr++; 5732 immutable h = *aptr++; 5733 immutable r = *aptr++; 5734 ctx.roundedRectVarying(x, y, w, h, r, r, r, r); 5735 } 5736 } 5737 5738 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it. 5739 /// Group: paths 5740 @scriptable 5741 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 { 5742 if (rw < 0.1f || rh < 0.1f) { 5743 rect(ctx, x, y, w, h); 5744 } else { 5745 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5746 Command.MoveTo, x+rw, y, 5747 Command.LineTo, x+w-rw, y, 5748 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh, 5749 Command.LineTo, x+w, y+h-rh, 5750 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h, 5751 Command.LineTo, x+rw, y+h, 5752 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh, 5753 Command.LineTo, x, y+rh, 5754 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y, 5755 Command.Close, 5756 ); 5757 } 5758 } 5759 5760 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it. 5761 /// Arguments: [x, y, w, h, rw, rh]* 5762 /// Group: paths 5763 public void roundedRectEllipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5764 enum ArgC = 6; 5765 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectEllipse] call"); 5766 if (args.length < ArgC) return; 5767 const(float)* aptr = args.ptr; 5768 foreach (immutable idx; 0..args.length/ArgC) { 5769 immutable x = *aptr++; 5770 immutable y = *aptr++; 5771 immutable w = *aptr++; 5772 immutable h = *aptr++; 5773 immutable rw = *aptr++; 5774 immutable rh = *aptr++; 5775 if (rw < 0.1f || rh < 0.1f) { 5776 rect(ctx, x, y, w, h); 5777 } else { 5778 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5779 Command.MoveTo, x+rw, y, 5780 Command.LineTo, x+w-rw, y, 5781 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh, 5782 Command.LineTo, x+w, y+h-rh, 5783 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h, 5784 Command.LineTo, x+rw, y+h, 5785 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh, 5786 Command.LineTo, x, y+rh, 5787 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y, 5788 Command.Close, 5789 ); 5790 } 5791 } 5792 } 5793 5794 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner. 5795 /// Group: paths 5796 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 { 5797 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { 5798 ctx.rect(x, y, w, h); 5799 } else { 5800 immutable float halfw = nvg__absf(w)*0.5f; 5801 immutable float halfh = nvg__absf(h)*0.5f; 5802 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h); 5803 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h); 5804 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h); 5805 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h); 5806 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5807 Command.MoveTo, x, y+ryTL, 5808 Command.LineTo, x, y+h-ryBL, 5809 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h, 5810 Command.LineTo, x+w-rxBR, y+h, 5811 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR, 5812 Command.LineTo, x+w, y+ryTR, 5813 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y, 5814 Command.LineTo, x+rxTL, y, 5815 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL, 5816 Command.Close, 5817 ); 5818 } 5819 } 5820 5821 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner. 5822 /// Arguments: [x, y, w, h, radTopLeft, radTopRight, radBottomRight, radBottomLeft]* 5823 /// Group: paths 5824 public void roundedRectVarying (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5825 enum ArgC = 8; 5826 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectVarying] call"); 5827 if (args.length < ArgC) return; 5828 const(float)* aptr = args.ptr; 5829 foreach (immutable idx; 0..args.length/ArgC) { 5830 immutable x = *aptr++; 5831 immutable y = *aptr++; 5832 immutable w = *aptr++; 5833 immutable h = *aptr++; 5834 immutable radTopLeft = *aptr++; 5835 immutable radTopRight = *aptr++; 5836 immutable radBottomRight = *aptr++; 5837 immutable radBottomLeft = *aptr++; 5838 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { 5839 ctx.rect(x, y, w, h); 5840 } else { 5841 immutable float halfw = nvg__absf(w)*0.5f; 5842 immutable float halfh = nvg__absf(h)*0.5f; 5843 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h); 5844 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h); 5845 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h); 5846 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h); 5847 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5848 Command.MoveTo, x, y+ryTL, 5849 Command.LineTo, x, y+h-ryBL, 5850 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h, 5851 Command.LineTo, x+w-rxBR, y+h, 5852 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR, 5853 Command.LineTo, x+w, y+ryTR, 5854 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y, 5855 Command.LineTo, x+rxTL, y, 5856 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL, 5857 Command.Close, 5858 ); 5859 } 5860 } 5861 } 5862 5863 /// Creates new ellipse shaped sub-path. 5864 /// Group: paths 5865 public void ellipse (NVGContext ctx, in float cx, in float cy, in float rx, in float ry) nothrow @trusted @nogc { 5866 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5867 Command.MoveTo, cx-rx, cy, 5868 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry, 5869 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy, 5870 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry, 5871 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy, 5872 Command.Close, 5873 ); 5874 } 5875 5876 /// Creates new ellipse shaped sub-path. 5877 /// Arguments: [cx, cy, rx, ry]* 5878 /// Group: paths 5879 public void ellipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5880 enum ArgC = 4; 5881 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [ellipse] call"); 5882 if (args.length < ArgC) return; 5883 const(float)* aptr = args.ptr; 5884 foreach (immutable idx; 0..args.length/ArgC) { 5885 immutable cx = *aptr++; 5886 immutable cy = *aptr++; 5887 immutable rx = *aptr++; 5888 immutable ry = *aptr++; 5889 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5890 Command.MoveTo, cx-rx, cy, 5891 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry, 5892 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy, 5893 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry, 5894 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy, 5895 Command.Close, 5896 ); 5897 } 5898 } 5899 5900 /// Creates new circle shaped sub-path. 5901 /// Group: paths 5902 public void circle (NVGContext ctx, in float cx, in float cy, in float r) nothrow @trusted @nogc { 5903 ctx.ellipse(cx, cy, r, r); 5904 } 5905 5906 /// Creates new circle shaped sub-path. 5907 /// Arguments: [cx, cy, r]* 5908 /// Group: paths 5909 public void circle (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5910 enum ArgC = 3; 5911 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [circle] call"); 5912 if (args.length < ArgC) return; 5913 const(float)* aptr = args.ptr; 5914 foreach (immutable idx; 0..args.length/ArgC) { 5915 immutable cx = *aptr++; 5916 immutable cy = *aptr++; 5917 immutable r = *aptr++; 5918 ctx.ellipse(cx, cy, r, r); 5919 } 5920 } 5921 5922 // Debug function to dump cached path data. 5923 debug public void debugDumpPathCache (NVGContext ctx) nothrow @trusted @nogc { 5924 import core.stdc.stdio : printf; 5925 const(NVGpath)* path; 5926 printf("Dumping %d cached paths\n", ctx.cache.npaths); 5927 for (int i = 0; i < ctx.cache.npaths; ++i) { 5928 path = &ctx.cache.paths[i]; 5929 printf("-Path %d\n", i); 5930 if (path.nfill) { 5931 printf("-fill: %d\n", path.nfill); 5932 for (int j = 0; j < path.nfill; ++j) printf("%f\t%f\n", path.fill[j].x, path.fill[j].y); 5933 } 5934 if (path.nstroke) { 5935 printf("-stroke: %d\n", path.nstroke); 5936 for (int j = 0; j < path.nstroke; ++j) printf("%f\t%f\n", path.stroke[j].x, path.stroke[j].y); 5937 } 5938 } 5939 } 5940 5941 // Flatten path, prepare it for fill operation. 5942 void nvg__prepareFill (NVGContext ctx) nothrow @trusted @nogc { 5943 NVGpathCache* cache = ctx.cache; 5944 NVGstate* state = nvg__getState(ctx); 5945 5946 nvg__flattenPaths!false(ctx); 5947 5948 if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) { 5949 nvg__expandFill(ctx, ctx.fringeWidth, NVGLineCap.Miter, 2.4f); 5950 } else { 5951 nvg__expandFill(ctx, 0.0f, NVGLineCap.Miter, 2.4f); 5952 } 5953 5954 cache.evenOddMode = state.evenOddMode; 5955 cache.fringeWidth = ctx.fringeWidth; 5956 cache.fillReady = true; 5957 cache.strokeReady = false; 5958 cache.clipmode = NVGClipMode.None; 5959 } 5960 5961 // Flatten path, prepare it for stroke operation. 5962 void nvg__prepareStroke (NVGContext ctx) nothrow @trusted @nogc { 5963 NVGstate* state = nvg__getState(ctx); 5964 NVGpathCache* cache = ctx.cache; 5965 5966 nvg__flattenPaths!true(ctx); 5967 5968 immutable float scale = nvg__getAverageScale(state.xform); 5969 float strokeWidth = nvg__clamp(state.strokeWidth*scale, 0.0f, 200.0f); 5970 5971 if (strokeWidth < ctx.fringeWidth) { 5972 // If the stroke width is less than pixel size, use alpha to emulate coverage. 5973 // Since coverage is area, scale by alpha*alpha. 5974 immutable float alpha = nvg__clamp(strokeWidth/ctx.fringeWidth, 0.0f, 1.0f); 5975 cache.strokeAlphaMul = alpha*alpha; 5976 strokeWidth = ctx.fringeWidth; 5977 } else { 5978 cache.strokeAlphaMul = 1.0f; 5979 } 5980 cache.strokeWidth = strokeWidth; 5981 5982 if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) { 5983 nvg__expandStroke(ctx, strokeWidth*0.5f+ctx.fringeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit); 5984 } else { 5985 nvg__expandStroke(ctx, strokeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit); 5986 } 5987 5988 cache.fringeWidth = ctx.fringeWidth; 5989 cache.fillReady = false; 5990 cache.strokeReady = true; 5991 cache.clipmode = NVGClipMode.None; 5992 } 5993 5994 /// Fills the current path with current fill style. 5995 /// Group: paths 5996 @scriptable 5997 public void fill (NVGContext ctx) nothrow @trusted @nogc { 5998 NVGstate* state = nvg__getState(ctx); 5999 6000 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) { 6001 ctx.pathPickRegistered |= NVGPickKind.Fill<<16; 6002 ctx.currFillHitId = ctx.pathPickId; 6003 } 6004 6005 nvg__prepareFill(ctx); 6006 6007 // apply global alpha 6008 NVGPaint fillPaint = state.fill; 6009 fillPaint.innerColor.a *= state.alpha; 6010 fillPaint.middleColor.a *= state.alpha; 6011 fillPaint.outerColor.a *= state.alpha; 6012 6013 ctx.appendCurrentPathToCache(ctx.recset, state.fill); 6014 6015 if (ctx.recblockdraw) return; 6016 6017 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); 6018 6019 // count triangles 6020 foreach (int i; 0..ctx.cache.npaths) { 6021 NVGpath* path = &ctx.cache.paths[i]; 6022 ctx.fillTriCount += path.nfill-2; 6023 ctx.fillTriCount += path.nstroke-2; 6024 ctx.drawCallCount += 2; 6025 } 6026 } 6027 6028 /// Fills the current path with current stroke style. 6029 /// Group: paths 6030 @scriptable 6031 public void stroke (NVGContext ctx) nothrow @trusted @nogc { 6032 NVGstate* state = nvg__getState(ctx); 6033 6034 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) { 6035 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16; 6036 ctx.currStrokeHitId = ctx.pathPickId; 6037 } 6038 6039 nvg__prepareStroke(ctx); 6040 6041 NVGpathCache* cache = ctx.cache; 6042 6043 NVGPaint strokePaint = state.stroke; 6044 strokePaint.innerColor.a *= cache.strokeAlphaMul; 6045 strokePaint.middleColor.a *= cache.strokeAlphaMul; 6046 strokePaint.outerColor.a *= cache.strokeAlphaMul; 6047 6048 // apply global alpha 6049 strokePaint.innerColor.a *= state.alpha; 6050 strokePaint.middleColor.a *= state.alpha; 6051 strokePaint.outerColor.a *= state.alpha; 6052 6053 ctx.appendCurrentPathToCache(ctx.recset, state.stroke); 6054 6055 if (ctx.recblockdraw) return; 6056 6057 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths); 6058 6059 // count triangles 6060 foreach (int i; 0..ctx.cache.npaths) { 6061 NVGpath* path = &ctx.cache.paths[i]; 6062 ctx.strokeTriCount += path.nstroke-2; 6063 ++ctx.drawCallCount; 6064 } 6065 } 6066 6067 /// Sets current path as clipping region. 6068 /// Group: clipping 6069 public void clip (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc { 6070 NVGstate* state = nvg__getState(ctx); 6071 6072 if (aclipmode == NVGClipMode.None) return; 6073 if (ctx.recblockdraw) return; //??? 6074 6075 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr); 6076 6077 /* 6078 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) { 6079 ctx.pathPickRegistered |= NVGPickKind.Fill<<16; 6080 ctx.currFillHitId = ctx.pathPickId; 6081 } 6082 */ 6083 6084 nvg__prepareFill(ctx); 6085 6086 // apply global alpha 6087 NVGPaint fillPaint = state.fill; 6088 fillPaint.innerColor.a *= state.alpha; 6089 fillPaint.middleColor.a *= state.alpha; 6090 fillPaint.outerColor.a *= state.alpha; 6091 6092 //ctx.appendCurrentPathToCache(ctx.recset, state.fill); 6093 6094 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); 6095 6096 // count triangles 6097 foreach (int i; 0..ctx.cache.npaths) { 6098 NVGpath* path = &ctx.cache.paths[i]; 6099 ctx.fillTriCount += path.nfill-2; 6100 ctx.fillTriCount += path.nstroke-2; 6101 ctx.drawCallCount += 2; 6102 } 6103 } 6104 6105 /// Sets current path as clipping region. 6106 /// Group: clipping 6107 public alias clipFill = clip; 6108 6109 /// Sets current path' stroke as clipping region. 6110 /// Group: clipping 6111 public void clipStroke (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc { 6112 NVGstate* state = nvg__getState(ctx); 6113 6114 if (aclipmode == NVGClipMode.None) return; 6115 if (ctx.recblockdraw) return; //??? 6116 6117 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr); 6118 6119 /* 6120 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) { 6121 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16; 6122 ctx.currStrokeHitId = ctx.pathPickId; 6123 } 6124 */ 6125 6126 nvg__prepareStroke(ctx); 6127 6128 NVGpathCache* cache = ctx.cache; 6129 6130 NVGPaint strokePaint = state.stroke; 6131 strokePaint.innerColor.a *= cache.strokeAlphaMul; 6132 strokePaint.middleColor.a *= cache.strokeAlphaMul; 6133 strokePaint.outerColor.a *= cache.strokeAlphaMul; 6134 6135 // apply global alpha 6136 strokePaint.innerColor.a *= state.alpha; 6137 strokePaint.middleColor.a *= state.alpha; 6138 strokePaint.outerColor.a *= state.alpha; 6139 6140 //ctx.appendCurrentPathToCache(ctx.recset, state.stroke); 6141 6142 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, aclipmode, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths); 6143 6144 // count triangles 6145 foreach (int i; 0..ctx.cache.npaths) { 6146 NVGpath* path = &ctx.cache.paths[i]; 6147 ctx.strokeTriCount += path.nstroke-2; 6148 ++ctx.drawCallCount; 6149 } 6150 } 6151 6152 6153 // ////////////////////////////////////////////////////////////////////////// // 6154 // Picking API 6155 6156 // most of the code is by Michael Wynne <mike@mikesspace.net> 6157 // https://github.com/memononen/nanovg/pull/230 6158 // https://github.com/MikeWW/nanovg 6159 6160 /// Pick type query. Used in [hitTest] and [hitTestAll]. 6161 /// Group: picking_api 6162 public enum NVGPickKind : ubyte { 6163 Fill = 0x01, /// 6164 Stroke = 0x02, /// 6165 All = 0x03, /// 6166 } 6167 6168 /// Marks the fill of the current path as pickable with the specified id. 6169 /// Note that you can create and mark path without rasterizing it. 6170 /// Group: picking_api 6171 public void currFillHitId (NVGContext ctx, int id) nothrow @trusted @nogc { 6172 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6173 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/false); 6174 nvg__pickSceneInsert(ps, pp); 6175 } 6176 6177 public alias currFillPickId = currFillHitId; /// Ditto. 6178 6179 /// Marks the stroke of the current path as pickable with the specified id. 6180 /// Note that you can create and mark path without rasterizing it. 6181 /// Group: picking_api 6182 public void currStrokeHitId (NVGContext ctx, int id) nothrow @trusted @nogc { 6183 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6184 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/true); 6185 nvg__pickSceneInsert(ps, pp); 6186 } 6187 6188 public alias currStrokePickId = currStrokeHitId; /// Ditto. 6189 6190 // Marks the saved path set (fill) as pickable with the specified id. 6191 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!) 6192 // Group: picking_api 6193 /+ 6194 public void pathSetFillHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc { 6195 if (svp is null) return; 6196 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context"); 6197 foreach (ref cp; svp.caches[0..svp.ncaches]) { 6198 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6199 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/false); 6200 nvg__pickSceneInsert(ps, pp); 6201 } 6202 } 6203 +/ 6204 6205 // Marks the saved path set (stroke) as pickable with the specified id. 6206 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!) 6207 // Group: picking_api 6208 /+ 6209 public void pathSetStrokeHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc { 6210 if (svp is null) return; 6211 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context"); 6212 foreach (ref cp; svp.caches[0..svp.ncaches]) { 6213 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6214 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/true); 6215 nvg__pickSceneInsert(ps, pp); 6216 } 6217 } 6218 +/ 6219 6220 private template IsGoodHitTestDG(DG) { 6221 enum IsGoodHitTestDG = 6222 __traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); }) || 6223 __traits(compiles, (){ DG dg; dg(cast(int)42, cast(int)666); }); 6224 } 6225 6226 private template IsGoodHitTestInternalDG(DG) { 6227 enum IsGoodHitTestInternalDG = 6228 __traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); }) || 6229 __traits(compiles, (){ DG dg; NVGpickPath* pp; dg(pp); }); 6230 } 6231 6232 /// Call delegate [dg] for each path under the specified position (in no particular order). 6233 /// Returns the id of the path for which delegate [dg] returned true or [NVGNoPick]. 6234 /// dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending). 6235 /// Group: picking_api 6236 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) { 6237 if (ctx.pickScene is null || ctx.pickScene.npaths == 0 || (kind&NVGPickKind.All) == 0) return -1; 6238 6239 NVGpickScene* ps = ctx.pickScene; 6240 int levelwidth = 1<<(ps.nlevels-1); 6241 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth); 6242 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth); 6243 int npicked = 0; 6244 6245 // if we are interested only in most-toplevel path, there is no reason to check paths with worser order. 6246 // but we cannot just get out on the first path found, 'cause we are using quad tree to speed up bounds 6247 // checking, so path walking order is not guaranteed. 6248 static if (bestOrder) { 6249 int lastBestOrder = int.min; 6250 } 6251 6252 //{ import core.stdc.stdio; printf("npaths=%d\n", ps.npaths); } 6253 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) { 6254 for (NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; pp !is null; pp = pp.next) { 6255 //{ 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); } 6256 static if (bestOrder) { 6257 // reject earlier paths 6258 if (pp.order <= lastBestOrder) continue; // not interesting 6259 } 6260 immutable uint kpx = kind&pp.flags&3; 6261 if (kpx == 0) continue; // not interesting 6262 if (!nvg__pickPathTestBounds(ctx, ps, pp, x, y)) continue; // not interesting 6263 //{ import core.stdc.stdio; printf("in bounds!\n"); } 6264 int hit = 0; 6265 if (kpx&NVGPickKind.Stroke) hit = nvg__pickPathStroke(ps, pp, x, y); 6266 if (!hit && (kpx&NVGPickKind.Fill)) hit = nvg__pickPath(ps, pp, x, y); 6267 if (!hit) continue; 6268 //{ import core.stdc.stdio; printf(" HIT!\n"); } 6269 static if (bestOrder) lastBestOrder = pp.order; 6270 static if (IsGoodHitTestDG!DG) { 6271 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) { 6272 if (dg(pp.id, cast(int)pp.order)) return pp.id; 6273 } else { 6274 dg(pp.id, cast(int)pp.order); 6275 } 6276 } else { 6277 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) { 6278 if (dg(pp)) return pp.id; 6279 } else { 6280 dg(pp); 6281 } 6282 } 6283 } 6284 cellx >>= 1; 6285 celly >>= 1; 6286 levelwidth >>= 1; 6287 } 6288 6289 return -1; 6290 } 6291 6292 /// Fills ids with a list of the top most hit ids (from bottom to top) under the specified position. 6293 /// Returns the slice of [ids]. 6294 /// Group: picking_api 6295 public int[] hitTestAll (NVGContext ctx, in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc { 6296 if (ctx.pickScene is null || ids.length == 0) return ids[0..0]; 6297 6298 int npicked = 0; 6299 NVGpickScene* ps = ctx.pickScene; 6300 6301 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 6302 if (npicked == ps.cpicked) { 6303 int cpicked = ps.cpicked+ps.cpicked; 6304 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked); 6305 if (picked is null) return true; // abort 6306 ps.cpicked = cpicked; 6307 ps.picked = picked; 6308 } 6309 ps.picked[npicked] = pp; 6310 ++npicked; 6311 return false; // go on 6312 }); 6313 6314 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths); 6315 6316 assert(npicked >= 0); 6317 if (npicked > ids.length) npicked = cast(int)ids.length; 6318 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id; 6319 6320 return ids[0..npicked]; 6321 } 6322 6323 /// Returns the id of the pickable shape containing x,y or [NVGNoPick] if no shape was found. 6324 /// Group: picking_api 6325 public int hitTest (NVGContext ctx, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc { 6326 if (ctx.pickScene is null) return -1; 6327 6328 int bestOrder = int.min; 6329 int bestID = -1; 6330 6331 ctx.hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) { 6332 if (pp.order > bestOrder) { 6333 bestOrder = pp.order; 6334 bestID = pp.id; 6335 } 6336 }); 6337 6338 return bestID; 6339 } 6340 6341 /// Returns `true` if the path with the given id contains x,y. 6342 /// Group: picking_api 6343 public bool hitTestForId (NVGContext ctx, in int id, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc { 6344 if (ctx.pickScene is null || id == NVGNoPick) return false; 6345 6346 bool res = false; 6347 6348 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) { 6349 if (pp.id == id) { 6350 res = true; 6351 return true; // stop 6352 } 6353 return false; // continue 6354 }); 6355 6356 return res; 6357 } 6358 6359 /// Returns `true` if the given point is within the fill of the currently defined path. 6360 /// This operation can be done before rasterizing the current path. 6361 /// Group: picking_api 6362 public bool hitTestCurrFill (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 6363 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6364 int oldnpoints = ps.npoints; 6365 int oldnsegments = ps.nsegments; 6366 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/false); 6367 if (pp is null) return false; // oops 6368 scope(exit) { 6369 nvg__freePickPath(ps, pp); 6370 ps.npoints = oldnpoints; 6371 ps.nsegments = oldnsegments; 6372 } 6373 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPath(ps, pp, x, y) : false); 6374 } 6375 6376 public alias isPointInPath = hitTestCurrFill; /// Ditto. 6377 6378 /// Returns `true` if the given point is within the stroke of the currently defined path. 6379 /// This operation can be done before rasterizing the current path. 6380 /// Group: picking_api 6381 public bool hitTestCurrStroke (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 6382 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6383 int oldnpoints = ps.npoints; 6384 int oldnsegments = ps.nsegments; 6385 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/true); 6386 if (pp is null) return false; // oops 6387 scope(exit) { 6388 nvg__freePickPath(ps, pp); 6389 ps.npoints = oldnpoints; 6390 ps.nsegments = oldnsegments; 6391 } 6392 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPathStroke(ps, pp, x, y) : false); 6393 } 6394 6395 6396 nothrow @trusted @nogc { 6397 extern(C) { 6398 private alias _compare_fp_t = int function (const void*, const void*) nothrow @nogc; 6399 private extern(C) void qsort (scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc; 6400 6401 extern(C) int nvg__comparePaths (const void* a, const void* b) { 6402 return (*cast(const(NVGpickPath)**)b).order-(*cast(const(NVGpickPath)**)a).order; 6403 } 6404 } 6405 6406 enum NVGPickEPS = 0.0001f; 6407 6408 // Segment flags 6409 enum NVGSegmentFlags { 6410 Corner = 1, 6411 Bevel = 2, 6412 InnerBevel = 4, 6413 Cap = 8, 6414 Endcap = 16, 6415 } 6416 6417 // Path flags 6418 enum NVGPathFlags : ushort { 6419 Fill = NVGPickKind.Fill, 6420 Stroke = NVGPickKind.Stroke, 6421 Scissor = 0x80, 6422 } 6423 6424 struct NVGsegment { 6425 int firstPoint; // Index into NVGpickScene.points 6426 short type; // NVG_LINETO or NVG_BEZIERTO 6427 short flags; // Flags relate to the corner between the prev segment and this one. 6428 float[4] bounds; 6429 float[2] startDir; // Direction at t == 0 6430 float[2] endDir; // Direction at t == 1 6431 float[2] miterDir; // Direction of miter of corner between the prev segment and this one. 6432 } 6433 6434 struct NVGpickSubPath { 6435 short winding; // TODO: Merge to flag field 6436 bool closed; // TODO: Merge to flag field 6437 6438 int firstSegment; // Index into NVGpickScene.segments 6439 int nsegments; 6440 6441 float[4] bounds; 6442 6443 NVGpickSubPath* next; 6444 } 6445 6446 struct NVGpickPath { 6447 int id; 6448 short flags; 6449 short order; 6450 float strokeWidth; 6451 float miterLimit; 6452 short lineCap; 6453 short lineJoin; 6454 bool evenOddMode; 6455 6456 float[4] bounds; 6457 int scissor; // Indexes into ps->points and defines scissor rect as XVec, YVec and Center 6458 6459 NVGpickSubPath* subPaths; 6460 NVGpickPath* next; 6461 NVGpickPath* cellnext; 6462 } 6463 6464 struct NVGpickScene { 6465 int npaths; 6466 6467 NVGpickPath* paths; // Linked list of paths 6468 NVGpickPath* lastPath; // The last path in the paths linked list (the first path added) 6469 NVGpickPath* freePaths; // Linked list of free paths 6470 6471 NVGpickSubPath* freeSubPaths; // Linked list of free sub paths 6472 6473 int width; 6474 int height; 6475 6476 // Points for all path sub paths. 6477 float* points; 6478 int npoints; 6479 int cpoints; 6480 6481 // Segments for all path sub paths 6482 NVGsegment* segments; 6483 int nsegments; 6484 int csegments; 6485 6486 // Implicit quadtree 6487 float xdim; // Width / (1 << nlevels) 6488 float ydim; // Height / (1 << nlevels) 6489 int ncells; // Total number of cells in all levels 6490 int nlevels; 6491 NVGpickPath*** levels; // Index: [Level][LevelY * LevelW + LevelX] Value: Linked list of paths 6492 6493 // Temp storage for picking 6494 int cpicked; 6495 NVGpickPath** picked; 6496 } 6497 6498 6499 // bounds utilities 6500 void nvg__initBounds (ref float[4] bounds) { 6501 bounds.ptr[0] = bounds.ptr[1] = float.max; 6502 bounds.ptr[2] = bounds.ptr[3] = -float.max; 6503 } 6504 6505 void nvg__expandBounds (ref float[4] bounds, const(float)* points, int npoints) { 6506 npoints *= 2; 6507 for (int i = 0; i < npoints; i += 2) { 6508 bounds.ptr[0] = nvg__min(bounds.ptr[0], points[i]); 6509 bounds.ptr[1] = nvg__min(bounds.ptr[1], points[i+1]); 6510 bounds.ptr[2] = nvg__max(bounds.ptr[2], points[i]); 6511 bounds.ptr[3] = nvg__max(bounds.ptr[3], points[i+1]); 6512 } 6513 } 6514 6515 void nvg__unionBounds (ref float[4] bounds, const scope ref float[4] boundsB) { 6516 bounds.ptr[0] = nvg__min(bounds.ptr[0], boundsB.ptr[0]); 6517 bounds.ptr[1] = nvg__min(bounds.ptr[1], boundsB.ptr[1]); 6518 bounds.ptr[2] = nvg__max(bounds.ptr[2], boundsB.ptr[2]); 6519 bounds.ptr[3] = nvg__max(bounds.ptr[3], boundsB.ptr[3]); 6520 } 6521 6522 void nvg__intersectBounds (ref float[4] bounds, const scope ref float[4] boundsB) { 6523 bounds.ptr[0] = nvg__max(boundsB.ptr[0], bounds.ptr[0]); 6524 bounds.ptr[1] = nvg__max(boundsB.ptr[1], bounds.ptr[1]); 6525 bounds.ptr[2] = nvg__min(boundsB.ptr[2], bounds.ptr[2]); 6526 bounds.ptr[3] = nvg__min(boundsB.ptr[3], bounds.ptr[3]); 6527 6528 bounds.ptr[2] = nvg__max(bounds.ptr[0], bounds.ptr[2]); 6529 bounds.ptr[3] = nvg__max(bounds.ptr[1], bounds.ptr[3]); 6530 } 6531 6532 bool nvg__pointInBounds (in float x, in float y, const scope ref float[4] bounds) { 6533 pragma(inline, true); 6534 return (x >= bounds.ptr[0] && x <= bounds.ptr[2] && y >= bounds.ptr[1] && y <= bounds.ptr[3]); 6535 } 6536 6537 // building paths & sub paths 6538 int nvg__pickSceneAddPoints (NVGpickScene* ps, const(float)* xy, int n) { 6539 import core.stdc.string : memcpy; 6540 if (ps.npoints+n > ps.cpoints) { 6541 import core.stdc.stdlib : realloc; 6542 int cpoints = ps.npoints+n+(ps.cpoints<<1); 6543 float* points = cast(float*)realloc(ps.points, float.sizeof*2*cpoints); 6544 if (points is null) assert(0, "NanoVega: out of memory"); 6545 ps.points = points; 6546 ps.cpoints = cpoints; 6547 } 6548 int i = ps.npoints; 6549 if (xy !is null) memcpy(&ps.points[i*2], xy, float.sizeof*2*n); 6550 ps.npoints += n; 6551 return i; 6552 } 6553 6554 void nvg__pickSubPathAddSegment (NVGpickScene* ps, NVGpickSubPath* psp, int firstPoint, int type, short flags) { 6555 NVGsegment* seg = null; 6556 if (ps.nsegments == ps.csegments) { 6557 int csegments = 1+ps.csegments+(ps.csegments<<1); 6558 NVGsegment* segments = cast(NVGsegment*)realloc(ps.segments, NVGsegment.sizeof*csegments); 6559 if (segments is null) assert(0, "NanoVega: out of memory"); 6560 ps.segments = segments; 6561 ps.csegments = csegments; 6562 } 6563 6564 if (psp.firstSegment == -1) psp.firstSegment = ps.nsegments; 6565 6566 seg = &ps.segments[ps.nsegments]; 6567 ++ps.nsegments; 6568 seg.firstPoint = firstPoint; 6569 seg.type = cast(short)type; 6570 seg.flags = flags; 6571 ++psp.nsegments; 6572 6573 nvg__segmentDir(ps, psp, seg, 0, seg.startDir); 6574 nvg__segmentDir(ps, psp, seg, 1, seg.endDir); 6575 } 6576 6577 void nvg__segmentDir (NVGpickScene* ps, NVGpickSubPath* psp, NVGsegment* seg, float t, ref float[2] d) { 6578 const(float)* points = &ps.points[seg.firstPoint*2]; 6579 immutable float x0 = points[0*2+0], x1 = points[1*2+0]; 6580 immutable float y0 = points[0*2+1], y1 = points[1*2+1]; 6581 switch (seg.type) { 6582 case Command.LineTo: 6583 d.ptr[0] = x1-x0; 6584 d.ptr[1] = y1-y0; 6585 nvg__normalize(&d.ptr[0], &d.ptr[1]); 6586 break; 6587 case Command.BezierTo: 6588 immutable float x2 = points[2*2+0]; 6589 immutable float y2 = points[2*2+1]; 6590 immutable float x3 = points[3*2+0]; 6591 immutable float y3 = points[3*2+1]; 6592 6593 immutable float omt = 1.0f-t; 6594 immutable float omt2 = omt*omt; 6595 immutable float t2 = t*t; 6596 6597 d.ptr[0] = 6598 3.0f*omt2*(x1-x0)+ 6599 6.0f*omt*t*(x2-x1)+ 6600 3.0f*t2*(x3-x2); 6601 6602 d.ptr[1] = 6603 3.0f*omt2*(y1-y0)+ 6604 6.0f*omt*t*(y2-y1)+ 6605 3.0f*t2*(y3-y2); 6606 6607 nvg__normalize(&d.ptr[0], &d.ptr[1]); 6608 break; 6609 default: 6610 break; 6611 } 6612 } 6613 6614 void nvg__pickSubPathAddFillSupports (NVGpickScene* ps, NVGpickSubPath* psp) { 6615 if (psp.firstSegment == -1) return; 6616 NVGsegment* segments = &ps.segments[psp.firstSegment]; 6617 for (int s = 0; s < psp.nsegments; ++s) { 6618 NVGsegment* seg = &segments[s]; 6619 const(float)* points = &ps.points[seg.firstPoint*2]; 6620 if (seg.type == Command.LineTo) { 6621 nvg__initBounds(seg.bounds); 6622 nvg__expandBounds(seg.bounds, points, 2); 6623 } else { 6624 nvg__bezierBounds(points, seg.bounds); 6625 } 6626 } 6627 } 6628 6629 void nvg__pickSubPathAddStrokeSupports (NVGpickScene* ps, NVGpickSubPath* psp, float strokeWidth, int lineCap, int lineJoin, float miterLimit) { 6630 if (psp.firstSegment == -1) return; 6631 immutable bool closed = psp.closed; 6632 const(float)* points = ps.points; 6633 NVGsegment* seg = null; 6634 NVGsegment* segments = &ps.segments[psp.firstSegment]; 6635 int nsegments = psp.nsegments; 6636 NVGsegment* prevseg = (closed ? &segments[psp.nsegments-1] : null); 6637 6638 int ns = 0; // nsupports 6639 float[32] supportingPoints = void; 6640 int firstPoint, lastPoint; 6641 6642 if (!closed) { 6643 segments[0].flags |= NVGSegmentFlags.Cap; 6644 segments[nsegments-1].flags |= NVGSegmentFlags.Endcap; 6645 } 6646 6647 for (int s = 0; s < nsegments; ++s) { 6648 seg = &segments[s]; 6649 nvg__initBounds(seg.bounds); 6650 6651 firstPoint = seg.firstPoint*2; 6652 lastPoint = firstPoint+(seg.type == Command.LineTo ? 2 : 6); 6653 6654 ns = 0; 6655 6656 // First two supporting points are either side of the start point 6657 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[1]*strokeWidth; 6658 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.startDir.ptr[0]*strokeWidth; 6659 6660 supportingPoints.ptr[ns++] = points[firstPoint]+seg.startDir.ptr[1]*strokeWidth; 6661 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[0]*strokeWidth; 6662 6663 // Second two supporting points are either side of the end point 6664 supportingPoints.ptr[ns++] = points[lastPoint]-seg.endDir.ptr[1]*strokeWidth; 6665 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[0]*strokeWidth; 6666 6667 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[1]*strokeWidth; 6668 supportingPoints.ptr[ns++] = points[lastPoint+1]-seg.endDir.ptr[0]*strokeWidth; 6669 6670 if ((seg.flags&NVGSegmentFlags.Corner) && prevseg !is null) { 6671 seg.miterDir.ptr[0] = 0.5f*(-prevseg.endDir.ptr[1]-seg.startDir.ptr[1]); 6672 seg.miterDir.ptr[1] = 0.5f*(prevseg.endDir.ptr[0]+seg.startDir.ptr[0]); 6673 6674 immutable float M2 = seg.miterDir.ptr[0]*seg.miterDir.ptr[0]+seg.miterDir.ptr[1]*seg.miterDir.ptr[1]; 6675 6676 if (M2 > 0.000001f) { 6677 float scale = 1.0f/M2; 6678 if (scale > 600.0f) scale = 600.0f; 6679 seg.miterDir.ptr[0] *= scale; 6680 seg.miterDir.ptr[1] *= scale; 6681 } 6682 6683 //NVG_PICK_DEBUG_VECTOR_SCALE(&points[firstPoint], seg.miterDir, 10); 6684 6685 // Add an additional support at the corner on the other line 6686 supportingPoints.ptr[ns++] = points[firstPoint]-prevseg.endDir.ptr[1]*strokeWidth; 6687 supportingPoints.ptr[ns++] = points[firstPoint+1]+prevseg.endDir.ptr[0]*strokeWidth; 6688 6689 if (lineJoin == NVGLineCap.Miter || lineJoin == NVGLineCap.Bevel) { 6690 // Set a corner as beveled if the join type is bevel or mitered and 6691 // miterLimit is hit. 6692 if (lineJoin == NVGLineCap.Bevel || (M2*miterLimit*miterLimit) < 1.0f) { 6693 seg.flags |= NVGSegmentFlags.Bevel; 6694 } else { 6695 // Corner is mitered - add miter point as a support 6696 supportingPoints.ptr[ns++] = points[firstPoint]+seg.miterDir.ptr[0]*strokeWidth; 6697 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.miterDir.ptr[1]*strokeWidth; 6698 } 6699 } else if (lineJoin == NVGLineCap.Round) { 6700 // ... and at the midpoint of the corner arc 6701 float[2] vertexN = [ -seg.startDir.ptr[0]+prevseg.endDir.ptr[0], -seg.startDir.ptr[1]+prevseg.endDir.ptr[1] ]; 6702 nvg__normalize(&vertexN[0], &vertexN[1]); 6703 6704 supportingPoints.ptr[ns++] = points[firstPoint]+vertexN[0]*strokeWidth; 6705 supportingPoints.ptr[ns++] = points[firstPoint+1]+vertexN[1]*strokeWidth; 6706 } 6707 } 6708 6709 if (seg.flags&NVGSegmentFlags.Cap) { 6710 switch (lineCap) { 6711 case NVGLineCap.Butt: 6712 // supports for butt already added 6713 break; 6714 case NVGLineCap.Square: 6715 // square cap supports are just the original two supports moved out along the direction 6716 supportingPoints.ptr[ns++] = supportingPoints.ptr[0]-seg.startDir.ptr[0]*strokeWidth; 6717 supportingPoints.ptr[ns++] = supportingPoints.ptr[1]-seg.startDir.ptr[1]*strokeWidth; 6718 supportingPoints.ptr[ns++] = supportingPoints.ptr[2]-seg.startDir.ptr[0]*strokeWidth; 6719 supportingPoints.ptr[ns++] = supportingPoints.ptr[3]-seg.startDir.ptr[1]*strokeWidth; 6720 break; 6721 case NVGLineCap.Round: 6722 // add one additional support for the round cap along the dir 6723 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[0]*strokeWidth; 6724 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[1]*strokeWidth; 6725 break; 6726 default: 6727 break; 6728 } 6729 } 6730 6731 if (seg.flags&NVGSegmentFlags.Endcap) { 6732 // end supporting points, either side of line 6733 int end = 4; 6734 switch(lineCap) { 6735 case NVGLineCap.Butt: 6736 // supports for butt already added 6737 break; 6738 case NVGLineCap.Square: 6739 // square cap supports are just the original two supports moved out along the direction 6740 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+0]+seg.endDir.ptr[0]*strokeWidth; 6741 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+1]+seg.endDir.ptr[1]*strokeWidth; 6742 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+2]+seg.endDir.ptr[0]*strokeWidth; 6743 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+3]+seg.endDir.ptr[1]*strokeWidth; 6744 break; 6745 case NVGLineCap.Round: 6746 // add one additional support for the round cap along the dir 6747 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[0]*strokeWidth; 6748 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[1]*strokeWidth; 6749 break; 6750 default: 6751 break; 6752 } 6753 } 6754 6755 nvg__expandBounds(seg.bounds, supportingPoints.ptr, ns/2); 6756 6757 prevseg = seg; 6758 } 6759 } 6760 6761 NVGpickPath* nvg__pickPathCreate (NVGContext context, const(float)[] acommands, int id, bool forStroke) { 6762 NVGpickScene* ps = nvg__pickSceneGet(context); 6763 if (ps is null) return null; 6764 6765 int i = 0; 6766 6767 int ncommands = cast(int)acommands.length; 6768 const(float)* commands = acommands.ptr; 6769 6770 NVGpickPath* pp = null; 6771 NVGpickSubPath* psp = null; 6772 float[2] start = void; 6773 int firstPoint; 6774 6775 //bool hasHoles = false; 6776 NVGpickSubPath* prev = null; 6777 6778 float[8] points = void; 6779 float[2] inflections = void; 6780 int ninflections = 0; 6781 6782 NVGstate* state = nvg__getState(context); 6783 float[4] totalBounds = void; 6784 NVGsegment* segments = null; 6785 const(NVGsegment)* seg = null; 6786 NVGpickSubPath *curpsp; 6787 6788 pp = nvg__allocPickPath(ps); 6789 if (pp is null) return null; 6790 6791 pp.id = id; 6792 6793 bool hasPoints = false; 6794 6795 void closeIt () { 6796 if (psp is null || !hasPoints) return; 6797 if (ps.points[(ps.npoints-1)*2] != start.ptr[0] || ps.points[(ps.npoints-1)*2+1] != start.ptr[1]) { 6798 firstPoint = nvg__pickSceneAddPoints(ps, start.ptr, 1); 6799 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, Command.LineTo, NVGSegmentFlags.Corner); 6800 } 6801 psp.closed = true; 6802 } 6803 6804 while (i < ncommands) { 6805 int cmd = cast(int)commands[i++]; 6806 switch (cmd) { 6807 case Command.MoveTo: // one coordinate pair 6808 const(float)* tfxy = commands+i; 6809 i += 2; 6810 6811 // new starting point 6812 start.ptr[0..2] = tfxy[0..2]; 6813 6814 // start a new path for each sub path to handle sub paths that intersect other sub paths 6815 prev = psp; 6816 psp = nvg__allocPickSubPath(ps); 6817 if (psp is null) { psp = prev; break; } 6818 psp.firstSegment = -1; 6819 psp.winding = NVGSolidity.Solid; 6820 psp.next = prev; 6821 6822 nvg__pickSceneAddPoints(ps, tfxy, 1); 6823 hasPoints = true; 6824 break; 6825 case Command.LineTo: // one coordinate pair 6826 const(float)* tfxy = commands+i; 6827 i += 2; 6828 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 1); 6829 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner); 6830 hasPoints = true; 6831 break; 6832 case Command.BezierTo: // three coordinate pairs 6833 const(float)* tfxy = commands+i; 6834 i += 3*2; 6835 6836 // Split the curve at it's dx==0 or dy==0 inflection points. 6837 // Thus: 6838 // A horizontal line only ever interects the curves once. 6839 // and 6840 // Finding the closest point on any curve converges more reliably. 6841 6842 // NOTE: We could just split on dy==0 here. 6843 6844 memcpy(&points.ptr[0], &ps.points[(ps.npoints-1)*2], float.sizeof*2); 6845 memcpy(&points.ptr[2], tfxy, float.sizeof*2*3); 6846 6847 ninflections = 0; 6848 nvg__bezierInflections(points.ptr, 1, &ninflections, inflections.ptr); 6849 nvg__bezierInflections(points.ptr, 0, &ninflections, inflections.ptr); 6850 6851 if (ninflections) { 6852 float previnfl = 0; 6853 float[8] pointsA = void, pointsB = void; 6854 6855 nvg__smallsort(inflections.ptr, ninflections); 6856 6857 for (int infl = 0; infl < ninflections; ++infl) { 6858 if (nvg__absf(inflections.ptr[infl]-previnfl) < NVGPickEPS) continue; 6859 6860 immutable float t = (inflections.ptr[infl]-previnfl)*(1.0f/(1.0f-previnfl)); 6861 6862 previnfl = inflections.ptr[infl]; 6863 6864 nvg__splitBezier(points.ptr, t, pointsA.ptr, pointsB.ptr); 6865 6866 firstPoint = nvg__pickSceneAddPoints(ps, &pointsA.ptr[2], 3); 6867 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, (infl == 0) ? NVGSegmentFlags.Corner : 0); 6868 6869 memcpy(points.ptr, pointsB.ptr, float.sizeof*8); 6870 } 6871 6872 firstPoint = nvg__pickSceneAddPoints(ps, &pointsB.ptr[2], 3); 6873 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, 0); 6874 } else { 6875 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 3); 6876 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner); 6877 } 6878 hasPoints = true; 6879 break; 6880 case Command.Close: 6881 closeIt(); 6882 break; 6883 case Command.Winding: 6884 psp.winding = cast(short)cast(int)commands[i]; 6885 //if (psp.winding == NVGSolidity.Hole) hasHoles = true; 6886 i += 1; 6887 break; 6888 default: 6889 break; 6890 } 6891 } 6892 6893 // force-close filled paths 6894 if (psp !is null && !forStroke && hasPoints && !psp.closed) closeIt(); 6895 6896 pp.flags = (forStroke ? NVGPathFlags.Stroke : NVGPathFlags.Fill); 6897 pp.subPaths = psp; 6898 pp.strokeWidth = state.strokeWidth*0.5f; 6899 pp.miterLimit = state.miterLimit; 6900 pp.lineCap = cast(short)state.lineCap; 6901 pp.lineJoin = cast(short)state.lineJoin; 6902 pp.evenOddMode = nvg__getState(context).evenOddMode; 6903 6904 nvg__initBounds(totalBounds); 6905 6906 for (curpsp = psp; curpsp; curpsp = curpsp.next) { 6907 if (forStroke) { 6908 nvg__pickSubPathAddStrokeSupports(ps, curpsp, pp.strokeWidth, pp.lineCap, pp.lineJoin, pp.miterLimit); 6909 } else { 6910 nvg__pickSubPathAddFillSupports(ps, curpsp); 6911 } 6912 6913 if (curpsp.firstSegment == -1) continue; 6914 segments = &ps.segments[curpsp.firstSegment]; 6915 nvg__initBounds(curpsp.bounds); 6916 for (int s = 0; s < curpsp.nsegments; ++s) { 6917 seg = &segments[s]; 6918 //NVG_PICK_DEBUG_BOUNDS(seg.bounds); 6919 nvg__unionBounds(curpsp.bounds, seg.bounds); 6920 } 6921 6922 nvg__unionBounds(totalBounds, curpsp.bounds); 6923 } 6924 6925 // Store the scissor rect if present. 6926 if (state.scissor.extent.ptr[0] != -1.0f) { 6927 // Use points storage to store the scissor data 6928 pp.scissor = nvg__pickSceneAddPoints(ps, null, 4); 6929 float* scissor = &ps.points[pp.scissor*2]; 6930 6931 //memcpy(scissor, state.scissor.xform.ptr, 6*float.sizeof); 6932 scissor[0..6] = state.scissor.xform.mat[]; 6933 memcpy(scissor+6, state.scissor.extent.ptr, 2*float.sizeof); 6934 6935 pp.flags |= NVGPathFlags.Scissor; 6936 } 6937 6938 memcpy(pp.bounds.ptr, totalBounds.ptr, float.sizeof*4); 6939 6940 return pp; 6941 } 6942 6943 6944 // Struct management 6945 NVGpickPath* nvg__allocPickPath (NVGpickScene* ps) { 6946 NVGpickPath* pp = ps.freePaths; 6947 if (pp !is null) { 6948 ps.freePaths = pp.next; 6949 } else { 6950 pp = cast(NVGpickPath*)malloc(NVGpickPath.sizeof); 6951 } 6952 memset(pp, 0, NVGpickPath.sizeof); 6953 return pp; 6954 } 6955 6956 // Put a pick path and any sub paths (back) to the free lists. 6957 void nvg__freePickPath (NVGpickScene* ps, NVGpickPath* pp) { 6958 // Add all sub paths to the sub path free list. 6959 // Finds the end of the path sub paths, links that to the current 6960 // sub path free list head and replaces the head ptr with the 6961 // head path sub path entry. 6962 NVGpickSubPath* psp = null; 6963 for (psp = pp.subPaths; psp !is null && psp.next !is null; psp = psp.next) {} 6964 6965 if (psp) { 6966 psp.next = ps.freeSubPaths; 6967 ps.freeSubPaths = pp.subPaths; 6968 } 6969 pp.subPaths = null; 6970 6971 // Add the path to the path freelist 6972 pp.next = ps.freePaths; 6973 ps.freePaths = pp; 6974 if (pp.next is null) ps.lastPath = pp; 6975 } 6976 6977 NVGpickSubPath* nvg__allocPickSubPath (NVGpickScene* ps) { 6978 NVGpickSubPath* psp = ps.freeSubPaths; 6979 if (psp !is null) { 6980 ps.freeSubPaths = psp.next; 6981 } else { 6982 psp = cast(NVGpickSubPath*)malloc(NVGpickSubPath.sizeof); 6983 if (psp is null) return null; 6984 } 6985 memset(psp, 0, NVGpickSubPath.sizeof); 6986 return psp; 6987 } 6988 6989 void nvg__returnPickSubPath (NVGpickScene* ps, NVGpickSubPath* psp) { 6990 psp.next = ps.freeSubPaths; 6991 ps.freeSubPaths = psp; 6992 } 6993 6994 NVGpickScene* nvg__allocPickScene () { 6995 NVGpickScene* ps = cast(NVGpickScene*)malloc(NVGpickScene.sizeof); 6996 if (ps is null) return null; 6997 memset(ps, 0, NVGpickScene.sizeof); 6998 ps.nlevels = 5; 6999 return ps; 7000 } 7001 7002 void nvg__deletePickScene (NVGpickScene* ps) { 7003 NVGpickPath* pp; 7004 NVGpickSubPath* psp; 7005 7006 // Add all paths (and thus sub paths) to the free list(s). 7007 while (ps.paths !is null) { 7008 pp = ps.paths.next; 7009 nvg__freePickPath(ps, ps.paths); 7010 ps.paths = pp; 7011 } 7012 7013 // Delete all paths 7014 while (ps.freePaths !is null) { 7015 pp = ps.freePaths; 7016 ps.freePaths = pp.next; 7017 while (pp.subPaths !is null) { 7018 psp = pp.subPaths; 7019 pp.subPaths = psp.next; 7020 free(psp); 7021 } 7022 free(pp); 7023 } 7024 7025 // Delete all sub paths 7026 while (ps.freeSubPaths !is null) { 7027 psp = ps.freeSubPaths.next; 7028 free(ps.freeSubPaths); 7029 ps.freeSubPaths = psp; 7030 } 7031 7032 ps.npoints = 0; 7033 ps.nsegments = 0; 7034 7035 if (ps.levels !is null) { 7036 free(ps.levels[0]); 7037 free(ps.levels); 7038 } 7039 7040 if (ps.picked !is null) free(ps.picked); 7041 if (ps.points !is null) free(ps.points); 7042 if (ps.segments !is null) free(ps.segments); 7043 7044 free(ps); 7045 } 7046 7047 NVGpickScene* nvg__pickSceneGet (NVGContext ctx) { 7048 if (ctx.pickScene is null) ctx.pickScene = nvg__allocPickScene(); 7049 return ctx.pickScene; 7050 } 7051 7052 7053 // Applies Casteljau's algorithm to a cubic bezier for a given parameter t 7054 // points is 4 points (8 floats) 7055 // lvl1 is 3 points (6 floats) 7056 // lvl2 is 2 points (4 floats) 7057 // lvl3 is 1 point (2 floats) 7058 void nvg__casteljau (const(float)* points, float t, float* lvl1, float* lvl2, float* lvl3) { 7059 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0; 7060 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1; 7061 7062 // Level 1 7063 lvl1[x0] = (points[x1]-points[x0])*t+points[x0]; 7064 lvl1[y0] = (points[y1]-points[y0])*t+points[y0]; 7065 7066 lvl1[x1] = (points[x2]-points[x1])*t+points[x1]; 7067 lvl1[y1] = (points[y2]-points[y1])*t+points[y1]; 7068 7069 lvl1[x2] = (points[x3]-points[x2])*t+points[x2]; 7070 lvl1[y2] = (points[y3]-points[y2])*t+points[y2]; 7071 7072 // Level 2 7073 lvl2[x0] = (lvl1[x1]-lvl1[x0])*t+lvl1[x0]; 7074 lvl2[y0] = (lvl1[y1]-lvl1[y0])*t+lvl1[y0]; 7075 7076 lvl2[x1] = (lvl1[x2]-lvl1[x1])*t+lvl1[x1]; 7077 lvl2[y1] = (lvl1[y2]-lvl1[y1])*t+lvl1[y1]; 7078 7079 // Level 3 7080 lvl3[x0] = (lvl2[x1]-lvl2[x0])*t+lvl2[x0]; 7081 lvl3[y0] = (lvl2[y1]-lvl2[y0])*t+lvl2[y0]; 7082 } 7083 7084 // Calculates a point on a bezier at point t. 7085 void nvg__bezierEval (const(float)* points, float t, ref float[2] tpoint) { 7086 immutable float omt = 1-t; 7087 immutable float omt3 = omt*omt*omt; 7088 immutable float omt2 = omt*omt; 7089 immutable float t3 = t*t*t; 7090 immutable float t2 = t*t; 7091 7092 tpoint.ptr[0] = 7093 points[0]*omt3+ 7094 points[2]*3.0f*omt2*t+ 7095 points[4]*3.0f*omt*t2+ 7096 points[6]*t3; 7097 7098 tpoint.ptr[1] = 7099 points[1]*omt3+ 7100 points[3]*3.0f*omt2*t+ 7101 points[5]*3.0f*omt*t2+ 7102 points[7]*t3; 7103 } 7104 7105 // Splits a cubic bezier curve into two parts at point t. 7106 void nvg__splitBezier (const(float)* points, float t, float* pointsA, float* pointsB) { 7107 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0; 7108 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1; 7109 7110 float[6] lvl1 = void; 7111 float[4] lvl2 = void; 7112 float[2] lvl3 = void; 7113 7114 nvg__casteljau(points, t, lvl1.ptr, lvl2.ptr, lvl3.ptr); 7115 7116 // First half 7117 pointsA[x0] = points[x0]; 7118 pointsA[y0] = points[y0]; 7119 7120 pointsA[x1] = lvl1.ptr[x0]; 7121 pointsA[y1] = lvl1.ptr[y0]; 7122 7123 pointsA[x2] = lvl2.ptr[x0]; 7124 pointsA[y2] = lvl2.ptr[y0]; 7125 7126 pointsA[x3] = lvl3.ptr[x0]; 7127 pointsA[y3] = lvl3.ptr[y0]; 7128 7129 // Second half 7130 pointsB[x0] = lvl3.ptr[x0]; 7131 pointsB[y0] = lvl3.ptr[y0]; 7132 7133 pointsB[x1] = lvl2.ptr[x1]; 7134 pointsB[y1] = lvl2.ptr[y1]; 7135 7136 pointsB[x2] = lvl1.ptr[x2]; 7137 pointsB[y2] = lvl1.ptr[y2]; 7138 7139 pointsB[x3] = points[x3]; 7140 pointsB[y3] = points[y3]; 7141 } 7142 7143 // Calculates the inflection points in coordinate coord (X = 0, Y = 1) of a cubic bezier. 7144 // Appends any found inflection points to the array inflections and increments *ninflections. 7145 // So finds the parameters where dx/dt or dy/dt is 0 7146 void nvg__bezierInflections (const(float)* points, int coord, int* ninflections, float* inflections) { 7147 immutable float v0 = points[0*2+coord], v1 = points[1*2+coord], v2 = points[2*2+coord], v3 = points[3*2+coord]; 7148 float[2] t = void; 7149 int nvalid = *ninflections; 7150 7151 immutable float a = 3.0f*( -v0+3.0f*v1-3.0f*v2+v3 ); 7152 immutable float b = 6.0f*( v0-2.0f*v1+v2 ); 7153 immutable float c = 3.0f*( v1-v0 ); 7154 7155 float d = b*b-4.0f*a*c; 7156 if (nvg__absf(d-0.0f) < NVGPickEPS) { 7157 // Zero or one root 7158 t.ptr[0] = -b/2.0f*a; 7159 if (t.ptr[0] > NVGPickEPS && t.ptr[0] < (1.0f-NVGPickEPS)) { 7160 inflections[nvalid] = t.ptr[0]; 7161 ++nvalid; 7162 } 7163 } else if (d > NVGPickEPS) { 7164 // zero, one or two roots 7165 d = nvg__sqrtf(d); 7166 7167 t.ptr[0] = (-b+d)/(2.0f*a); 7168 t.ptr[1] = (-b-d)/(2.0f*a); 7169 7170 for (int i = 0; i < 2; ++i) { 7171 if (t.ptr[i] > NVGPickEPS && t.ptr[i] < (1.0f-NVGPickEPS)) { 7172 inflections[nvalid] = t.ptr[i]; 7173 ++nvalid; 7174 } 7175 } 7176 } else { 7177 // zero roots 7178 } 7179 7180 *ninflections = nvalid; 7181 } 7182 7183 // Sort a small number of floats in ascending order (0 < n < 6) 7184 void nvg__smallsort (float* values, int n) { 7185 bool bSwapped = true; 7186 for (int j = 0; j < n-1 && bSwapped; ++j) { 7187 bSwapped = false; 7188 for (int i = 0; i < n-1; ++i) { 7189 if (values[i] > values[i+1]) { 7190 auto tmp = values[i]; 7191 values[i] = values[i+1]; 7192 values[i+1] = tmp; 7193 } 7194 } 7195 } 7196 } 7197 7198 // Calculates the bounding rect of a given cubic bezier curve. 7199 void nvg__bezierBounds (const(float)* points, ref float[4] bounds) { 7200 float[4] inflections = void; 7201 int ninflections = 0; 7202 float[2] tpoint = void; 7203 7204 nvg__initBounds(bounds); 7205 7206 // Include start and end points in bounds 7207 nvg__expandBounds(bounds, &points[0], 1); 7208 nvg__expandBounds(bounds, &points[6], 1); 7209 7210 // Calculate dx==0 and dy==0 inflection points and add them to the bounds 7211 7212 nvg__bezierInflections(points, 0, &ninflections, inflections.ptr); 7213 nvg__bezierInflections(points, 1, &ninflections, inflections.ptr); 7214 7215 foreach (immutable int i; 0..ninflections) { 7216 nvg__bezierEval(points, inflections[i], tpoint); 7217 nvg__expandBounds(bounds, tpoint.ptr, 1); 7218 } 7219 } 7220 7221 // Checks to see if a line originating from x,y along the +ve x axis 7222 // intersects the given line (points[0],points[1]) -> (points[2], points[3]). 7223 // Returns `true` on intersection. 7224 // Horizontal lines are never hit. 7225 bool nvg__intersectLine (const(float)* points, float x, float y) { 7226 immutable float x1 = points[0]; 7227 immutable float y1 = points[1]; 7228 immutable float x2 = points[2]; 7229 immutable float y2 = points[3]; 7230 immutable float d = y2-y1; 7231 if (d > NVGPickEPS || d < -NVGPickEPS) { 7232 immutable float s = (x2-x1)/d; 7233 immutable float lineX = x1+(y-y1)*s; 7234 return (lineX > x); 7235 } else { 7236 return false; 7237 } 7238 } 7239 7240 // Checks to see if a line originating from x,y along the +ve x axis intersects the given bezier. 7241 // It is assumed that the line originates from within the bounding box of 7242 // the bezier and that the curve has no dy=0 inflection points. 7243 // Returns the number of intersections found (which is either 1 or 0). 7244 int nvg__intersectBezier (const(float)* points, float x, float y) { 7245 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0]; 7246 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1]; 7247 7248 if (y0 == y1 && y1 == y2 && y2 == y3) return 0; 7249 7250 // Initial t guess 7251 float t = void; 7252 if (y3 != y0) t = (y-y0)/(y3-y0); 7253 else if (x3 != x0) t = (x-x0)/(x3-x0); 7254 else t = 0.5f; 7255 7256 // A few Newton iterations 7257 for (int iter = 0; iter < 6; ++iter) { 7258 immutable float omt = 1-t; 7259 immutable float omt2 = omt*omt; 7260 immutable float t2 = t*t; 7261 immutable float omt3 = omt2*omt; 7262 immutable float t3 = t2*t; 7263 7264 immutable float ty = y0*omt3 + 7265 y1*3.0f*omt2*t + 7266 y2*3.0f*omt*t2 + 7267 y3*t3; 7268 7269 // Newton iteration 7270 immutable float dty = 3.0f*omt2*(y1-y0) + 7271 6.0f*omt*t*(y2-y1) + 7272 3.0f*t2*(y3-y2); 7273 7274 // dty will never == 0 since: 7275 // Either omt, omt2 are zero OR t2 is zero 7276 // y0 != y1 != y2 != y3 (checked above) 7277 t = t-(ty-y)/dty; 7278 } 7279 7280 { 7281 immutable float omt = 1-t; 7282 immutable float omt2 = omt*omt; 7283 immutable float t2 = t*t; 7284 immutable float omt3 = omt2*omt; 7285 immutable float t3 = t2*t; 7286 7287 immutable float tx = 7288 x0*omt3+ 7289 x1*3.0f*omt2*t+ 7290 x2*3.0f*omt*t2+ 7291 x3*t3; 7292 7293 return (tx > x ? 1 : 0); 7294 } 7295 } 7296 7297 // Finds the closest point on a line to a given point 7298 void nvg__closestLine (const(float)* points, float x, float y, float* closest, float* ot) { 7299 immutable float x1 = points[0]; 7300 immutable float y1 = points[1]; 7301 immutable float x2 = points[2]; 7302 immutable float y2 = points[3]; 7303 immutable float pqx = x2-x1; 7304 immutable float pqz = y2-y1; 7305 immutable float dx = x-x1; 7306 immutable float dz = y-y1; 7307 immutable float d = pqx*pqx+pqz*pqz; 7308 float t = pqx*dx+pqz*dz; 7309 if (d > 0) t /= d; 7310 if (t < 0) t = 0; else if (t > 1) t = 1; 7311 closest[0] = x1+t*pqx; 7312 closest[1] = y1+t*pqz; 7313 *ot = t; 7314 } 7315 7316 // Finds the closest point on a curve for a given point (x,y). 7317 // Assumes that the curve has no dx==0 or dy==0 inflection points. 7318 void nvg__closestBezier (const(float)* points, float x, float y, float* closest, float *ot) { 7319 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0]; 7320 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1]; 7321 7322 // This assumes that the curve has no dy=0 inflection points. 7323 7324 // Initial t guess 7325 float t = 0.5f; 7326 7327 // A few Newton iterations 7328 for (int iter = 0; iter < 6; ++iter) { 7329 immutable float omt = 1-t; 7330 immutable float omt2 = omt*omt; 7331 immutable float t2 = t*t; 7332 immutable float omt3 = omt2*omt; 7333 immutable float t3 = t2*t; 7334 7335 immutable float ty = 7336 y0*omt3+ 7337 y1*3.0f*omt2*t+ 7338 y2*3.0f*omt*t2+ 7339 y3*t3; 7340 7341 immutable float tx = 7342 x0*omt3+ 7343 x1*3.0f*omt2*t+ 7344 x2*3.0f*omt*t2+ 7345 x3*t3; 7346 7347 // Newton iteration 7348 immutable float dty = 7349 3.0f*omt2*(y1-y0)+ 7350 6.0f*omt*t*(y2-y1)+ 7351 3.0f*t2*(y3-y2); 7352 7353 immutable float ddty = 7354 6.0f*omt*(y2-2.0f*y1+y0)+ 7355 6.0f*t*(y3-2.0f*y2+y1); 7356 7357 immutable float dtx = 7358 3.0f*omt2*(x1-x0)+ 7359 6.0f*omt*t*(x2-x1)+ 7360 3.0f*t2*(x3-x2); 7361 7362 immutable float ddtx = 7363 6.0f*omt*(x2-2.0f*x1+x0)+ 7364 6.0f*t*(x3-2.0f*x2+x1); 7365 7366 immutable float errorx = tx-x; 7367 immutable float errory = ty-y; 7368 7369 immutable float n = errorx*dtx+errory*dty; 7370 if (n == 0) break; 7371 7372 immutable float d = dtx*dtx+dty*dty+errorx*ddtx+errory*ddty; 7373 if (d != 0) t = t-n/d; else break; 7374 } 7375 7376 t = nvg__max(0, nvg__min(1.0, t)); 7377 *ot = t; 7378 { 7379 immutable float omt = 1-t; 7380 immutable float omt2 = omt*omt; 7381 immutable float t2 = t*t; 7382 immutable float omt3 = omt2*omt; 7383 immutable float t3 = t2*t; 7384 7385 immutable float ty = 7386 y0*omt3+ 7387 y1*3.0f*omt2*t+ 7388 y2*3.0f*omt*t2+ 7389 y3*t3; 7390 7391 immutable float tx = 7392 x0*omt3+ 7393 x1*3.0f*omt2*t+ 7394 x2*3.0f*omt*t2+ 7395 x3*t3; 7396 7397 closest[0] = tx; 7398 closest[1] = ty; 7399 } 7400 } 7401 7402 // Returns: 7403 // 1 If (x,y) is contained by the stroke of the path 7404 // 0 If (x,y) is not contained by the path. 7405 int nvg__pickSubPathStroke (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, float strokeWidth, int lineCap, int lineJoin) { 7406 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0; 7407 if (psp.firstSegment == -1) return 0; 7408 7409 float[2] closest = void; 7410 float[2] d = void; 7411 float t = void; 7412 7413 // trace a line from x,y out along the positive x axis and count the number of intersections 7414 int nsegments = psp.nsegments; 7415 const(NVGsegment)* seg = ps.segments+psp.firstSegment; 7416 const(NVGsegment)* prevseg = (psp.closed ? &ps.segments[psp.firstSegment+nsegments-1] : null); 7417 immutable float strokeWidthSqd = strokeWidth*strokeWidth; 7418 7419 for (int s = 0; s < nsegments; ++s, prevseg = seg, ++seg) { 7420 if (nvg__pointInBounds(x, y, seg.bounds)) { 7421 // Line potentially hits stroke. 7422 switch (seg.type) { 7423 case Command.LineTo: 7424 nvg__closestLine(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t); 7425 break; 7426 case Command.BezierTo: 7427 nvg__closestBezier(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t); 7428 break; 7429 default: 7430 continue; 7431 } 7432 7433 d.ptr[0] = x-closest.ptr[0]; 7434 d.ptr[1] = y-closest.ptr[1]; 7435 7436 if ((t >= NVGPickEPS && t <= 1.0f-NVGPickEPS) || 7437 (seg.flags&(NVGSegmentFlags.Corner|NVGSegmentFlags.Cap|NVGSegmentFlags.Endcap)) == 0 || 7438 (lineJoin == NVGLineCap.Round)) 7439 { 7440 // Closest point is in the middle of the line/curve, at a rounded join/cap 7441 // or at a smooth join 7442 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7443 if (distSqd < strokeWidthSqd) return 1; 7444 } else if ((t > 1.0f-NVGPickEPS && (seg.flags&NVGSegmentFlags.Endcap)) || 7445 (t < NVGPickEPS && (seg.flags&NVGSegmentFlags.Cap))) { 7446 switch (lineCap) { 7447 case NVGLineCap.Butt: 7448 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7449 immutable float dirD = (t < NVGPickEPS ? 7450 -(d.ptr[0]*seg.startDir.ptr[0]+d.ptr[1]*seg.startDir.ptr[1]) : 7451 d.ptr[0]*seg.endDir.ptr[0]+d.ptr[1]*seg.endDir.ptr[1]); 7452 if (dirD < -NVGPickEPS && distSqd < strokeWidthSqd) return 1; 7453 break; 7454 case NVGLineCap.Square: 7455 if (nvg__absf(d.ptr[0]) < strokeWidth && nvg__absf(d.ptr[1]) < strokeWidth) return 1; 7456 break; 7457 case NVGLineCap.Round: 7458 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7459 if (distSqd < strokeWidthSqd) return 1; 7460 break; 7461 default: 7462 break; 7463 } 7464 } else if (seg.flags&NVGSegmentFlags.Corner) { 7465 // Closest point is at a corner 7466 const(NVGsegment)* seg0, seg1; 7467 7468 if (t < NVGPickEPS) { 7469 seg0 = prevseg; 7470 seg1 = seg; 7471 } else { 7472 seg0 = seg; 7473 seg1 = (s == nsegments-1 ? &ps.segments[psp.firstSegment] : seg+1); 7474 } 7475 7476 if (!(seg1.flags&NVGSegmentFlags.Bevel)) { 7477 immutable float prevNDist = -seg0.endDir.ptr[1]*d.ptr[0]+seg0.endDir.ptr[0]*d.ptr[1]; 7478 immutable float curNDist = seg1.startDir.ptr[1]*d.ptr[0]-seg1.startDir.ptr[0]*d.ptr[1]; 7479 if (nvg__absf(prevNDist) < strokeWidth && nvg__absf(curNDist) < strokeWidth) return 1; 7480 } else { 7481 d.ptr[0] -= -seg1.startDir.ptr[1]*strokeWidth; 7482 d.ptr[1] -= +seg1.startDir.ptr[0]*strokeWidth; 7483 if (seg1.miterDir.ptr[0]*d.ptr[0]+seg1.miterDir.ptr[1]*d.ptr[1] < 0) return 1; 7484 } 7485 } 7486 } 7487 } 7488 7489 return 0; 7490 } 7491 7492 // Returns: 7493 // 1 If (x,y) is contained by the path and the path is solid. 7494 // -1 If (x,y) is contained by the path and the path is a hole. 7495 // 0 If (x,y) is not contained by the path. 7496 int nvg__pickSubPath (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, bool evenOddMode) { 7497 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0; 7498 if (psp.firstSegment == -1) return 0; 7499 7500 const(NVGsegment)* seg = &ps.segments[psp.firstSegment]; 7501 int nsegments = psp.nsegments; 7502 int nintersections = 0; 7503 7504 // trace a line from x,y out along the positive x axis and count the number of intersections 7505 for (int s = 0; s < nsegments; ++s, ++seg) { 7506 if ((seg.bounds.ptr[1]-NVGPickEPS) < y && 7507 (seg.bounds.ptr[3]-NVGPickEPS) > y && 7508 seg.bounds.ptr[2] > x) 7509 { 7510 // Line hits the box. 7511 switch (seg.type) { 7512 case Command.LineTo: 7513 if (seg.bounds.ptr[0] > x) { 7514 // line originates outside the box 7515 ++nintersections; 7516 } else { 7517 // line originates inside the box 7518 nintersections += nvg__intersectLine(&ps.points[seg.firstPoint*2], x, y); 7519 } 7520 break; 7521 case Command.BezierTo: 7522 if (seg.bounds.ptr[0] > x) { 7523 // line originates outside the box 7524 ++nintersections; 7525 } else { 7526 // line originates inside the box 7527 nintersections += nvg__intersectBezier(&ps.points[seg.firstPoint*2], x, y); 7528 } 7529 break; 7530 default: 7531 break; 7532 } 7533 } 7534 } 7535 7536 if (evenOddMode) { 7537 return nintersections; 7538 } else { 7539 return (nintersections&1 ? (psp.winding == NVGSolidity.Solid ? 1 : -1) : 0); 7540 } 7541 } 7542 7543 bool nvg__pickPath (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) { 7544 int pickCount = 0; 7545 const(NVGpickSubPath)* psp = pp.subPaths; 7546 while (psp !is null) { 7547 pickCount += nvg__pickSubPath(ps, psp, x, y, pp.evenOddMode); 7548 psp = psp.next; 7549 } 7550 return ((pp.evenOddMode ? pickCount&1 : pickCount) != 0); 7551 } 7552 7553 bool nvg__pickPathStroke (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) { 7554 const(NVGpickSubPath)* psp = pp.subPaths; 7555 while (psp !is null) { 7556 if (nvg__pickSubPathStroke(ps, psp, x, y, pp.strokeWidth, pp.lineCap, pp.lineJoin)) return true; 7557 psp = psp.next; 7558 } 7559 return false; 7560 } 7561 7562 bool nvg__pickPathTestBounds (NVGContext ctx, const NVGpickScene* ps, const NVGpickPath* pp, float x, float y) { 7563 if (nvg__pointInBounds(x, y, pp.bounds)) { 7564 //{ import core.stdc.stdio; printf(" (0): in bounds!\n"); } 7565 if (pp.flags&NVGPathFlags.Scissor) { 7566 const(float)* scissor = &ps.points[pp.scissor*2]; 7567 // untransform scissor translation 7568 float stx = void, sty = void; 7569 ctx.gpuUntransformPoint(&stx, &sty, scissor[4], scissor[5]); 7570 immutable float rx = x-stx; 7571 immutable float ry = y-sty; 7572 //{ 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]); } 7573 if (nvg__absf((scissor[0]*rx)+(scissor[1]*ry)) > scissor[6] || 7574 nvg__absf((scissor[2]*rx)+(scissor[3]*ry)) > scissor[7]) 7575 { 7576 //{ import core.stdc.stdio; printf(" (1): scissor reject!\n"); } 7577 return false; 7578 } 7579 } 7580 return true; 7581 } 7582 return false; 7583 } 7584 7585 int nvg__countBitsUsed (uint v) pure { 7586 pragma(inline, true); 7587 import core.bitop : bsr; 7588 return (v != 0 ? bsr(v)+1 : 0); 7589 } 7590 7591 void nvg__pickSceneInsert (NVGpickScene* ps, NVGpickPath* pp) { 7592 if (ps is null || pp is null) return; 7593 7594 int[4] cellbounds; 7595 int base = ps.nlevels-1; 7596 int level; 7597 int levelwidth; 7598 int levelshift; 7599 int levelx; 7600 int levely; 7601 NVGpickPath** cell = null; 7602 7603 // Bit tricks for inserting into an implicit quadtree. 7604 7605 // Calc bounds of path in cells at the lowest level 7606 cellbounds.ptr[0] = cast(int)(pp.bounds.ptr[0]/ps.xdim); 7607 cellbounds.ptr[1] = cast(int)(pp.bounds.ptr[1]/ps.ydim); 7608 cellbounds.ptr[2] = cast(int)(pp.bounds.ptr[2]/ps.xdim); 7609 cellbounds.ptr[3] = cast(int)(pp.bounds.ptr[3]/ps.ydim); 7610 7611 // Find which bits differ between the min/max x/y coords 7612 cellbounds.ptr[0] ^= cellbounds.ptr[2]; 7613 cellbounds.ptr[1] ^= cellbounds.ptr[3]; 7614 7615 // Use the number of bits used (countBitsUsed(x) == sizeof(int) * 8 - clz(x); 7616 // to calculate the level to insert at (the level at which the bounds fit in a single cell) 7617 level = nvg__min(base-nvg__countBitsUsed(cellbounds.ptr[0]), base-nvg__countBitsUsed(cellbounds.ptr[1])); 7618 if (level < 0) level = 0; 7619 //{ 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]); } 7620 //level = 0; 7621 7622 // Find the correct cell in the chosen level, clamping to the edges. 7623 levelwidth = 1<<level; 7624 levelshift = (ps.nlevels-level)-1; 7625 levelx = nvg__clamp(cellbounds.ptr[2]>>levelshift, 0, levelwidth-1); 7626 levely = nvg__clamp(cellbounds.ptr[3]>>levelshift, 0, levelwidth-1); 7627 7628 // Insert the path into the linked list at that cell. 7629 cell = &ps.levels[level][levely*levelwidth+levelx]; 7630 7631 pp.cellnext = *cell; 7632 *cell = pp; 7633 7634 if (ps.paths is null) ps.lastPath = pp; 7635 pp.next = ps.paths; 7636 ps.paths = pp; 7637 7638 // Store the order (depth) of the path for picking ops. 7639 pp.order = cast(short)ps.npaths; 7640 ++ps.npaths; 7641 } 7642 7643 void nvg__pickBeginFrame (NVGContext ctx, int width, int height) { 7644 NVGpickScene* ps = nvg__pickSceneGet(ctx); 7645 7646 //NVG_PICK_DEBUG_NEWFRAME(); 7647 7648 // Return all paths & sub paths from last frame to the free list 7649 while (ps.paths !is null) { 7650 NVGpickPath* pp = ps.paths.next; 7651 nvg__freePickPath(ps, ps.paths); 7652 ps.paths = pp; 7653 } 7654 7655 ps.paths = null; 7656 ps.npaths = 0; 7657 7658 // Store the screen metrics for the quadtree 7659 ps.width = width; 7660 ps.height = height; 7661 7662 immutable float lowestSubDiv = cast(float)(1<<(ps.nlevels-1)); 7663 ps.xdim = cast(float)width/lowestSubDiv; 7664 ps.ydim = cast(float)height/lowestSubDiv; 7665 7666 // Allocate the quadtree if required. 7667 if (ps.levels is null) { 7668 int ncells = 1; 7669 7670 ps.levels = cast(NVGpickPath***)malloc((NVGpickPath**).sizeof*ps.nlevels); 7671 for (int l = 0; l < ps.nlevels; ++l) { 7672 int leveldim = 1<<l; 7673 ncells += leveldim*leveldim; 7674 } 7675 7676 ps.levels[0] = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ncells); 7677 7678 int cell = 1; 7679 for (int l = 1; l < ps.nlevels; ++l) { 7680 ps.levels[l] = &ps.levels[0][cell]; 7681 int leveldim = 1<<l; 7682 cell += leveldim*leveldim; 7683 } 7684 7685 ps.ncells = ncells; 7686 } 7687 memset(ps.levels[0], 0, ps.ncells*(NVGpickPath*).sizeof); 7688 7689 // Allocate temporary storage for nvgHitTestAll results if required. 7690 if (ps.picked is null) { 7691 ps.cpicked = 16; 7692 ps.picked = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ps.cpicked); 7693 } 7694 7695 ps.npoints = 0; 7696 ps.nsegments = 0; 7697 } 7698 } // nothrow @trusted @nogc 7699 7700 7701 /// Return outline of the current path. Returned outline is not flattened. 7702 /// Group: paths 7703 public NVGPathOutline getCurrPathOutline (NVGContext ctx) nothrow @trusted @nogc { 7704 if (ctx is null || !ctx.contextAlive || ctx.ncommands == 0) return NVGPathOutline.init; 7705 7706 auto res = NVGPathOutline.createNew(); 7707 7708 const(float)[] acommands = ctx.commands[0..ctx.ncommands]; 7709 int ncommands = cast(int)acommands.length; 7710 const(float)* commands = acommands.ptr; 7711 7712 float cx = 0, cy = 0; 7713 float[2] start = void; 7714 float[4] totalBounds = [float.max, float.max, -float.max, -float.max]; 7715 float[8] bcp = void; // bezier curve points; used to calculate bounds 7716 7717 void addToBounds (in float x, in float y) nothrow @trusted @nogc { 7718 totalBounds.ptr[0] = nvg__min(totalBounds.ptr[0], x); 7719 totalBounds.ptr[1] = nvg__min(totalBounds.ptr[1], y); 7720 totalBounds.ptr[2] = nvg__max(totalBounds.ptr[2], x); 7721 totalBounds.ptr[3] = nvg__max(totalBounds.ptr[3], y); 7722 } 7723 7724 bool hasPoints = false; 7725 7726 void closeIt () nothrow @trusted @nogc { 7727 if (!hasPoints) return; 7728 if (cx != start.ptr[0] || cy != start.ptr[1]) { 7729 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo); 7730 res.ds.putArgs(start[]); 7731 cx = start.ptr[0]; 7732 cy = start.ptr[1]; 7733 addToBounds(cx, cy); 7734 } 7735 } 7736 7737 int i = 0; 7738 while (i < ncommands) { 7739 int cmd = cast(int)commands[i++]; 7740 switch (cmd) { 7741 case Command.MoveTo: // one coordinate pair 7742 const(float)* tfxy = commands+i; 7743 i += 2; 7744 // add command 7745 res.ds.putCommand(NVGPathOutline.Command.Kind.MoveTo); 7746 res.ds.putArgs(tfxy[0..2]); 7747 // new starting point 7748 start.ptr[0..2] = tfxy[0..2]; 7749 cx = tfxy[0]; 7750 cy = tfxy[0]; 7751 addToBounds(cx, cy); 7752 hasPoints = true; 7753 break; 7754 case Command.LineTo: // one coordinate pair 7755 const(float)* tfxy = commands+i; 7756 i += 2; 7757 // add command 7758 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo); 7759 res.ds.putArgs(tfxy[0..2]); 7760 cx = tfxy[0]; 7761 cy = tfxy[0]; 7762 addToBounds(cx, cy); 7763 hasPoints = true; 7764 break; 7765 case Command.BezierTo: // three coordinate pairs 7766 const(float)* tfxy = commands+i; 7767 i += 3*2; 7768 // add command 7769 res.ds.putCommand(NVGPathOutline.Command.Kind.BezierTo); 7770 res.ds.putArgs(tfxy[0..6]); 7771 // bounds 7772 bcp.ptr[0] = cx; 7773 bcp.ptr[1] = cy; 7774 bcp.ptr[2..8] = tfxy[0..6]; 7775 nvg__bezierBounds(bcp.ptr, totalBounds); 7776 cx = tfxy[4]; 7777 cy = tfxy[5]; 7778 hasPoints = true; 7779 break; 7780 case Command.Close: 7781 closeIt(); 7782 hasPoints = false; 7783 break; 7784 case Command.Winding: 7785 //psp.winding = cast(short)cast(int)commands[i]; 7786 i += 1; 7787 break; 7788 default: 7789 break; 7790 } 7791 } 7792 7793 res.ds.bounds[] = totalBounds[]; 7794 return res; 7795 } 7796 7797 7798 // ////////////////////////////////////////////////////////////////////////// // 7799 // Text 7800 7801 /** Creates font by loading it from the disk from specified file name. 7802 * Returns handle to the font or FONS_INVALID (aka -1) on error. 7803 * Use "fontname:noaa" as [name] to turn off antialiasing (if font driver supports that). 7804 * 7805 * On POSIX systems it is possible to use fontconfig font names too. 7806 * `:noaa` in font path is still allowed, but it must be the last option. 7807 * 7808 * Group: text_api 7809 */ 7810 public int createFont (NVGContext ctx, const(char)[] name, const(char)[] path) nothrow @trusted { 7811 return ctx.fs.addFont(name, path, ctx.params.fontAA); 7812 } 7813 7814 /** Creates font by loading it from the specified memory chunk. 7815 * Returns handle to the font or FONS_INVALID (aka -1) on error. 7816 * Won't free data on error. 7817 * 7818 * Group: text_api 7819 */ 7820 public int createFontMem (NVGContext ctx, const(char)[] name, ubyte* data, int ndata, bool freeData) nothrow @trusted @nogc { 7821 return ctx.fs.addFontMem(name, data, ndata, freeData, ctx.params.fontAA); 7822 } 7823 7824 /// Add fonts from another context. 7825 /// This is more effective than reloading fonts, 'cause font data will be shared. 7826 /// Group: text_api 7827 public void addFontsFrom (NVGContext ctx, NVGContext source) nothrow @trusted @nogc { 7828 if (ctx is null || source is null) return; 7829 ctx.fs.addFontsFrom(source.fs); 7830 } 7831 7832 /// Finds a loaded font of specified name, and returns handle to it, or FONS_INVALID (aka -1) if the font is not found. 7833 /// Group: text_api 7834 public int findFont (NVGContext ctx, const(char)[] name) nothrow @trusted @nogc { 7835 pragma(inline, true); 7836 return (name.length == 0 ? FONS_INVALID : ctx.fs.getFontByName(name)); 7837 } 7838 7839 /// Sets the font size of current text style. 7840 /// Group: text_api 7841 public void fontSize (NVGContext ctx, float size) nothrow @trusted @nogc { 7842 pragma(inline, true); 7843 nvg__getState(ctx).fontSize = size; 7844 } 7845 7846 /// Gets the font size of current text style. 7847 /// Group: text_api 7848 public float fontSize (NVGContext ctx) nothrow @trusted @nogc { 7849 pragma(inline, true); 7850 return nvg__getState(ctx).fontSize; 7851 } 7852 7853 /// Sets the blur of current text style. 7854 /// Group: text_api 7855 public void fontBlur (NVGContext ctx, float blur) nothrow @trusted @nogc { 7856 pragma(inline, true); 7857 nvg__getState(ctx).fontBlur = blur; 7858 } 7859 7860 /// Gets the blur of current text style. 7861 /// Group: text_api 7862 public float fontBlur (NVGContext ctx) nothrow @trusted @nogc { 7863 pragma(inline, true); 7864 return nvg__getState(ctx).fontBlur; 7865 } 7866 7867 /// Sets the letter spacing of current text style. 7868 /// Group: text_api 7869 public void textLetterSpacing (NVGContext ctx, float spacing) nothrow @trusted @nogc { 7870 pragma(inline, true); 7871 nvg__getState(ctx).letterSpacing = spacing; 7872 } 7873 7874 /// Gets the letter spacing of current text style. 7875 /// Group: text_api 7876 public float textLetterSpacing (NVGContext ctx) nothrow @trusted @nogc { 7877 pragma(inline, true); 7878 return nvg__getState(ctx).letterSpacing; 7879 } 7880 7881 /// Sets the proportional line height of current text style. The line height is specified as multiple of font size. 7882 /// Group: text_api 7883 public void textLineHeight (NVGContext ctx, float lineHeight) nothrow @trusted @nogc { 7884 pragma(inline, true); 7885 nvg__getState(ctx).lineHeight = lineHeight; 7886 } 7887 7888 /// Gets the proportional line height of current text style. The line height is specified as multiple of font size. 7889 /// Group: text_api 7890 public float textLineHeight (NVGContext ctx) nothrow @trusted @nogc { 7891 pragma(inline, true); 7892 return nvg__getState(ctx).lineHeight; 7893 } 7894 7895 /// Sets the text align of current text style, see [NVGTextAlign] for options. 7896 /// Group: text_api 7897 public void textAlign (NVGContext ctx, NVGTextAlign talign) nothrow @trusted @nogc { 7898 pragma(inline, true); 7899 nvg__getState(ctx).textAlign = talign; 7900 } 7901 7902 /// Ditto. 7903 public void textAlign (NVGContext ctx, NVGTextAlign.H h) nothrow @trusted @nogc { 7904 pragma(inline, true); 7905 nvg__getState(ctx).textAlign.horizontal = h; 7906 } 7907 7908 /// Ditto. 7909 public void textAlign (NVGContext ctx, NVGTextAlign.V v) nothrow @trusted @nogc { 7910 pragma(inline, true); 7911 nvg__getState(ctx).textAlign.vertical = v; 7912 } 7913 7914 /// Ditto. 7915 public void textAlign (NVGContext ctx, NVGTextAlign.H h, NVGTextAlign.V v) nothrow @trusted @nogc { 7916 pragma(inline, true); 7917 nvg__getState(ctx).textAlign.reset(h, v); 7918 } 7919 7920 /// Ditto. 7921 public void textAlign (NVGContext ctx, NVGTextAlign.V v, NVGTextAlign.H h) nothrow @trusted @nogc { 7922 pragma(inline, true); 7923 nvg__getState(ctx).textAlign.reset(h, v); 7924 } 7925 7926 /// Gets the text align of current text style, see [NVGTextAlign] for options. 7927 /// Group: text_api 7928 public NVGTextAlign textAlign (NVGContext ctx) nothrow @trusted @nogc { 7929 pragma(inline, true); 7930 return nvg__getState(ctx).textAlign; 7931 } 7932 7933 /// Sets the font face based on specified id of current text style. 7934 /// Group: text_api 7935 public void fontFaceId (NVGContext ctx, int font) nothrow @trusted @nogc { 7936 pragma(inline, true); 7937 nvg__getState(ctx).fontId = font; 7938 } 7939 7940 /// Gets the font face based on specified id of current text style. 7941 /// Group: text_api 7942 public int fontFaceId (NVGContext ctx) nothrow @trusted @nogc { 7943 pragma(inline, true); 7944 return nvg__getState(ctx).fontId; 7945 } 7946 7947 /** Sets the font face based on specified name of current text style. 7948 * 7949 * The underlying implementation is using O(1) data structure to lookup 7950 * font names, so you probably should use this function instead of [fontFaceId] 7951 * to make your code more robust and less error-prone. 7952 * 7953 * Group: text_api 7954 */ 7955 public void fontFace (NVGContext ctx, const(char)[] font) nothrow @trusted @nogc { 7956 pragma(inline, true); 7957 nvg__getState(ctx).fontId = ctx.fs.getFontByName(font); 7958 } 7959 7960 static if (is(typeof(&fons__nvg__toPath))) { 7961 public enum NanoVegaHasCharToPath = true; /// 7962 } else { 7963 public enum NanoVegaHasCharToPath = false; /// 7964 } 7965 7966 /// Adds glyph outlines to the current path. Vertical 0 is baseline. 7967 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead. 7968 /// Returns `false` if there is no such glyph, or current font is not scalable. 7969 /// Group: text_api 7970 public bool charToPath (NVGContext ctx, dchar dch, float[] bounds=null) nothrow @trusted @nogc { 7971 NVGstate* state = nvg__getState(ctx); 7972 ctx.fs.fontId = state.fontId; 7973 return ctx.fs.toPath(ctx, dch, bounds); 7974 } 7975 7976 static if (is(typeof(&fons__nvg__bounds))) { 7977 public enum NanoVegaHasCharPathBounds = true; /// 7978 } else { 7979 public enum NanoVegaHasCharPathBounds = false; /// 7980 } 7981 7982 /// Returns bounds of the glyph outlines. Vertical 0 is baseline. 7983 /// The glyph is not scaled in any way. 7984 /// Returns `false` if there is no such glyph, or current font is not scalable. 7985 /// Group: text_api 7986 public bool charPathBounds (NVGContext ctx, dchar dch, float[] bounds) nothrow @trusted @nogc { 7987 NVGstate* state = nvg__getState(ctx); 7988 ctx.fs.fontId = state.fontId; 7989 return ctx.fs.getPathBounds(dch, bounds); 7990 } 7991 7992 /** [charOutline] will return [NVGPathOutline]. 7993 7994 some usage samples: 7995 7996 --- 7997 float[4] bounds = void; 7998 7999 nvg.scale(0.5, 0.5); 8000 nvg.translate(500, 800); 8001 nvg.evenOddFill; 8002 8003 nvg.newPath(); 8004 nvg.charToPath('&', bounds[]); 8005 conwriteln(bounds[]); 8006 nvg.fillPaint(nvg.linearGradient(0, 0, 600, 600, NVGColor("#f70"), NVGColor("#ff0"))); 8007 nvg.strokeColor(NVGColor("#0f0")); 8008 nvg.strokeWidth = 3; 8009 nvg.fill(); 8010 nvg.stroke(); 8011 // glyph bounds 8012 nvg.newPath(); 8013 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); 8014 nvg.strokeColor(NVGColor("#00f")); 8015 nvg.stroke(); 8016 8017 nvg.newPath(); 8018 nvg.charToPath('g', bounds[]); 8019 conwriteln(bounds[]); 8020 nvg.fill(); 8021 nvg.strokeColor(NVGColor("#0f0")); 8022 nvg.stroke(); 8023 // glyph bounds 8024 nvg.newPath(); 8025 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); 8026 nvg.strokeColor(NVGColor("#00f")); 8027 nvg.stroke(); 8028 8029 nvg.newPath(); 8030 nvg.moveTo(0, 0); 8031 nvg.lineTo(600, 0); 8032 nvg.strokeColor(NVGColor("#0ff")); 8033 nvg.stroke(); 8034 8035 if (auto ol = nvg.charOutline('Q')) { 8036 scope(exit) ol.kill(); 8037 nvg.newPath(); 8038 conwriteln("==== length: ", ol.length, " ===="); 8039 foreach (const ref cmd; ol.commands) { 8040 //conwriteln(" ", cmd.code, ": ", cmd.args[]); 8041 assert(cmd.valid); 8042 final switch (cmd.code) { 8043 case cmd.Kind.MoveTo: nvg.moveTo(cmd.args[0], cmd.args[1]); break; 8044 case cmd.Kind.LineTo: nvg.lineTo(cmd.args[0], cmd.args[1]); break; 8045 case cmd.Kind.QuadTo: nvg.quadTo(cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]); break; 8046 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; 8047 } 8048 } 8049 nvg.strokeColor(NVGColor("#f00")); 8050 nvg.stroke(); 8051 } 8052 --- 8053 8054 Group: text_api 8055 */ 8056 public struct NVGPathOutline { 8057 private nothrow @trusted @nogc: 8058 struct DataStore { 8059 uint rc; // refcount 8060 ubyte* data; 8061 uint used; 8062 uint size; 8063 uint ccount; // number of commands 8064 float[4] bounds = 0; /// outline bounds 8065 nothrow @trusted @nogc: 8066 void putBytes (const(void)[] b) { 8067 if (b.length == 0) return; 8068 if (b.length >= int.max/8) assert(0, "NanoVega: out of memory"); 8069 if (int.max/8-used < b.length) assert(0, "NanoVega: out of memory"); 8070 if (used+cast(uint)b.length > size) { 8071 import core.stdc.stdlib : realloc; 8072 uint newsz = size; 8073 while (newsz < used+cast(uint)b.length) newsz = (newsz == 0 ? 1024 : newsz < 32768 ? newsz*2 : newsz+8192); 8074 assert(used+cast(uint)b.length <= newsz); 8075 data = cast(ubyte*)realloc(data, newsz); 8076 if (data is null) assert(0, "NanoVega: out of memory"); 8077 size = newsz; 8078 } 8079 import core.stdc.string : memcpy; 8080 memcpy(data+used, b.ptr, b.length); 8081 used += cast(uint)b.length; 8082 } 8083 void putCommand (ubyte cmd) { pragma(inline, true); ++ccount; putBytes((&cmd)[0..1]); } 8084 void putArgs (const(float)[] f...) { pragma(inline, true); putBytes(f[]); } 8085 } 8086 8087 static void incRef (DataStore* ds) { 8088 pragma(inline, true); 8089 if (ds !is null) { 8090 ++ds.rc; 8091 //{ import core.stdc.stdio; printf("ods(%p): incref: newrc=%u\n", ds, ds.rc); } 8092 } 8093 } 8094 8095 static void decRef (DataStore* ds) { 8096 version(aliced) pragma(inline, true); 8097 if (ds !is null) { 8098 //{ import core.stdc.stdio; printf("ods(%p): decref: newrc=%u\n", ds, ds.rc-1); } 8099 if (--ds.rc == 0) { 8100 import core.stdc.stdlib : free; 8101 import core.stdc.string : memset; 8102 if (ds.data !is null) free(ds.data); 8103 memset(ds, 0, DataStore.sizeof); // just in case 8104 free(ds); 8105 //{ import core.stdc.stdio; printf(" ods(%p): killed.\n"); } 8106 } 8107 } 8108 } 8109 8110 private: 8111 static NVGPathOutline createNew () { 8112 import core.stdc.stdlib : malloc; 8113 import core.stdc.string : memset; 8114 auto ds = cast(DataStore*)malloc(DataStore.sizeof); 8115 if (ds is null) assert(0, "NanoVega: out of memory"); 8116 memset(ds, 0, DataStore.sizeof); 8117 ds.rc = 1; 8118 NVGPathOutline res; 8119 res.dsaddr = cast(usize)ds; 8120 return res; 8121 } 8122 8123 private: 8124 usize dsaddr; // fool GC 8125 8126 @property inout(DataStore)* ds () inout pure { pragma(inline, true); return cast(DataStore*)dsaddr; } 8127 8128 public: 8129 /// commands 8130 static struct Command { 8131 /// 8132 enum Kind : ubyte { 8133 MoveTo, /// 8134 LineTo, /// 8135 QuadTo, /// 8136 BezierTo, /// 8137 End, /// no more commands (this command is not `valid`!) 8138 8139 } 8140 Kind code; /// 8141 const(float)[] args; /// 8142 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (code >= Kind.min && code < Kind.End && args.length >= 2); } /// 8143 8144 static uint arglen (Kind code) pure nothrow @safe @nogc { 8145 pragma(inline, true); 8146 return 8147 code == Kind.MoveTo || code == Kind.LineTo ? 2 : 8148 code == Kind.QuadTo ? 4 : 8149 code == Kind.BezierTo ? 6 : 8150 0; 8151 } 8152 8153 /// perform NanoVega command with stored data. 8154 void perform (NVGContext ctx) const nothrow @trusted @nogc { 8155 if (ctx is null) return; 8156 final switch (code) { 8157 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(args.ptr[0..2]); break; 8158 case Kind.LineTo: if (args.length > 1) ctx.lineTo(args.ptr[0..2]); break; 8159 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(args.ptr[0..4]); break; 8160 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(args.ptr[0..6]); break; 8161 case Kind.End: break; 8162 } 8163 } 8164 8165 /// perform NanoVega command with stored data, transforming points with [xform] transformation matrix. 8166 void perform() (NVGContext ctx, const scope auto ref NVGMatrix xform) const nothrow @trusted @nogc { 8167 if (ctx is null || !valid) return; 8168 float[6] pts = void; 8169 pts[0..args.length] = args[]; 8170 foreach (immutable pidx; 0..args.length/2) xform.point(pts.ptr[pidx*2+0], pts.ptr[pidx*2+1]); 8171 final switch (code) { 8172 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(pts.ptr[0..2]); break; 8173 case Kind.LineTo: if (args.length > 1) ctx.lineTo(pts.ptr[0..2]); break; 8174 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(pts.ptr[0..4]); break; 8175 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(pts.ptr[0..6]); break; 8176 case Kind.End: break; 8177 } 8178 } 8179 } 8180 8181 public: 8182 /// Create new path with quadratic bezier (first command is MoveTo, second command is QuadTo). 8183 static NVGPathOutline createNewQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) { 8184 auto res = createNew(); 8185 res.ds.putCommand(Command.Kind.MoveTo); 8186 res.ds.putArgs(x0, y0); 8187 res.ds.putCommand(Command.Kind.QuadTo); 8188 res.ds.putArgs(cx, cy, x, y); 8189 return res; 8190 } 8191 8192 /// Create new path with cubic bezier (first command is MoveTo, second command is BezierTo). 8193 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) { 8194 auto res = createNew(); 8195 res.ds.putCommand(Command.Kind.MoveTo); 8196 res.ds.putArgs(x1, y1); 8197 res.ds.putCommand(Command.Kind.BezierTo); 8198 res.ds.putArgs(x2, y2, x3, y3, x4, y4); 8199 return res; 8200 } 8201 8202 public: 8203 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); } 8204 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); } 8205 8206 void opAssign() (const scope auto ref NVGPathOutline a) { 8207 incRef(cast(DataStore*)a.dsaddr); 8208 decRef(cast(DataStore*)dsaddr); 8209 dsaddr = a.dsaddr; 8210 } 8211 8212 /// Clear storage. 8213 void clear () { 8214 pragma(inline, true); 8215 decRef(ds); 8216 dsaddr = 0; 8217 } 8218 8219 /// Is this outline empty? 8220 @property empty () const pure { pragma(inline, true); return (dsaddr == 0 || ds.ccount == 0); } 8221 8222 /// Returns number of commands in outline. 8223 @property int length () const pure { pragma(inline, true); return (dsaddr ? ds.ccount : 0); } 8224 8225 /// Returns "flattened" path. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8226 NVGPathOutline flatten () const { pragma(inline, true); return flattenInternal(null); } 8227 8228 /// Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8229 NVGPathOutline flatten() (const scope auto ref NVGMatrix mt) const { pragma(inline, true); return flattenInternal(&mt); } 8230 8231 // Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8232 private NVGPathOutline flattenInternal (scope NVGMatrix* tfm) const { 8233 import core.stdc.string : memset; 8234 8235 NVGPathOutline res; 8236 if (dsaddr == 0 || ds.ccount == 0) { res = this; return res; } // nothing to do 8237 8238 // check if we need to flatten the path 8239 if (tfm is null) { 8240 bool dowork = false; 8241 foreach (const ref cs; commands) { 8242 if (cs.code != Command.Kind.MoveTo && cs.code != Command.Kind.LineTo) { 8243 dowork = true; 8244 break; 8245 } 8246 } 8247 if (!dowork) { res = this; return res; } // nothing to do 8248 } 8249 8250 NVGcontextinternal ctx; 8251 memset(&ctx, 0, ctx.sizeof); 8252 ctx.cache = nvg__allocPathCache(); 8253 scope(exit) { 8254 import core.stdc.stdlib : free; 8255 nvg__deletePathCache(ctx.cache); 8256 } 8257 8258 ctx.tessTol = 0.25f; 8259 ctx.angleTol = 0; // 0 -- angle tolerance for McSeem Bezier rasterizer 8260 ctx.cuspLimit = 0; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps) 8261 ctx.distTol = 0.01f; 8262 ctx.tesselatortype = NVGTesselation.DeCasteljau; 8263 8264 nvg__addPath(&ctx); // we need this for `nvg__addPoint()` 8265 8266 // has some curves or transformations, convert path 8267 res = createNew(); 8268 float[8] args = void; 8269 8270 res.ds.bounds = [float.max, float.max, -float.max, -float.max]; 8271 8272 float lastX = float.max, lastY = float.max; 8273 bool lastWasMove = false; 8274 8275 void addPoint (float x, float y, Command.Kind cmd=Command.Kind.LineTo) nothrow @trusted @nogc { 8276 if (tfm !is null) tfm.point(x, y); 8277 bool isMove = (cmd == Command.Kind.MoveTo); 8278 if (isMove) { 8279 // moveto 8280 if (lastWasMove && nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return; 8281 } else { 8282 // lineto 8283 if (nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return; 8284 } 8285 lastWasMove = isMove; 8286 lastX = x; 8287 lastY = y; 8288 res.ds.putCommand(cmd); 8289 res.ds.putArgs(x, y); 8290 res.ds.bounds.ptr[0] = nvg__min(res.ds.bounds.ptr[0], x); 8291 res.ds.bounds.ptr[1] = nvg__min(res.ds.bounds.ptr[1], y); 8292 res.ds.bounds.ptr[2] = nvg__max(res.ds.bounds.ptr[2], x); 8293 res.ds.bounds.ptr[3] = nvg__max(res.ds.bounds.ptr[3], y); 8294 } 8295 8296 // sorry for this pasta 8297 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 { 8298 ctx.cache.npoints = 0; 8299 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) { 8300 nvg__tesselateBezier(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner); 8301 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) { 8302 nvg__tesselateBezierMcSeem(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner); 8303 } else { 8304 nvg__tesselateBezierAFD(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, PointFlag.Corner); 8305 } 8306 // add generated points 8307 foreach (const ref pt; ctx.cache.points[0..ctx.cache.npoints]) addPoint(pt.x, pt.y); 8308 } 8309 8310 void flattenQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) { 8311 flattenBezier( 8312 x0, y0, 8313 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 8314 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 8315 x, y, 8316 0, 8317 ); 8318 } 8319 8320 float cx = 0, cy = 0; 8321 foreach (const ref cs; commands) { 8322 switch (cs.code) { 8323 case Command.Kind.LineTo: 8324 case Command.Kind.MoveTo: 8325 addPoint(cs.args[0], cs.args[1], cs.code); 8326 cx = cs.args[0]; 8327 cy = cs.args[1]; 8328 break; 8329 case Command.Kind.QuadTo: 8330 flattenQuad(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3]); 8331 cx = cs.args[2]; 8332 cy = cs.args[3]; 8333 break; 8334 case Command.Kind.BezierTo: 8335 flattenBezier(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3], cs.args[4], cs.args[5], 0); 8336 cx = cs.args[4]; 8337 cy = cs.args[5]; 8338 break; 8339 default: 8340 break; 8341 } 8342 } 8343 8344 return res; 8345 } 8346 8347 /// Returns forward range with all glyph commands. 8348 auto commands () const nothrow @trusted @nogc { 8349 static struct Range { 8350 private nothrow @trusted @nogc: 8351 usize dsaddr; 8352 uint cpos; // current position in data 8353 uint cleft; // number of commands left 8354 @property const(ubyte)* data () inout pure { pragma(inline, true); return (dsaddr ? (cast(DataStore*)dsaddr).data : null); } 8355 public: 8356 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); } 8357 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); } 8358 void opAssign() (const scope auto ref Range a) { 8359 incRef(cast(DataStore*)a.dsaddr); 8360 decRef(cast(DataStore*)dsaddr); 8361 dsaddr = a.dsaddr; 8362 cpos = a.cpos; 8363 cleft = a.cleft; 8364 } 8365 float[4] bounds () const pure { float[4] res = 0; pragma(inline, true); if (dsaddr) res[] = (cast(DataStore*)dsaddr).bounds[]; return res; } /// outline bounds 8366 @property bool empty () const pure { pragma(inline, true); return (cleft == 0); } 8367 @property int length () const pure { pragma(inline, true); return cleft; } 8368 @property Range save () const { pragma(inline, true); Range res = this; return res; } 8369 @property Command front () const { 8370 Command res = void; 8371 if (cleft > 0) { 8372 res.code = cast(Command.Kind)data[cpos]; 8373 switch (res.code) { 8374 case Command.Kind.MoveTo: 8375 case Command.Kind.LineTo: 8376 res.args = (cast(const(float*))(data+cpos+1))[0..1*2]; 8377 break; 8378 case Command.Kind.QuadTo: 8379 res.args = (cast(const(float*))(data+cpos+1))[0..2*2]; 8380 break; 8381 case Command.Kind.BezierTo: 8382 res.args = (cast(const(float*))(data+cpos+1))[0..3*2]; 8383 break; 8384 default: 8385 res.code = Command.Kind.End; 8386 res.args = null; 8387 break; 8388 } 8389 } else { 8390 res.code = Command.Kind.End; 8391 res.args = null; 8392 } 8393 return res; 8394 } 8395 void popFront () { 8396 if (cleft <= 1) { cleft = 0; return; } // don't waste time skipping last command 8397 --cleft; 8398 switch (data[cpos]) { 8399 case Command.Kind.MoveTo: 8400 case Command.Kind.LineTo: 8401 cpos += 1+1*2*cast(uint)float.sizeof; 8402 break; 8403 case Command.Kind.QuadTo: 8404 cpos += 1+2*2*cast(uint)float.sizeof; 8405 break; 8406 case Command.Kind.BezierTo: 8407 cpos += 1+3*2*cast(uint)float.sizeof; 8408 break; 8409 default: 8410 cleft = 0; 8411 break; 8412 } 8413 } 8414 } 8415 if (dsaddr) { 8416 incRef(cast(DataStore*)dsaddr); // range anchors it 8417 return Range(dsaddr, 0, ds.ccount); 8418 } else { 8419 return Range.init; 8420 } 8421 } 8422 } 8423 8424 public alias NVGGlyphOutline = NVGPathOutline; /// For backwards compatibility. 8425 8426 /// Destroy glyph outiline and free allocated memory. 8427 /// Group: text_api 8428 public void kill (ref NVGPathOutline ol) nothrow @trusted @nogc { 8429 pragma(inline, true); 8430 ol.clear(); 8431 } 8432 8433 static if (is(typeof(&fons__nvg__toOutline))) { 8434 public enum NanoVegaHasCharOutline = true; /// 8435 } else { 8436 public enum NanoVegaHasCharOutline = false; /// 8437 } 8438 8439 /// Returns glyph outlines as array of commands. Vertical 0 is baseline. 8440 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead. 8441 /// Returns `null` if there is no such glyph, or current font is not scalable. 8442 /// Group: text_api 8443 public NVGPathOutline charOutline (NVGContext ctx, dchar dch) nothrow @trusted @nogc { 8444 import core.stdc.stdlib : malloc; 8445 import core.stdc.string : memcpy; 8446 NVGstate* state = nvg__getState(ctx); 8447 ctx.fs.fontId = state.fontId; 8448 auto oline = NVGPathOutline.createNew(); 8449 if (!ctx.fs.toOutline(dch, oline.ds)) oline.clear(); 8450 return oline; 8451 } 8452 8453 8454 float nvg__quantize (float a, float d) pure nothrow @safe @nogc { 8455 pragma(inline, true); 8456 return (cast(int)(a/d+0.5f))*d; 8457 } 8458 8459 float nvg__getFontScale (NVGstate* state) /*pure*/ nothrow @safe @nogc { 8460 pragma(inline, true); 8461 return nvg__min(nvg__quantize(nvg__getAverageScale(state.xform), 0.01f), 4.0f); 8462 } 8463 8464 void nvg__flushTextTexture (NVGContext ctx) nothrow @trusted @nogc { 8465 int[4] dirty = void; 8466 if (ctx.fs.validateTexture(dirty.ptr)) { 8467 auto fontImage = &ctx.fontImages[ctx.fontImageIdx]; 8468 // Update texture 8469 if (fontImage.valid) { 8470 int iw, ih; 8471 const(ubyte)* data = ctx.fs.getTextureData(&iw, &ih); 8472 int x = dirty[0]; 8473 int y = dirty[1]; 8474 int w = dirty[2]-dirty[0]; 8475 int h = dirty[3]-dirty[1]; 8476 ctx.params.renderUpdateTexture(ctx.params.userPtr, fontImage.id, x, y, w, h, data); 8477 } 8478 } 8479 } 8480 8481 bool nvg__allocTextAtlas (NVGContext ctx) nothrow @trusted @nogc { 8482 int iw, ih; 8483 nvg__flushTextTexture(ctx); 8484 if (ctx.fontImageIdx >= NVG_MAX_FONTIMAGES-1) return false; 8485 // if next fontImage already have a texture 8486 if (ctx.fontImages[ctx.fontImageIdx+1].valid) { 8487 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx+1], iw, ih); 8488 } else { 8489 // calculate the new font image size and create it 8490 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx], iw, ih); 8491 if (iw > ih) ih *= 2; else iw *= 2; 8492 if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE; 8493 ctx.fontImages[ctx.fontImageIdx+1].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, iw, ih, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); 8494 if (ctx.fontImages[ctx.fontImageIdx+1].id > 0) { 8495 ctx.fontImages[ctx.fontImageIdx+1].ctx = ctx; 8496 ctx.nvg__imageIncRef(ctx.fontImages[ctx.fontImageIdx+1].id, false); // don't increment driver refcount 8497 } 8498 } 8499 ++ctx.fontImageIdx; 8500 ctx.fs.resetAtlas(iw, ih); 8501 return true; 8502 } 8503 8504 void nvg__renderText (NVGContext ctx, NVGVertex* verts, int nverts) nothrow @trusted @nogc { 8505 NVGstate* state = nvg__getState(ctx); 8506 NVGPaint paint = state.fill; 8507 8508 // Render triangles. 8509 paint.image = ctx.fontImages[ctx.fontImageIdx]; 8510 8511 // Apply global alpha 8512 paint.innerColor.a *= state.alpha; 8513 paint.middleColor.a *= state.alpha; 8514 paint.outerColor.a *= state.alpha; 8515 8516 ctx.params.renderTriangles(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &paint, &state.scissor, verts, nverts); 8517 8518 ++ctx.drawCallCount; 8519 ctx.textTriCount += nverts/3; 8520 } 8521 8522 /// Draws text string at specified location. Returns next x position. 8523 /// Group: text_api 8524 public float text(T) (NVGContext ctx, float x, float y, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 8525 NVGstate* state = nvg__getState(ctx); 8526 FONSTextIter!T iter, prevIter; 8527 FONSQuad q; 8528 NVGVertex* verts; 8529 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8530 float invscale = 1.0f/scale; 8531 int cverts = 0; 8532 int nverts = 0; 8533 8534 if (state.fontId == FONS_INVALID) return x; 8535 if (str.length == 0) return x; 8536 8537 ctx.fs.size = state.fontSize*scale; 8538 ctx.fs.spacing = state.letterSpacing*scale; 8539 ctx.fs.blur = state.fontBlur*scale; 8540 ctx.fs.textAlign = state.textAlign; 8541 ctx.fs.fontId = state.fontId; 8542 8543 cverts = nvg__max(2, cast(int)(str.length))*6; // conservative estimate 8544 verts = nvg__allocTempVerts(ctx, cverts); 8545 if (verts is null) return x; 8546 8547 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Required)) return x; 8548 prevIter = iter; 8549 while (iter.next(q)) { 8550 float[4*2] c = void; 8551 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8552 if (nverts != 0) { 8553 // TODO: add back-end bit to do this just once per frame 8554 nvg__flushTextTexture(ctx); 8555 nvg__renderText(ctx, verts, nverts); 8556 nverts = 0; 8557 } 8558 if (!nvg__allocTextAtlas(ctx)) break; // no memory :( 8559 iter = prevIter; 8560 iter.next(q); // try again 8561 if (iter.prevGlyphIndex < 0) { 8562 // still can not find glyph, try replacement 8563 iter = prevIter; 8564 if (!iter.getDummyChar(q)) break; 8565 } 8566 } 8567 prevIter = iter; 8568 // transform corners 8569 state.xform.point(&c[0], &c[1], q.x0*invscale, q.y0*invscale); 8570 state.xform.point(&c[2], &c[3], q.x1*invscale, q.y0*invscale); 8571 state.xform.point(&c[4], &c[5], q.x1*invscale, q.y1*invscale); 8572 state.xform.point(&c[6], &c[7], q.x0*invscale, q.y1*invscale); 8573 // create triangles 8574 if (nverts+6 <= cverts) { 8575 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts; 8576 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts; 8577 nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); ++nverts; 8578 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts; 8579 nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); ++nverts; 8580 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts; 8581 } 8582 } 8583 8584 // TODO: add back-end bit to do this just once per frame 8585 if (nverts > 0) { 8586 nvg__flushTextTexture(ctx); 8587 nvg__renderText(ctx, verts, nverts); 8588 } 8589 8590 return iter.nextx/scale; 8591 } 8592 8593 /** Draws multi-line text string at specified location wrapped at the specified width. 8594 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8595 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8596 * 8597 * Group: text_api 8598 */ 8599 public void textBox(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 8600 NVGstate* state = nvg__getState(ctx); 8601 if (state.fontId == FONS_INVALID) return; 8602 8603 NVGTextRow!T[2] rows; 8604 auto oldAlign = state.textAlign; 8605 scope(exit) state.textAlign = oldAlign; 8606 auto halign = state.textAlign.horizontal; 8607 float lineh = 0; 8608 8609 ctx.textMetrics(null, null, &lineh); 8610 state.textAlign.horizontal = NVGTextAlign.H.Left; 8611 for (;;) { 8612 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]); 8613 //{ import core.stdc.stdio : printf; printf("slen=%u; rlen=%u; bw=%f\n", cast(uint)str.length, cast(uint)rres.length, cast(double)breakRowWidth); } 8614 if (rres.length == 0) break; 8615 foreach (ref row; rres) { 8616 final switch (halign) { 8617 case NVGTextAlign.H.Left: ctx.text(x, y, row.row); break; 8618 case NVGTextAlign.H.Center: ctx.text(x+breakRowWidth*0.5f-row.width*0.5f, y, row.row); break; 8619 case NVGTextAlign.H.Right: ctx.text(x+breakRowWidth-row.width, y, row.row); break; 8620 } 8621 y += lineh*state.lineHeight; 8622 } 8623 str = rres[$-1].rest; 8624 } 8625 } 8626 8627 private template isGoodPositionDelegate(DG) { 8628 private DG dg; 8629 static if (is(typeof({ NVGGlyphPosition pos; bool res = dg(pos); })) || 8630 is(typeof({ NVGGlyphPosition pos; dg(pos); }))) 8631 enum isGoodPositionDelegate = true; 8632 else 8633 enum isGoodPositionDelegate = false; 8634 } 8635 8636 /** Calculates the glyph x positions of the specified text. 8637 * Measured values are returned in local coordinate space. 8638 * 8639 * Group: text_api 8640 */ 8641 public NVGGlyphPosition[] textGlyphPositions(T) (NVGContext ctx, float x, float y, const(T)[] str, NVGGlyphPosition[] positions) nothrow @trusted @nogc 8642 if (isAnyCharType!T) 8643 { 8644 if (str.length == 0 || positions.length == 0) return positions[0..0]; 8645 usize posnum; 8646 auto len = ctx.textGlyphPositions(x, y, str, (const scope ref NVGGlyphPosition pos) { 8647 positions.ptr[posnum++] = pos; 8648 return (posnum < positions.length); 8649 }); 8650 return positions[0..len]; 8651 } 8652 8653 /// Ditto. 8654 public int textGlyphPositions(T, DG) (NVGContext ctx, float x, float y, const(T)[] str, scope DG dg) 8655 if (isAnyCharType!T && isGoodPositionDelegate!DG) 8656 { 8657 import std.traits : ReturnType; 8658 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true; 8659 8660 NVGstate* state = nvg__getState(ctx); 8661 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8662 float invscale = 1.0f/scale; 8663 FONSTextIter!T iter, prevIter; 8664 FONSQuad q; 8665 int npos = 0; 8666 8667 if (str.length == 0) return 0; 8668 8669 ctx.fs.size = state.fontSize*scale; 8670 ctx.fs.spacing = state.letterSpacing*scale; 8671 ctx.fs.blur = state.fontBlur*scale; 8672 ctx.fs.textAlign = state.textAlign; 8673 ctx.fs.fontId = state.fontId; 8674 8675 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Optional)) return npos; 8676 prevIter = iter; 8677 while (iter.next(q)) { 8678 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8679 if (!nvg__allocTextAtlas(ctx)) break; // no memory 8680 iter = prevIter; 8681 iter.next(q); // try again 8682 if (iter.prevGlyphIndex < 0) { 8683 // still can not find glyph, try replacement 8684 iter = prevIter; 8685 if (!iter.getDummyChar(q)) break; 8686 } 8687 } 8688 prevIter = iter; 8689 NVGGlyphPosition position = void; //WARNING! 8690 position.strpos = cast(usize)(iter.stringp-str.ptr); 8691 position.x = iter.x*invscale; 8692 position.minx = nvg__min(iter.x, q.x0)*invscale; 8693 position.maxx = nvg__max(iter.nextx, q.x1)*invscale; 8694 ++npos; 8695 static if (RetBool) { if (!dg(position)) return npos; } else dg(position); 8696 } 8697 8698 return npos; 8699 } 8700 8701 private template isGoodRowDelegate(CT, DG) { 8702 private DG dg; 8703 static if (is(typeof({ NVGTextRow!CT row; bool res = dg(row); })) || 8704 is(typeof({ NVGTextRow!CT row; dg(row); }))) 8705 enum isGoodRowDelegate = true; 8706 else 8707 enum isGoodRowDelegate = false; 8708 } 8709 8710 /** Breaks the specified text into lines. 8711 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8712 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8713 * 8714 * Group: text_api 8715 */ 8716 public NVGTextRow!T[] textBreakLines(T) (NVGContext ctx, const(T)[] str, float breakRowWidth, NVGTextRow!T[] rows) nothrow @trusted @nogc 8717 if (isAnyCharType!T) 8718 { 8719 if (rows.length == 0) return rows; 8720 if (rows.length > int.max-1) rows = rows[0..int.max-1]; 8721 int nrow = 0; 8722 auto count = ctx.textBreakLines(str, breakRowWidth, (const scope ref NVGTextRow!T row) { 8723 rows[nrow++] = row; 8724 return (nrow < rows.length); 8725 }); 8726 return rows[0..count]; 8727 } 8728 8729 /** Breaks the specified text into lines. 8730 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8731 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8732 * Returns number of rows. 8733 * 8734 * Group: text_api 8735 */ 8736 public int textBreakLines(T, DG) (NVGContext ctx, const(T)[] str, float breakRowWidth, scope DG dg) 8737 if (isAnyCharType!T && isGoodRowDelegate!(T, DG)) 8738 { 8739 import std.traits : ReturnType; 8740 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true; 8741 8742 enum NVGcodepointType : int { 8743 Space, 8744 NewLine, 8745 Char, 8746 } 8747 8748 NVGstate* state = nvg__getState(ctx); 8749 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8750 float invscale = 1.0f/scale; 8751 FONSTextIter!T iter, prevIter; 8752 FONSQuad q; 8753 int nrows = 0; 8754 float rowStartX = 0; 8755 float rowWidth = 0; 8756 float rowMinX = 0; 8757 float rowMaxX = 0; 8758 int rowStart = 0; 8759 int rowEnd = 0; 8760 int wordStart = 0; 8761 float wordStartX = 0; 8762 float wordMinX = 0; 8763 int breakEnd = 0; 8764 float breakWidth = 0; 8765 float breakMaxX = 0; 8766 int type = NVGcodepointType.Space, ptype = NVGcodepointType.Space; 8767 uint pcodepoint = 0; 8768 8769 if (state.fontId == FONS_INVALID) return 0; 8770 if (str.length == 0 || dg is null) return 0; 8771 8772 ctx.fs.size = state.fontSize*scale; 8773 ctx.fs.spacing = state.letterSpacing*scale; 8774 ctx.fs.blur = state.fontBlur*scale; 8775 ctx.fs.textAlign = state.textAlign; 8776 ctx.fs.fontId = state.fontId; 8777 8778 breakRowWidth *= scale; 8779 8780 enum Phase { 8781 Normal, // searching for breaking point 8782 SkipBlanks, // skip leading blanks 8783 } 8784 Phase phase = Phase.SkipBlanks; // don't skip blanks on first line 8785 8786 if (!iter.setup(ctx.fs, 0, 0, str, FONSBitmapFlag.Optional)) return 0; 8787 prevIter = iter; 8788 while (iter.next(q)) { 8789 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8790 if (!nvg__allocTextAtlas(ctx)) break; // no memory 8791 iter = prevIter; 8792 iter.next(q); // try again 8793 if (iter.prevGlyphIndex < 0) { 8794 // still can not find glyph, try replacement 8795 iter = prevIter; 8796 if (!iter.getDummyChar(q)) break; 8797 } 8798 } 8799 prevIter = iter; 8800 switch (iter.codepoint) { 8801 case 9: // \t 8802 case 11: // \v 8803 case 12: // \f 8804 case 32: // space 8805 case 0x00a0: // NBSP 8806 type = NVGcodepointType.Space; 8807 break; 8808 case 10: // \n 8809 type = (pcodepoint == 13 ? NVGcodepointType.Space : NVGcodepointType.NewLine); 8810 break; 8811 case 13: // \r 8812 type = (pcodepoint == 10 ? NVGcodepointType.Space : NVGcodepointType.NewLine); 8813 break; 8814 case 0x0085: // NEL 8815 case 0x2028: // Line Separator 8816 case 0x2029: // Paragraph Separator 8817 type = NVGcodepointType.NewLine; 8818 break; 8819 default: 8820 type = NVGcodepointType.Char; 8821 break; 8822 } 8823 if (phase == Phase.SkipBlanks) { 8824 // fix row start 8825 rowStart = cast(int)(iter.stringp-str.ptr); 8826 rowEnd = rowStart; 8827 rowStartX = iter.x; 8828 rowWidth = iter.nextx-rowStartX; // q.x1-rowStartX; 8829 rowMinX = q.x0-rowStartX; 8830 rowMaxX = q.x1-rowStartX; 8831 wordStart = rowStart; 8832 wordStartX = iter.x; 8833 wordMinX = q.x0-rowStartX; 8834 breakEnd = rowStart; 8835 breakWidth = 0.0; 8836 breakMaxX = 0.0; 8837 if (type == NVGcodepointType.Space) continue; 8838 phase = Phase.Normal; 8839 } 8840 8841 if (type == NVGcodepointType.NewLine) { 8842 // always handle new lines 8843 NVGTextRow!T row; 8844 row.string = str; 8845 row.start = rowStart; 8846 row.end = rowEnd; 8847 row.width = rowWidth*invscale; 8848 row.minx = rowMinX*invscale; 8849 row.maxx = rowMaxX*invscale; 8850 ++nrows; 8851 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8852 phase = Phase.SkipBlanks; 8853 } else { 8854 float nextWidth = iter.nextx-rowStartX; 8855 // track last non-white space character 8856 if (type == NVGcodepointType.Char) { 8857 rowEnd = cast(int)(iter.nextp-str.ptr); 8858 rowWidth = iter.nextx-rowStartX; 8859 rowMaxX = q.x1-rowStartX; 8860 } 8861 // track last end of a word 8862 if (ptype == NVGcodepointType.Char && type == NVGcodepointType.Space) { 8863 breakEnd = cast(int)(iter.stringp-str.ptr); 8864 breakWidth = rowWidth; 8865 breakMaxX = rowMaxX; 8866 } 8867 // track last beginning of a word 8868 if (ptype == NVGcodepointType.Space && type == NVGcodepointType.Char) { 8869 wordStart = cast(int)(iter.stringp-str.ptr); 8870 wordStartX = iter.x; 8871 wordMinX = q.x0-rowStartX; 8872 } 8873 // break to new line when a character is beyond break width 8874 if (type == NVGcodepointType.Char && nextWidth > breakRowWidth) { 8875 // the run length is too long, need to break to new line 8876 NVGTextRow!T row; 8877 row.string = str; 8878 if (breakEnd == rowStart) { 8879 // the current word is longer than the row length, just break it from here 8880 row.start = rowStart; 8881 row.end = cast(int)(iter.stringp-str.ptr); 8882 row.width = rowWidth*invscale; 8883 row.minx = rowMinX*invscale; 8884 row.maxx = rowMaxX*invscale; 8885 ++nrows; 8886 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8887 rowStartX = iter.x; 8888 rowStart = cast(int)(iter.stringp-str.ptr); 8889 rowEnd = cast(int)(iter.nextp-str.ptr); 8890 rowWidth = iter.nextx-rowStartX; 8891 rowMinX = q.x0-rowStartX; 8892 rowMaxX = q.x1-rowStartX; 8893 wordStart = rowStart; 8894 wordStartX = iter.x; 8895 wordMinX = q.x0-rowStartX; 8896 } else { 8897 // break the line from the end of the last word, and start new line from the beginning of the new 8898 //{ import core.stdc.stdio : printf; printf("rowStart=%u; rowEnd=%u; breakEnd=%u; len=%u\n", rowStart, rowEnd, breakEnd, cast(uint)str.length); } 8899 row.start = rowStart; 8900 row.end = breakEnd; 8901 row.width = breakWidth*invscale; 8902 row.minx = rowMinX*invscale; 8903 row.maxx = breakMaxX*invscale; 8904 ++nrows; 8905 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8906 rowStartX = wordStartX; 8907 rowStart = wordStart; 8908 rowEnd = cast(int)(iter.nextp-str.ptr); 8909 rowWidth = iter.nextx-rowStartX; 8910 rowMinX = wordMinX; 8911 rowMaxX = q.x1-rowStartX; 8912 // no change to the word start 8913 } 8914 // set null break point 8915 breakEnd = rowStart; 8916 breakWidth = 0.0; 8917 breakMaxX = 0.0; 8918 } 8919 } 8920 8921 pcodepoint = iter.codepoint; 8922 ptype = type; 8923 } 8924 8925 // break the line from the end of the last word, and start new line from the beginning of the new 8926 if (phase != Phase.SkipBlanks && rowStart < str.length) { 8927 //{ import core.stdc.stdio : printf; printf(" rowStart=%u; len=%u\n", rowStart, cast(uint)str.length); } 8928 NVGTextRow!T row; 8929 row.string = str; 8930 row.start = rowStart; 8931 row.end = cast(int)str.length; 8932 row.width = rowWidth*invscale; 8933 row.minx = rowMinX*invscale; 8934 row.maxx = rowMaxX*invscale; 8935 ++nrows; 8936 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8937 } 8938 8939 return nrows; 8940 } 8941 8942 /** Returns iterator which you can use to calculate text bounds and advancement. 8943 * This is usable when you need to do some text layouting with wrapping, to avoid 8944 * guesswork ("will advancement for this space stay the same?"), and Schlemiel's 8945 * algorithm. Note that you can copy the returned struct to save iterator state. 8946 * 8947 * You can check if iterator is valid with [valid] property, put new chars with 8948 * [put] method, get current advance with [advance] property, and current 8949 * bounds with `getBounds(ref float[4] bounds)` method. 8950 * 8951 * $(WARNING Don't change font parameters while iterating! Or use [restoreFont] method.) 8952 * 8953 * Group: text_api 8954 */ 8955 public struct TextBoundsIterator { 8956 private: 8957 NVGContext ctx; 8958 FONSTextBoundsIterator fsiter; // fontstash iterator 8959 float scale, invscale, xscaled, yscaled; 8960 // font settings 8961 float fsSize, fsSpacing, fsBlur; 8962 int fsFontId; 8963 NVGTextAlign fsAlign; 8964 8965 public: 8966 /// Setups iteration. Takes current font parameters from the given NanoVega context. 8967 this (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { reset(actx, ax, ay); } 8968 8969 /// Resets iteration. Takes current font parameters from the given NanoVega context. 8970 void reset (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { 8971 fsiter = fsiter.init; 8972 this = this.init; 8973 if (actx is null) return; 8974 NVGstate* state = nvg__getState(actx); 8975 if (state is null) return; 8976 if (state.fontId == FONS_INVALID) { ctx = null; return; } 8977 8978 ctx = actx; 8979 scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8980 invscale = 1.0f/scale; 8981 8982 fsSize = state.fontSize*scale; 8983 fsSpacing = state.letterSpacing*scale; 8984 fsBlur = state.fontBlur*scale; 8985 fsAlign = state.textAlign; 8986 fsFontId = state.fontId; 8987 restoreFont(); 8988 8989 xscaled = ax*scale; 8990 yscaled = ay*scale; 8991 fsiter.reset(ctx.fs, xscaled, yscaled); 8992 } 8993 8994 /// Restart iteration. Will not restore font. 8995 void restart () nothrow @trusted @nogc { 8996 if (ctx !is null) fsiter.reset(ctx.fs, xscaled, yscaled); 8997 } 8998 8999 /// Restore font settings for the context. 9000 void restoreFont () nothrow @trusted @nogc { 9001 if (ctx !is null) { 9002 ctx.fs.size = fsSize; 9003 ctx.fs.spacing = fsSpacing; 9004 ctx.fs.blur = fsBlur; 9005 ctx.fs.textAlign = fsAlign; 9006 ctx.fs.fontId = fsFontId; 9007 } 9008 } 9009 9010 /// Is this iterator valid? 9011 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null); } 9012 9013 /// Add chars. 9014 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { pragma(inline, true); if (ctx !is null) fsiter.put(str[]); } 9015 9016 /// Returns current advance 9017 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null ? fsiter.advance*invscale : 0); } 9018 9019 /// Returns current text bounds. 9020 void getBounds (ref float[4] bounds) nothrow @trusted @nogc { 9021 if (ctx !is null) { 9022 fsiter.getBounds(bounds); 9023 ctx.fs.getLineBounds(yscaled, &bounds[1], &bounds[3]); 9024 bounds[0] *= invscale; 9025 bounds[1] *= invscale; 9026 bounds[2] *= invscale; 9027 bounds[3] *= invscale; 9028 } else { 9029 bounds[] = 0; 9030 } 9031 } 9032 9033 /// Returns current horizontal text bounds. 9034 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc { 9035 if (ctx !is null) { 9036 fsiter.getHBounds(xmin, xmax); 9037 xmin *= invscale; 9038 xmax *= invscale; 9039 } 9040 } 9041 9042 /// Returns current vertical text bounds. 9043 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc { 9044 if (ctx !is null) { 9045 //fsiter.getVBounds(ymin, ymax); 9046 ctx.fs.getLineBounds(yscaled, &ymin, &ymax); 9047 ymin *= invscale; 9048 ymax *= invscale; 9049 } 9050 } 9051 } 9052 9053 /// Returns font line height (without line spacing), measured in local coordinate space. 9054 /// Group: text_api 9055 public float textFontHeight (NVGContext ctx) nothrow @trusted @nogc { 9056 float res = void; 9057 ctx.textMetrics(null, null, &res); 9058 return res; 9059 } 9060 9061 /// Returns font ascender (positive), measured in local coordinate space. 9062 /// Group: text_api 9063 public float textFontAscender (NVGContext ctx) nothrow @trusted @nogc { 9064 float res = void; 9065 ctx.textMetrics(&res, null, null); 9066 return res; 9067 } 9068 9069 /// Returns font descender (negative), measured in local coordinate space. 9070 /// Group: text_api 9071 public float textFontDescender (NVGContext ctx) nothrow @trusted @nogc { 9072 float res = void; 9073 ctx.textMetrics(null, &res, null); 9074 return res; 9075 } 9076 9077 /** Measures the specified text string. Returns horizontal and vertical sizes of the measured text. 9078 * Measured values are returned in local coordinate space. 9079 * 9080 * Group: text_api 9081 */ 9082 public void textExtents(T) (NVGContext ctx, const(T)[] str, float *w, float *h) nothrow @trusted @nogc if (isAnyCharType!T) { 9083 float[4] bnd = void; 9084 ctx.textBounds(0, 0, str, bnd[]); 9085 if (!ctx.fs.getFontAA(nvg__getState(ctx).fontId)) { 9086 if (w !is null) *w = nvg__lrintf(bnd.ptr[2]-bnd.ptr[0]); 9087 if (h !is null) *h = nvg__lrintf(bnd.ptr[3]-bnd.ptr[1]); 9088 } else { 9089 if (w !is null) *w = bnd.ptr[2]-bnd.ptr[0]; 9090 if (h !is null) *h = bnd.ptr[3]-bnd.ptr[1]; 9091 } 9092 } 9093 9094 /** Measures the specified text string. Returns horizontal size of the measured text. 9095 * Measured values are returned in local coordinate space. 9096 * 9097 * Group: text_api 9098 */ 9099 public float textWidth(T) (NVGContext ctx, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 9100 float w = void; 9101 ctx.textExtents(str, &w, null); 9102 return w; 9103 } 9104 9105 /** Measures the specified text string. Parameter bounds should be a float[4], 9106 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax] 9107 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn). 9108 * Measured values are returned in local coordinate space. 9109 * 9110 * Group: text_api 9111 */ 9112 public float textBounds(T) (NVGContext ctx, float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc 9113 if (isAnyCharType!T) 9114 { 9115 NVGstate* state = nvg__getState(ctx); 9116 9117 if (state.fontId == FONS_INVALID) { 9118 bounds[] = 0; 9119 return 0; 9120 } 9121 9122 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9123 ctx.fs.size = state.fontSize*scale; 9124 ctx.fs.spacing = state.letterSpacing*scale; 9125 ctx.fs.blur = state.fontBlur*scale; 9126 ctx.fs.textAlign = state.textAlign; 9127 ctx.fs.fontId = state.fontId; 9128 9129 float[4] b = void; 9130 immutable float width = ctx.fs.getTextBounds(x*scale, y*scale, str, b[]); 9131 immutable float invscale = 1.0f/scale; 9132 if (bounds.length) { 9133 // use line bounds for height 9134 ctx.fs.getLineBounds(y*scale, b.ptr+1, b.ptr+3); 9135 if (bounds.length > 0) bounds.ptr[0] = b.ptr[0]*invscale; 9136 if (bounds.length > 1) bounds.ptr[1] = b.ptr[1]*invscale; 9137 if (bounds.length > 2) bounds.ptr[2] = b.ptr[2]*invscale; 9138 if (bounds.length > 3) bounds.ptr[3] = b.ptr[3]*invscale; 9139 } 9140 return width*invscale; 9141 } 9142 9143 /// Ditto. 9144 public void textBoxBounds(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str, float[] bounds) if (isAnyCharType!T) { 9145 NVGstate* state = nvg__getState(ctx); 9146 NVGTextRow!T[2] rows; 9147 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9148 float invscale = 1.0f/scale; 9149 float lineh = 0, rminy = 0, rmaxy = 0; 9150 float minx, miny, maxx, maxy; 9151 9152 if (state.fontId == FONS_INVALID) { 9153 bounds[] = 0; 9154 return; 9155 } 9156 9157 auto oldAlign = state.textAlign; 9158 scope(exit) state.textAlign = oldAlign; 9159 auto halign = state.textAlign.horizontal; 9160 9161 ctx.textMetrics(null, null, &lineh); 9162 state.textAlign.horizontal = NVGTextAlign.H.Left; 9163 9164 minx = maxx = x; 9165 miny = maxy = y; 9166 9167 ctx.fs.size = state.fontSize*scale; 9168 ctx.fs.spacing = state.letterSpacing*scale; 9169 ctx.fs.blur = state.fontBlur*scale; 9170 ctx.fs.textAlign = state.textAlign; 9171 ctx.fs.fontId = state.fontId; 9172 ctx.fs.getLineBounds(0, &rminy, &rmaxy); 9173 rminy *= invscale; 9174 rmaxy *= invscale; 9175 9176 for (;;) { 9177 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]); 9178 if (rres.length == 0) break; 9179 foreach (ref row; rres) { 9180 float rminx, rmaxx, dx = 0; 9181 // horizontal bounds 9182 final switch (halign) { 9183 case NVGTextAlign.H.Left: dx = 0; break; 9184 case NVGTextAlign.H.Center: dx = breakRowWidth*0.5f-row.width*0.5f; break; 9185 case NVGTextAlign.H.Right: dx = breakRowWidth-row.width; break; 9186 } 9187 rminx = x+row.minx+dx; 9188 rmaxx = x+row.maxx+dx; 9189 minx = nvg__min(minx, rminx); 9190 maxx = nvg__max(maxx, rmaxx); 9191 // vertical bounds 9192 miny = nvg__min(miny, y+rminy); 9193 maxy = nvg__max(maxy, y+rmaxy); 9194 y += lineh*state.lineHeight; 9195 } 9196 str = rres[$-1].rest; 9197 } 9198 9199 if (bounds.length) { 9200 if (bounds.length > 0) bounds.ptr[0] = minx; 9201 if (bounds.length > 1) bounds.ptr[1] = miny; 9202 if (bounds.length > 2) bounds.ptr[2] = maxx; 9203 if (bounds.length > 3) bounds.ptr[3] = maxy; 9204 } 9205 } 9206 9207 /// Returns the vertical metrics based on the current text style. Measured values are returned in local coordinate space. 9208 /// Group: text_api 9209 public void textMetrics (NVGContext ctx, float* ascender, float* descender, float* lineh) nothrow @trusted @nogc { 9210 NVGstate* state = nvg__getState(ctx); 9211 9212 if (state.fontId == FONS_INVALID) { 9213 if (ascender !is null) *ascender *= 0; 9214 if (descender !is null) *descender *= 0; 9215 if (lineh !is null) *lineh *= 0; 9216 return; 9217 } 9218 9219 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9220 immutable float invscale = 1.0f/scale; 9221 9222 ctx.fs.size = state.fontSize*scale; 9223 ctx.fs.spacing = state.letterSpacing*scale; 9224 ctx.fs.blur = state.fontBlur*scale; 9225 ctx.fs.textAlign = state.textAlign; 9226 ctx.fs.fontId = state.fontId; 9227 9228 ctx.fs.getVertMetrics(ascender, descender, lineh); 9229 if (ascender !is null) *ascender *= invscale; 9230 if (descender !is null) *descender *= invscale; 9231 if (lineh !is null) *lineh *= invscale; 9232 } 9233 9234 9235 // ////////////////////////////////////////////////////////////////////////// // 9236 // fontstash 9237 // ////////////////////////////////////////////////////////////////////////// // 9238 import core.stdc.stdlib : malloc, realloc, free; 9239 import core.stdc.string : memset, memcpy, strncpy, strcmp, strlen; 9240 import core.stdc.stdio : FILE, fopen, fclose, fseek, ftell, fread, SEEK_END, SEEK_SET; 9241 9242 public: 9243 // welcome to version hell! 9244 version(nanovg_force_stb_ttf) { 9245 } else { 9246 version(nanovg_force_detect) {} else version(nanovg_use_freetype) { version = nanovg_use_freetype_ii; } 9247 } 9248 version(nanovg_ignore_iv_stb_ttf) enum nanovg_ignore_iv_stb_ttf = true; else enum nanovg_ignore_iv_stb_ttf = false; 9249 //version(nanovg_ignore_mono); 9250 9251 version(nanovg_force_stb_ttf) { 9252 private enum NanoVegaForceFreeType = false; 9253 } else { 9254 version (nanovg_builtin_freetype_bindings) { 9255 version(Posix) { 9256 private enum NanoVegaForceFreeType = true; 9257 } else { 9258 private enum NanoVegaForceFreeType = false; 9259 } 9260 } else { 9261 version(Posix) { 9262 private enum NanoVegaForceFreeType = true; 9263 } else { 9264 private enum NanoVegaForceFreeType = false; 9265 } 9266 } 9267 } 9268 9269 version(nanovg_use_freetype_ii) { 9270 enum NanoVegaIsUsingSTBTTF = false; 9271 //pragma(msg, "iv.freetype: forced"); 9272 } else { 9273 static if (NanoVegaForceFreeType) { 9274 enum NanoVegaIsUsingSTBTTF = false; 9275 } else { 9276 static if (!nanovg_ignore_iv_stb_ttf && __traits(compiles, { import iv.stb.ttf; })) { 9277 import iv.stb.ttf; 9278 enum NanoVegaIsUsingSTBTTF = true; 9279 version(nanovg_report_stb_ttf) pragma(msg, "iv.stb.ttf"); 9280 } else static if (__traits(compiles, { import arsd.ttf; })) { 9281 import arsd.ttf; 9282 enum NanoVegaIsUsingSTBTTF = true; 9283 version(nanovg_report_stb_ttf) pragma(msg, "arsd.ttf"); 9284 } else static if (__traits(compiles, { import stb_truetype; })) { 9285 import stb_truetype; 9286 enum NanoVegaIsUsingSTBTTF = true; 9287 version(nanovg_report_stb_ttf) pragma(msg, "stb_truetype"); 9288 } else static if (__traits(compiles, { import iv.freetype; })) { 9289 version (nanovg_builtin_freetype_bindings) { 9290 enum NanoVegaIsUsingSTBTTF = false; 9291 version = nanovg_builtin_freetype_bindings; 9292 } else { 9293 import iv.freetype; 9294 enum NanoVegaIsUsingSTBTTF = false; 9295 } 9296 version(nanovg_report_stb_ttf) pragma(msg, "freetype"); 9297 } else { 9298 static assert(0, "no stb_ttf/iv.freetype found!"); 9299 } 9300 } 9301 } 9302 9303 9304 // ////////////////////////////////////////////////////////////////////////// // 9305 //version = nanovg_ft_mono; 9306 9307 /// Invald font id. 9308 /// Group: font_stash 9309 public enum FONS_INVALID = -1; 9310 9311 public enum FONSBitmapFlag : uint { 9312 Required = 0, 9313 Optional = 1, 9314 } 9315 9316 public enum FONSError : int { 9317 NoError = 0, 9318 AtlasFull = 1, // Font atlas is full. 9319 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. 9320 StatesOverflow = 3, // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES. 9321 StatesUnderflow = 4, // Trying to pop too many states fonsPopState(). 9322 } 9323 9324 /// Initial parameters for new FontStash. 9325 /// Group: font_stash 9326 public struct FONSParams { 9327 enum Flag : uint { 9328 ZeroTopLeft = 0U, // default 9329 ZeroBottomLeft = 1U, 9330 } 9331 int width, height; 9332 Flag flags = Flag.ZeroTopLeft; 9333 void* userPtr; 9334 bool function (void* uptr, int width, int height) nothrow @trusted @nogc renderCreate; 9335 int function (void* uptr, int width, int height) nothrow @trusted @nogc renderResize; 9336 void function (void* uptr, int* rect, const(ubyte)* data) nothrow @trusted @nogc renderUpdate; 9337 void function (void* uptr) nothrow @trusted @nogc renderDelete; 9338 @property bool isZeroTopLeft () const pure nothrow @trusted @nogc { pragma(inline, true); return ((flags&Flag.ZeroBottomLeft) == 0); } 9339 } 9340 9341 //TODO: document this 9342 public struct FONSQuad { 9343 float x0=0, y0=0, s0=0, t0=0; 9344 float x1=0, y1=0, s1=0, t1=0; 9345 } 9346 9347 //TODO: document this 9348 public struct FONSTextIter(CT) if (isAnyCharType!CT) { 9349 alias CharType = CT; 9350 float x=0, y=0, nextx=0, nexty=0, scale=0, spacing=0; 9351 uint codepoint; 9352 short isize, iblur; 9353 FONSContext stash; 9354 FONSfont* font; 9355 int prevGlyphIndex; 9356 const(CT)* s; // string 9357 const(CT)* n; // next 9358 const(CT)* e; // end 9359 FONSBitmapFlag bitmapOption; 9360 static if (is(CT == char)) { 9361 uint utf8state; 9362 } 9363 9364 this (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { setup(astash, ax, ay, astr, abitmapOption); } 9365 ~this () nothrow @trusted @nogc { pragma(inline, true); static if (is(CT == char)) utf8state = 0; s = n = e = null; } 9366 9367 @property const(CT)* stringp () const pure nothrow @trusted @nogc { pragma(inline, true); return s; } 9368 @property const(CT)* nextp () const pure nothrow @trusted @nogc { pragma(inline, true); return n; } 9369 @property const(CT)* endp () const pure nothrow @trusted @nogc { pragma(inline, true); return e; } 9370 9371 bool setup (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { 9372 import core.stdc.string : memset; 9373 9374 memset(&this, 0, this.sizeof); 9375 if (astash is null) return false; 9376 9377 FONSstate* state = astash.getState; 9378 9379 if (state.font < 0 || state.font >= astash.nfonts) return false; 9380 font = astash.fonts[state.font]; 9381 if (font is null || font.fdata is null) return false; 9382 9383 isize = cast(short)(state.size*10.0f); 9384 iblur = cast(short)state.blur; 9385 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 9386 9387 // align horizontally 9388 if (state.talign.left) { 9389 // empty 9390 } else if (state.talign.right) { 9391 immutable float width = astash.getTextBounds(ax, ay, astr, null); 9392 ax -= width; 9393 } else if (state.talign.center) { 9394 immutable float width = astash.getTextBounds(ax, ay, astr, null); 9395 ax -= width*0.5f; 9396 } 9397 9398 // align vertically 9399 ay += astash.getVertAlign(font, state.talign, isize); 9400 9401 x = nextx = ax; 9402 y = nexty = ay; 9403 spacing = state.spacing; 9404 9405 if (astr.ptr is null) { 9406 static if (is(CharType == char)) astr = ""; 9407 else static if (is(CharType == wchar)) astr = ""w; 9408 else static if (is(CharType == dchar)) astr = ""d; 9409 else static assert(0, "wtf?!"); 9410 } 9411 s = astr.ptr; 9412 n = astr.ptr; 9413 e = astr.ptr+astr.length; 9414 9415 codepoint = 0; 9416 prevGlyphIndex = -1; 9417 bitmapOption = abitmapOption; 9418 stash = astash; 9419 9420 return true; 9421 } 9422 9423 bool getDummyChar (ref FONSQuad quad) nothrow @trusted @nogc { 9424 if (stash is null || font is null) return false; 9425 // get glyph and quad 9426 x = nextx; 9427 y = nexty; 9428 FONSglyph* glyph = stash.getGlyph(font, 0xFFFD, isize, iblur, bitmapOption); 9429 if (glyph !is null) { 9430 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9431 prevGlyphIndex = glyph.index; 9432 return true; 9433 } else { 9434 prevGlyphIndex = -1; 9435 return false; 9436 } 9437 } 9438 9439 bool next (ref FONSQuad quad) nothrow @trusted @nogc { 9440 if (stash is null || font is null) return false; 9441 FONSglyph* glyph = null; 9442 static if (is(CharType == char)) { 9443 const(char)* str = this.n; 9444 this.s = this.n; 9445 if (str is this.e) return false; 9446 const(char)* e = this.e; 9447 for (; str !is e; ++str) { 9448 /*if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;*/ 9449 mixin(DecUtfMixin!("this.utf8state", "this.codepoint", "*cast(const(ubyte)*)str")); 9450 if (utf8state) continue; 9451 ++str; // 'cause we'll break anyway 9452 // get glyph and quad 9453 x = nextx; 9454 y = nexty; 9455 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption); 9456 if (glyph !is null) { 9457 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9458 prevGlyphIndex = glyph.index; 9459 } else { 9460 prevGlyphIndex = -1; 9461 } 9462 break; 9463 } 9464 this.n = str; 9465 } else { 9466 const(CharType)* str = this.n; 9467 this.s = this.n; 9468 if (str is this.e) return false; 9469 codepoint = cast(uint)(*str++); 9470 if (codepoint > dchar.max) codepoint = 0xFFFD; 9471 // get glyph and quad 9472 x = nextx; 9473 y = nexty; 9474 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption); 9475 if (glyph !is null) { 9476 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9477 prevGlyphIndex = glyph.index; 9478 } else { 9479 prevGlyphIndex = -1; 9480 } 9481 this.n = str; 9482 } 9483 return true; 9484 } 9485 } 9486 9487 9488 // ////////////////////////////////////////////////////////////////////////// // 9489 //static if (!HasAST) version = nanovg_use_freetype_ii_x; 9490 9491 /*version(nanovg_use_freetype_ii_x)*/ static if (!NanoVegaIsUsingSTBTTF) { 9492 version(nanovg_builtin_freetype_bindings) { 9493 pragma(lib, "freetype"); 9494 private extern(C) nothrow @trusted @nogc { 9495 private import core.stdc.config : c_long, c_ulong; 9496 alias FT_Pos = c_long; 9497 // config/ftconfig.h 9498 alias FT_Int16 = short; 9499 alias FT_UInt16 = ushort; 9500 alias FT_Int32 = int; 9501 alias FT_UInt32 = uint; 9502 alias FT_Fast = int; 9503 alias FT_UFast = uint; 9504 alias FT_Int64 = long; 9505 alias FT_Uint64 = ulong; 9506 // fttypes.h 9507 alias FT_Bool = ubyte; 9508 alias FT_FWord = short; 9509 alias FT_UFWord = ushort; 9510 alias FT_Char = char; 9511 alias FT_Byte = ubyte; 9512 alias FT_Bytes = FT_Byte*; 9513 alias FT_Tag = FT_UInt32; 9514 alias FT_String = char; 9515 alias FT_Short = short; 9516 alias FT_UShort = ushort; 9517 alias FT_Int = int; 9518 alias FT_UInt = uint; 9519 alias FT_Long = c_long; 9520 alias FT_ULong = c_ulong; 9521 alias FT_F2Dot14 = short; 9522 alias FT_F26Dot6 = c_long; 9523 alias FT_Fixed = c_long; 9524 alias FT_Error = int; 9525 alias FT_Pointer = void*; 9526 alias FT_Offset = usize; 9527 alias FT_PtrDist = ptrdiff_t; 9528 9529 struct FT_UnitVector { 9530 FT_F2Dot14 x; 9531 FT_F2Dot14 y; 9532 } 9533 9534 struct FT_Matrix { 9535 FT_Fixed xx, xy; 9536 FT_Fixed yx, yy; 9537 } 9538 9539 struct FT_Data { 9540 const(FT_Byte)* pointer; 9541 FT_Int length; 9542 } 9543 alias FT_Face = FT_FaceRec*; 9544 struct FT_FaceRec { 9545 FT_Long num_faces; 9546 FT_Long face_index; 9547 FT_Long face_flags; 9548 FT_Long style_flags; 9549 FT_Long num_glyphs; 9550 FT_String* family_name; 9551 FT_String* style_name; 9552 FT_Int num_fixed_sizes; 9553 FT_Bitmap_Size* available_sizes; 9554 FT_Int num_charmaps; 9555 FT_CharMap* charmaps; 9556 FT_Generic generic; 9557 FT_BBox bbox; 9558 FT_UShort units_per_EM; 9559 FT_Short ascender; 9560 FT_Short descender; 9561 FT_Short height; 9562 FT_Short max_advance_width; 9563 FT_Short max_advance_height; 9564 FT_Short underline_position; 9565 FT_Short underline_thickness; 9566 FT_GlyphSlot glyph; 9567 FT_Size size; 9568 FT_CharMap charmap; 9569 FT_Driver driver; 9570 FT_Memory memory; 9571 FT_Stream stream; 9572 FT_ListRec sizes_list; 9573 FT_Generic autohint; 9574 void* extensions; 9575 FT_Face_Internal internal; 9576 } 9577 struct FT_Bitmap_Size { 9578 FT_Short height; 9579 FT_Short width; 9580 FT_Pos size; 9581 FT_Pos x_ppem; 9582 FT_Pos y_ppem; 9583 } 9584 alias FT_CharMap = FT_CharMapRec*; 9585 struct FT_CharMapRec { 9586 FT_Face face; 9587 FT_Encoding encoding; 9588 FT_UShort platform_id; 9589 FT_UShort encoding_id; 9590 } 9591 extern(C) nothrow @nogc { alias FT_Generic_Finalizer = void function (void* object); } 9592 struct FT_Generic { 9593 void* data; 9594 FT_Generic_Finalizer finalizer; 9595 } 9596 struct FT_Vector { 9597 FT_Pos x; 9598 FT_Pos y; 9599 } 9600 struct FT_BBox { 9601 FT_Pos xMin, yMin; 9602 FT_Pos xMax, yMax; 9603 } 9604 alias FT_Pixel_Mode = int; 9605 enum { 9606 FT_PIXEL_MODE_NONE = 0, 9607 FT_PIXEL_MODE_MONO, 9608 FT_PIXEL_MODE_GRAY, 9609 FT_PIXEL_MODE_GRAY2, 9610 FT_PIXEL_MODE_GRAY4, 9611 FT_PIXEL_MODE_LCD, 9612 FT_PIXEL_MODE_LCD_V, 9613 FT_PIXEL_MODE_MAX 9614 } 9615 struct FT_Bitmap { 9616 uint rows; 9617 uint width; 9618 int pitch; 9619 ubyte* buffer; 9620 ushort num_grays; 9621 ubyte pixel_mode; 9622 ubyte palette_mode; 9623 void* palette; 9624 } 9625 struct FT_Outline { 9626 short n_contours; 9627 short n_points; 9628 FT_Vector* points; 9629 byte* tags; 9630 short* contours; 9631 int flags; 9632 } 9633 alias FT_GlyphSlot = FT_GlyphSlotRec*; 9634 struct FT_GlyphSlotRec { 9635 FT_Library library; 9636 FT_Face face; 9637 FT_GlyphSlot next; 9638 FT_UInt reserved; 9639 FT_Generic generic; 9640 FT_Glyph_Metrics metrics; 9641 FT_Fixed linearHoriAdvance; 9642 FT_Fixed linearVertAdvance; 9643 FT_Vector advance; 9644 FT_Glyph_Format format; 9645 FT_Bitmap bitmap; 9646 FT_Int bitmap_left; 9647 FT_Int bitmap_top; 9648 FT_Outline outline; 9649 FT_UInt num_subglyphs; 9650 FT_SubGlyph subglyphs; 9651 void* control_data; 9652 c_long control_len; 9653 FT_Pos lsb_delta; 9654 FT_Pos rsb_delta; 9655 void* other; 9656 FT_Slot_Internal internal; 9657 } 9658 alias FT_Size = FT_SizeRec*; 9659 struct FT_SizeRec { 9660 FT_Face face; 9661 FT_Generic generic; 9662 FT_Size_Metrics metrics; 9663 FT_Size_Internal internal; 9664 } 9665 alias FT_Encoding = FT_Tag; 9666 alias FT_Face_Internal = void*; 9667 alias FT_Driver = void*; 9668 alias FT_Memory = void*; 9669 alias FT_Stream = void*; 9670 alias FT_Library = void*; 9671 alias FT_SubGlyph = void*; 9672 alias FT_Slot_Internal = void*; 9673 alias FT_Size_Internal = void*; 9674 alias FT_ListNode = FT_ListNodeRec*; 9675 alias FT_List = FT_ListRec*; 9676 struct FT_ListNodeRec { 9677 FT_ListNode prev; 9678 FT_ListNode next; 9679 void* data; 9680 } 9681 struct FT_ListRec { 9682 FT_ListNode head; 9683 FT_ListNode tail; 9684 } 9685 struct FT_Glyph_Metrics { 9686 FT_Pos width; 9687 FT_Pos height; 9688 FT_Pos horiBearingX; 9689 FT_Pos horiBearingY; 9690 FT_Pos horiAdvance; 9691 FT_Pos vertBearingX; 9692 FT_Pos vertBearingY; 9693 FT_Pos vertAdvance; 9694 } 9695 alias FT_Glyph_Format = FT_Tag; 9696 FT_Tag FT_MAKE_TAG (char x1, char x2, char x3, char x4) pure nothrow @safe @nogc { 9697 pragma(inline, true); 9698 return cast(FT_UInt32)((x1<<24)|(x2<<16)|(x3<<8)|x4); 9699 } 9700 enum : FT_Tag { 9701 FT_GLYPH_FORMAT_NONE = 0, 9702 FT_GLYPH_FORMAT_COMPOSITE = FT_MAKE_TAG('c','o','m','p'), 9703 FT_GLYPH_FORMAT_BITMAP = FT_MAKE_TAG('b','i','t','s'), 9704 FT_GLYPH_FORMAT_OUTLINE = FT_MAKE_TAG('o','u','t','l'), 9705 FT_GLYPH_FORMAT_PLOTTER = FT_MAKE_TAG('p','l','o','t'), 9706 } 9707 struct FT_Size_Metrics { 9708 FT_UShort x_ppem; 9709 FT_UShort y_ppem; 9710 9711 FT_Fixed x_scale; 9712 FT_Fixed y_scale; 9713 9714 FT_Pos ascender; 9715 FT_Pos descender; 9716 FT_Pos height; 9717 FT_Pos max_advance; 9718 } 9719 enum FT_LOAD_DEFAULT = 0x0U; 9720 enum FT_LOAD_NO_SCALE = 1U<<0; 9721 enum FT_LOAD_NO_HINTING = 1U<<1; 9722 enum FT_LOAD_RENDER = 1U<<2; 9723 enum FT_LOAD_NO_BITMAP = 1U<<3; 9724 enum FT_LOAD_VERTICAL_LAYOUT = 1U<<4; 9725 enum FT_LOAD_FORCE_AUTOHINT = 1U<<5; 9726 enum FT_LOAD_CROP_BITMAP = 1U<<6; 9727 enum FT_LOAD_PEDANTIC = 1U<<7; 9728 enum FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1U<<9; 9729 enum FT_LOAD_NO_RECURSE = 1U<<10; 9730 enum FT_LOAD_IGNORE_TRANSFORM = 1U<<11; 9731 enum FT_LOAD_MONOCHROME = 1U<<12; 9732 enum FT_LOAD_LINEAR_DESIGN = 1U<<13; 9733 enum FT_LOAD_NO_AUTOHINT = 1U<<15; 9734 enum FT_LOAD_COLOR = 1U<<20; 9735 enum FT_LOAD_COMPUTE_METRICS = 1U<<21; 9736 enum FT_FACE_FLAG_KERNING = 1U<<6; 9737 alias FT_Kerning_Mode = int; 9738 enum /*FT_Kerning_Mode*/ { 9739 FT_KERNING_DEFAULT = 0, 9740 FT_KERNING_UNFITTED, 9741 FT_KERNING_UNSCALED 9742 } 9743 extern(C) nothrow @nogc { 9744 alias FT_Outline_MoveToFunc = int function (const(FT_Vector)*, void*); 9745 alias FT_Outline_LineToFunc = int function (const(FT_Vector)*, void*); 9746 alias FT_Outline_ConicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, void*); 9747 alias FT_Outline_CubicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, const(FT_Vector)*, void*); 9748 } 9749 struct FT_Outline_Funcs { 9750 FT_Outline_MoveToFunc move_to; 9751 FT_Outline_LineToFunc line_to; 9752 FT_Outline_ConicToFunc conic_to; 9753 FT_Outline_CubicToFunc cubic_to; 9754 int shift; 9755 FT_Pos delta; 9756 } 9757 9758 FT_Error FT_Init_FreeType (FT_Library*); 9759 FT_Error FT_New_Memory_Face (FT_Library, const(FT_Byte)*, FT_Long, FT_Long, FT_Face*); 9760 FT_UInt FT_Get_Char_Index (FT_Face, FT_ULong); 9761 FT_Error FT_Set_Pixel_Sizes (FT_Face, FT_UInt, FT_UInt); 9762 FT_Error FT_Load_Glyph (FT_Face, FT_UInt, FT_Int32); 9763 FT_Error FT_Get_Advance (FT_Face, FT_UInt, FT_Int32, FT_Fixed*); 9764 FT_Error FT_Get_Kerning (FT_Face, FT_UInt, FT_UInt, FT_UInt, FT_Vector*); 9765 void FT_Outline_Get_CBox (const(FT_Outline)*, FT_BBox*); 9766 FT_Error FT_Outline_Decompose (FT_Outline*, const(FT_Outline_Funcs)*, void*); 9767 } 9768 } else version(bindbc) { 9769 import bindbc.freetype; 9770 alias FT_KERNING_DEFAULT = FT_Kerning_Mode.FT_KERNING_DEFAULT; 9771 alias FT_KERNING_UNFITTED = FT_Kerning_Mode.FT_KERNING_UNFITTED; 9772 alias FT_KERNING_UNSCALED = FT_Kerning_Mode.FT_KERNING_UNSCALED; 9773 } else { 9774 import iv.freetype; 9775 } 9776 9777 struct FONSttFontImpl { 9778 FT_Face font; 9779 bool mono; // no aa? 9780 } 9781 9782 __gshared FT_Library ftLibrary; 9783 9784 int fons__tt_init (FONSContext context) nothrow @trusted @nogc { 9785 FT_Error ftError; 9786 //FONS_NOTUSED(context); 9787 ftError = FT_Init_FreeType(&ftLibrary); 9788 return (ftError == 0); 9789 } 9790 9791 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc { 9792 font.mono = v; 9793 } 9794 9795 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc { 9796 return font.mono; 9797 } 9798 9799 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc { 9800 FT_Error ftError; 9801 //font.font.userdata = stash; 9802 ftError = FT_New_Memory_Face(ftLibrary, cast(const(FT_Byte)*)data, dataSize, 0, &font.font); 9803 return ftError == 0; 9804 } 9805 9806 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc { 9807 *ascent = font.font.ascender; 9808 *descent = font.font.descender; 9809 *lineGap = font.font.height-(*ascent - *descent); 9810 } 9811 9812 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc { 9813 return size/(font.font.ascender-font.font.descender); 9814 } 9815 9816 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc { 9817 return FT_Get_Char_Index(font.font, codepoint); 9818 } 9819 9820 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 { 9821 FT_Error ftError; 9822 FT_GlyphSlot ftGlyph; 9823 //version(nanovg_ignore_mono) enum exflags = 0; 9824 //else version(nanovg_ft_mono) enum exflags = FT_LOAD_MONOCHROME; else enum exflags = 0; 9825 uint exflags = (font.mono ? FT_LOAD_MONOCHROME : 0); 9826 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))); 9827 if (ftError) return 0; 9828 ftError = FT_Load_Glyph(font.font, glyph, FT_LOAD_RENDER|/*FT_LOAD_NO_AUTOHINT|*/exflags); 9829 if (ftError) return 0; 9830 ftError = FT_Get_Advance(font.font, glyph, FT_LOAD_NO_SCALE|/*FT_LOAD_NO_AUTOHINT|*/exflags, cast(FT_Fixed*)advance); 9831 if (ftError) return 0; 9832 ftGlyph = font.font.glyph; 9833 *lsb = cast(int)ftGlyph.metrics.horiBearingX; 9834 *x0 = ftGlyph.bitmap_left; 9835 *x1 = *x0+ftGlyph.bitmap.width; 9836 *y0 = -ftGlyph.bitmap_top; 9837 *y1 = *y0+ftGlyph.bitmap.rows; 9838 return 1; 9839 } 9840 9841 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc { 9842 FT_GlyphSlot ftGlyph = font.font.glyph; 9843 //FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap 9844 //version(nanovg_ignore_mono) enum RenderAA = true; 9845 //else version(nanovg_ft_mono) enum RenderAA = false; 9846 //else enum RenderAA = true; 9847 if (font.mono) { 9848 auto src = ftGlyph.bitmap.buffer; 9849 auto dst = output; 9850 auto spt = ftGlyph.bitmap.pitch; 9851 if (spt < 0) spt = -spt; 9852 foreach (int y; 0..ftGlyph.bitmap.rows) { 9853 ubyte count = 0, b = 0; 9854 auto s = src; 9855 auto d = dst; 9856 foreach (int x; 0..ftGlyph.bitmap.width) { 9857 if (count-- == 0) { count = 7; b = *s++; } else b <<= 1; 9858 *d++ = (b&0x80 ? 255 : 0); 9859 } 9860 src += spt; 9861 dst += outStride; 9862 } 9863 } else { 9864 auto src = ftGlyph.bitmap.buffer; 9865 auto dst = output; 9866 auto spt = ftGlyph.bitmap.pitch; 9867 if (spt < 0) spt = -spt; 9868 foreach (int y; 0..ftGlyph.bitmap.rows) { 9869 import core.stdc.string : memcpy; 9870 //dst[0..ftGlyph.bitmap.width] = src[0..ftGlyph.bitmap.width]; 9871 memcpy(dst, src, ftGlyph.bitmap.width); 9872 src += spt; 9873 dst += outStride; 9874 } 9875 } 9876 } 9877 9878 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc { 9879 FT_Vector ftKerning; 9880 version(none) { 9881 // fitted kerning 9882 FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning); 9883 //{ import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d\n", glyph1, glyph2, ftKerning.x, ftKerning.y); } 9884 return cast(int)ftKerning.x; // round up and convert to integer 9885 } else { 9886 // unfitted kerning 9887 //FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNFITTED, &ftKerning); 9888 if (glyph1 <= 0 || glyph2 <= 0 || (font.font.face_flags&FT_FACE_FLAG_KERNING) == 0) return 0; 9889 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; 9890 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning)) return 0; 9891 version(none) { 9892 if (ftKerning.x) { 9893 //{ import core.stdc.stdio : printf; printf("has kerning: %u\n", cast(uint)(font.font.face_flags&FT_FACE_FLAG_KERNING)); } 9894 { import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d (size=%g)\n", glyph1, glyph2, ftKerning.x, ftKerning.y, cast(double)size); } 9895 } 9896 } 9897 version(none) { 9898 FT_Vector kk; 9899 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNSCALED, &kk)) assert(0, "wtf?!"); 9900 auto kadvfrac = FT_MulFix(kk.x, font.font.size.metrics.x_scale); // 1/64 of pixel 9901 //return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6); 9902 //assert(ftKerning.x == kadvfrac); 9903 if (ftKerning.x || kadvfrac) { 9904 { 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); } 9905 } 9906 //return cast(int)(kadvfrac+(kadvfrac < 0 ? -31 : 32)>>6); // round up and convert to integer 9907 return kadvfrac/64.0f; 9908 } 9909 //return cast(int)(ftKerning.x+(ftKerning.x < 0 ? -31 : 32)>>6); // round up and convert to integer 9910 return ftKerning.x/64.0f; 9911 } 9912 } 9913 9914 extern(C) nothrow @trusted @nogc { 9915 static struct OutlinerData { 9916 @disable this (this); 9917 void opAssign() (const scope auto ref OutlinerData a) { static assert(0, "no copies!"); } 9918 NVGContext vg; 9919 NVGPathOutline.DataStore* ol; 9920 FT_BBox outlineBBox; 9921 nothrow @trusted @nogc: 9922 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; } 9923 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 9924 } 9925 9926 int fons__nvg__moveto_cb (const(FT_Vector)* to, void* user) { 9927 auto odata = cast(OutlinerData*)user; 9928 if (odata.vg !is null) odata.vg.moveTo(odata.transx(to.x), odata.transy(to.y)); 9929 if (odata.ol !is null) { 9930 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo); 9931 odata.ol.putArgs(odata.transx(to.x)); 9932 odata.ol.putArgs(odata.transy(to.y)); 9933 } 9934 return 0; 9935 } 9936 9937 int fons__nvg__lineto_cb (const(FT_Vector)* to, void* user) { 9938 auto odata = cast(OutlinerData*)user; 9939 if (odata.vg !is null) odata.vg.lineTo(odata.transx(to.x), odata.transy(to.y)); 9940 if (odata.ol !is null) { 9941 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo); 9942 odata.ol.putArgs(odata.transx(to.x)); 9943 odata.ol.putArgs(odata.transy(to.y)); 9944 } 9945 return 0; 9946 } 9947 9948 int fons__nvg__quadto_cb (const(FT_Vector)* c1, const(FT_Vector)* to, void* user) { 9949 auto odata = cast(OutlinerData*)user; 9950 if (odata.vg !is null) odata.vg.quadTo(odata.transx(c1.x), odata.transy(c1.y), odata.transx(to.x), odata.transy(to.y)); 9951 if (odata.ol !is null) { 9952 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo); 9953 odata.ol.putArgs(odata.transx(c1.x)); 9954 odata.ol.putArgs(odata.transy(c1.y)); 9955 odata.ol.putArgs(odata.transx(to.x)); 9956 odata.ol.putArgs(odata.transy(to.y)); 9957 } 9958 return 0; 9959 } 9960 9961 int fons__nvg__cubicto_cb (const(FT_Vector)* c1, const(FT_Vector)* c2, const(FT_Vector)* to, void* user) { 9962 auto odata = cast(OutlinerData*)user; 9963 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)); 9964 if (odata.ol !is null) { 9965 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo); 9966 odata.ol.putArgs(odata.transx(c1.x)); 9967 odata.ol.putArgs(odata.transy(c1.y)); 9968 odata.ol.putArgs(odata.transx(c2.x)); 9969 odata.ol.putArgs(odata.transy(c2.y)); 9970 odata.ol.putArgs(odata.transx(to.x)); 9971 odata.ol.putArgs(odata.transy(to.y)); 9972 } 9973 return 0; 9974 } 9975 } 9976 9977 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc { 9978 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 9979 9980 FT_Outline_Funcs funcs; 9981 funcs.move_to = &fons__nvg__moveto_cb; 9982 funcs.line_to = &fons__nvg__lineto_cb; 9983 funcs.conic_to = &fons__nvg__quadto_cb; 9984 funcs.cubic_to = &fons__nvg__cubicto_cb; 9985 9986 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 9987 if (err) { bounds[] = 0; return false; } 9988 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; } 9989 9990 FT_Outline outline = font.font.glyph.outline; 9991 9992 OutlinerData odata; 9993 odata.vg = vg; 9994 FT_Outline_Get_CBox(&outline, &odata.outlineBBox); 9995 9996 err = FT_Outline_Decompose(&outline, &funcs, &odata); 9997 if (err) { bounds[] = 0; return false; } 9998 if (bounds.length > 0) bounds.ptr[0] = odata.outlineBBox.xMin; 9999 if (bounds.length > 1) bounds.ptr[1] = -odata.outlineBBox.yMax; 10000 if (bounds.length > 2) bounds.ptr[2] = odata.outlineBBox.xMax; 10001 if (bounds.length > 3) bounds.ptr[3] = -odata.outlineBBox.yMin; 10002 return true; 10003 } 10004 10005 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 10006 FT_Outline_Funcs funcs; 10007 funcs.move_to = &fons__nvg__moveto_cb; 10008 funcs.line_to = &fons__nvg__lineto_cb; 10009 funcs.conic_to = &fons__nvg__quadto_cb; 10010 funcs.cubic_to = &fons__nvg__cubicto_cb; 10011 10012 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 10013 if (err) return false; 10014 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) return false; 10015 10016 FT_Outline outline = font.font.glyph.outline; 10017 10018 OutlinerData odata; 10019 odata.ol = ol; 10020 FT_Outline_Get_CBox(&outline, &odata.outlineBBox); 10021 10022 err = FT_Outline_Decompose(&outline, &funcs, &odata); 10023 if (err) return false; 10024 ol.bounds.ptr[0] = odata.outlineBBox.xMin; 10025 ol.bounds.ptr[1] = -odata.outlineBBox.yMax; 10026 ol.bounds.ptr[2] = odata.outlineBBox.xMax; 10027 ol.bounds.ptr[3] = -odata.outlineBBox.yMin; 10028 return true; 10029 } 10030 10031 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc { 10032 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10033 10034 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 10035 if (err) return false; 10036 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; } 10037 10038 FT_Outline outline = font.font.glyph.outline; 10039 FT_BBox outlineBBox; 10040 FT_Outline_Get_CBox(&outline, &outlineBBox); 10041 if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin; 10042 if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax; 10043 if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax; 10044 if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin; 10045 return true; 10046 } 10047 10048 10049 } else { 10050 // ////////////////////////////////////////////////////////////////////////// // 10051 // sorry 10052 import std.traits : isFunctionPointer, isDelegate; 10053 private auto assumeNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) { 10054 import std.traits; 10055 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_; 10056 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 10057 } 10058 10059 private auto forceNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) { 10060 try { 10061 return assumeNoThrowNoGC(t)(); 10062 } catch (Exception e) { 10063 assert(0, "OOPS!"); 10064 } 10065 } 10066 10067 struct FONSttFontImpl { 10068 stbtt_fontinfo font; 10069 bool mono; // no aa? 10070 } 10071 10072 int fons__tt_init (FONSContext context) nothrow @trusted @nogc { 10073 return 1; 10074 } 10075 10076 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc { 10077 font.mono = v; 10078 } 10079 10080 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc { 10081 return font.mono; 10082 } 10083 10084 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc { 10085 int stbError; 10086 font.font.userdata = context; 10087 forceNoThrowNoGC({ stbError = stbtt_InitFont(&font.font, data, 0); }); 10088 return stbError; 10089 } 10090 10091 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc { 10092 forceNoThrowNoGC({ stbtt_GetFontVMetrics(&font.font, ascent, descent, lineGap); }); 10093 } 10094 10095 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc { 10096 float res = void; 10097 forceNoThrowNoGC({ res = stbtt_ScaleForPixelHeight(&font.font, size); }); 10098 return res; 10099 } 10100 10101 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc { 10102 int res; 10103 forceNoThrowNoGC({ res = stbtt_FindGlyphIndex(&font.font, codepoint); }); 10104 return res; 10105 } 10106 10107 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 { 10108 forceNoThrowNoGC({ stbtt_GetGlyphHMetrics(&font.font, glyph, advance, lsb); }); 10109 forceNoThrowNoGC({ stbtt_GetGlyphBitmapBox(&font.font, glyph, scale, scale, x0, y0, x1, y1); }); 10110 return 1; 10111 } 10112 10113 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc { 10114 forceNoThrowNoGC({ stbtt_MakeGlyphBitmap(&font.font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); }); 10115 } 10116 10117 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc { 10118 // FUnits -> pixels: pointSize * resolution / (72 points per inch * units_per_em) 10119 // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#converting 10120 float res = void; 10121 forceNoThrowNoGC({ 10122 res = stbtt_GetGlyphKernAdvance(&font.font, glyph1, glyph2); 10123 res *= stbtt_ScaleForPixelHeight(&font.font, size); 10124 }); 10125 /* 10126 if (res != 0) { 10127 { 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)); } 10128 } 10129 */ 10130 //k8: dunno if this is right; i guess it isn't but... 10131 return res; 10132 } 10133 10134 // old arsd.ttf sux! ;-) 10135 static if (is(typeof(STBTT_vcubic))) { 10136 10137 static struct OutlinerData { 10138 @disable this (this); 10139 void opAssign() (const scope auto ref OutlinerData a) { static assert(0, "no copies!"); } 10140 NVGPathOutline.DataStore* ol; 10141 nothrow @trusted @nogc: 10142 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; } 10143 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 10144 } 10145 10146 10147 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc { 10148 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10149 10150 bool okflag = false; 10151 10152 forceNoThrowNoGC({ 10153 int x0, y0, x1, y1; 10154 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10155 bounds[] = 0; 10156 return; 10157 } 10158 10159 if (bounds.length > 0) bounds.ptr[0] = x0; 10160 if (bounds.length > 1) bounds.ptr[1] = -y1; 10161 if (bounds.length > 2) bounds.ptr[2] = x1; 10162 if (bounds.length > 3) bounds.ptr[3] = -y0; 10163 10164 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 10165 10166 stbtt_vertex* verts = null; 10167 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); } 10168 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts); 10169 if (vcount < 1) return; 10170 10171 foreach (const ref vt; verts[0..vcount]) { 10172 switch (vt.type) { 10173 case STBTT_vmove: vg.moveTo(vt.x, transy(vt.y)); break; 10174 case STBTT_vline: vg.lineTo(vt.x, transy(vt.y)); break; 10175 case STBTT_vcurve: vg.quadTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy)); break; 10176 case STBTT_vcubic: vg.bezierTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy), vt.cx1, transy(vt.cy1)); break; 10177 default: 10178 } 10179 } 10180 10181 okflag = true; 10182 }); 10183 10184 return okflag; 10185 } 10186 10187 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 10188 bool okflag = false; 10189 10190 forceNoThrowNoGC({ 10191 int x0, y0, x1, y1; 10192 10193 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10194 ol.bounds[] = 0; 10195 return; 10196 } 10197 10198 ol.bounds.ptr[0] = x0; 10199 ol.bounds.ptr[1] = -y1; 10200 ol.bounds.ptr[2] = x1; 10201 ol.bounds.ptr[3] = -y0; 10202 10203 stbtt_vertex* verts = null; 10204 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); } 10205 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts); 10206 if (vcount < 1) return; 10207 10208 OutlinerData odata; 10209 odata.ol = ol; 10210 10211 foreach (const ref vt; verts[0..vcount]) { 10212 switch (vt.type) { 10213 case STBTT_vmove: 10214 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo); 10215 odata.ol.putArgs(odata.transx(vt.x)); 10216 odata.ol.putArgs(odata.transy(vt.y)); 10217 break; 10218 case STBTT_vline: 10219 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo); 10220 odata.ol.putArgs(odata.transx(vt.x)); 10221 odata.ol.putArgs(odata.transy(vt.y)); 10222 break; 10223 case STBTT_vcurve: 10224 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo); 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 break; 10230 case STBTT_vcubic: 10231 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo); 10232 odata.ol.putArgs(odata.transx(vt.x)); 10233 odata.ol.putArgs(odata.transy(vt.y)); 10234 odata.ol.putArgs(odata.transx(vt.cx)); 10235 odata.ol.putArgs(odata.transy(vt.cy)); 10236 odata.ol.putArgs(odata.transx(vt.cx1)); 10237 odata.ol.putArgs(odata.transy(vt.cy1)); 10238 break; 10239 default: 10240 } 10241 } 10242 10243 okflag = true; 10244 }); 10245 10246 return okflag; 10247 } 10248 10249 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc { 10250 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10251 10252 bool okflag = false; 10253 10254 forceNoThrowNoGC({ 10255 int x0, y0, x1, y1; 10256 if (stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10257 if (bounds.length > 0) bounds.ptr[0] = x0; 10258 if (bounds.length > 1) bounds.ptr[1] = -y1; 10259 if (bounds.length > 2) bounds.ptr[2] = x1; 10260 if (bounds.length > 3) bounds.ptr[3] = -y0; 10261 okflag = true; 10262 } else { 10263 bounds[] = 0; 10264 } 10265 }); 10266 10267 return okflag; 10268 } 10269 10270 } // check for old stb_ttf 10271 10272 10273 } // version 10274 10275 10276 // ////////////////////////////////////////////////////////////////////////// // 10277 private: 10278 enum FONS_SCRATCH_BUF_SIZE = 64000; 10279 enum FONS_HASH_LUT_SIZE = 256; 10280 enum FONS_INIT_FONTS = 4; 10281 enum FONS_INIT_GLYPHS = 256; 10282 enum FONS_INIT_ATLAS_NODES = 256; 10283 enum FONS_VERTEX_COUNT = 1024; 10284 enum FONS_MAX_STATES = 20; 10285 enum FONS_MAX_FALLBACKS = 20; 10286 10287 10288 struct FONSglyph { 10289 uint codepoint; 10290 int index; 10291 int next; 10292 short size, blur; 10293 short x0, y0, x1, y1; 10294 short xadv, xoff, yoff; 10295 } 10296 10297 // refcounted 10298 struct FONSfontData { 10299 ubyte* data; 10300 int dataSize; 10301 bool freeData; 10302 int rc; 10303 10304 @disable this (this); // no copies 10305 void opAssign() (const scope auto ref FONSfontData a) { static assert(0, "no copies!"); } 10306 } 10307 10308 // won't set rc to 1 10309 FONSfontData* fons__createFontData (ubyte* adata, int asize, bool afree) nothrow @trusted @nogc { 10310 import core.stdc.stdlib : malloc; 10311 assert(adata !is null); 10312 assert(asize > 0); 10313 auto res = cast(FONSfontData*)malloc(FONSfontData.sizeof); 10314 if (res is null) assert(0, "FONS: out of memory"); 10315 res.data = adata; 10316 res.dataSize = asize; 10317 res.freeData = afree; 10318 res.rc = 0; 10319 return res; 10320 } 10321 10322 void incref (FONSfontData* fd) pure nothrow @trusted @nogc { 10323 pragma(inline, true); 10324 if (fd !is null) ++fd.rc; 10325 } 10326 10327 void decref (ref FONSfontData* fd) nothrow @trusted @nogc { 10328 if (fd !is null) { 10329 if (--fd.rc == 0) { 10330 import core.stdc.stdlib : free; 10331 if (fd.freeData && fd.data !is null) { 10332 free(fd.data); 10333 fd.data = null; 10334 } 10335 free(fd); 10336 fd = null; 10337 } 10338 } 10339 } 10340 10341 // as creating and destroying fonts is a rare operation, malloc some data 10342 struct FONSfont { 10343 FONSttFontImpl font; 10344 char* name; // malloced, strz, always lowercase 10345 uint namelen; 10346 uint namehash; 10347 char* path; // malloced, strz 10348 FONSfontData* fdata; 10349 float ascender; 10350 float descender; 10351 float lineh; 10352 FONSglyph* glyphs; 10353 int cglyphs; 10354 int nglyphs; 10355 int[FONS_HASH_LUT_SIZE] lut; 10356 int[FONS_MAX_FALLBACKS] fallbacks; 10357 int nfallbacks; 10358 10359 @disable this (this); 10360 void opAssign() (const scope auto ref FONSfont a) { static assert(0, "no copies"); } 10361 10362 static uint djbhash (const(void)[] s) pure nothrow @safe @nogc { 10363 uint hash = 5381; 10364 foreach (ubyte b; cast(const(ubyte)[])s) { 10365 if (b >= 'A' && b <= 'Z') b += 32; // poor man's tolower 10366 hash = ((hash<<5)+hash)+b; 10367 } 10368 return hash; 10369 } 10370 10371 // except glyphs 10372 void freeMemory () nothrow @trusted @nogc { 10373 import core.stdc.stdlib : free; 10374 if (name !is null) { free(name); name = null; } 10375 namelen = namehash = 0; 10376 if (path !is null) { free(path); path = null; } 10377 fdata.decref(); 10378 } 10379 10380 // this also calcs name hash 10381 void setName (const(char)[] aname) nothrow @trusted @nogc { 10382 //{ import core.stdc.stdio; printf("setname: [%.*s]\n", cast(uint)aname.length, aname.ptr); } 10383 import core.stdc.stdlib : realloc; 10384 if (aname.length > int.max/32) assert(0, "FONS: invalid font name"); 10385 namelen = cast(uint)aname.length; 10386 name = cast(char*)realloc(name, namelen+1); 10387 if (name is null) assert(0, "FONS: out of memory"); 10388 if (aname.length) name[0..aname.length] = aname[]; 10389 name[namelen] = 0; 10390 // lowercase it 10391 foreach (ref char ch; name[0..namelen]) if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower 10392 namehash = djbhash(name[0..namelen]); 10393 //{ import core.stdc.stdio; printf(" [%s] [%.*s] [0x%08x]\n", name, namelen, name, namehash); } 10394 } 10395 10396 void setPath (const(char)[] apath) nothrow @trusted @nogc { 10397 import core.stdc.stdlib : realloc; 10398 if (apath.length > int.max/32) assert(0, "FONS: invalid font path"); 10399 path = cast(char*)realloc(path, apath.length+1); 10400 if (path is null) assert(0, "FONS: out of memory"); 10401 if (apath.length) path[0..apath.length] = apath[]; 10402 path[apath.length] = 0; 10403 } 10404 10405 // this won't check hash 10406 bool nameEqu (const(char)[] aname) const pure nothrow @trusted @nogc { 10407 //{ import core.stdc.stdio; printf("nameEqu: aname=[%.*s]; namelen=%u; aslen=%u\n", cast(uint)aname.length, aname.ptr, namelen, cast(uint)aname.length); } 10408 if (namelen != aname.length) return false; 10409 const(char)* ns = name; 10410 // name part 10411 foreach (char ch; aname) { 10412 if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower 10413 if (ch != *ns++) return false; 10414 } 10415 // done (length was checked earlier) 10416 return true; 10417 } 10418 10419 void clear () nothrow @trusted @nogc { 10420 import core.stdc.stdlib : free; 10421 import core.stdc.string : memset; 10422 if (glyphs !is null) free(glyphs); 10423 freeMemory(); 10424 memset(&this, 0, this.sizeof); 10425 } 10426 10427 FONSglyph* allocGlyph () nothrow @trusted @nogc { 10428 if (nglyphs+1 > cglyphs) { 10429 import core.stdc.stdlib : realloc; 10430 cglyphs = (cglyphs == 0 ? 8 : cglyphs*2); 10431 glyphs = cast(FONSglyph*)realloc(glyphs, FONSglyph.sizeof*cglyphs); 10432 if (glyphs is null) assert(0, "FontStash: out of memory"); 10433 } 10434 ++nglyphs; 10435 return &glyphs[nglyphs-1]; 10436 } 10437 } 10438 10439 void kill (ref FONSfont* font) nothrow @trusted @nogc { 10440 if (font !is null) { 10441 import core.stdc.stdlib : free; 10442 font.clear(); 10443 free(font); 10444 font = null; 10445 } 10446 } 10447 10448 10449 // ////////////////////////////////////////////////////////////////////////// // 10450 struct FONSstate { 10451 int font; 10452 NVGTextAlign talign; 10453 float size = 0; 10454 float blur = 0; 10455 float spacing = 0; 10456 } 10457 10458 10459 // ////////////////////////////////////////////////////////////////////////// // 10460 // atlas based on Skyline Bin Packer by Jukka Jylänki 10461 alias FONSAtlas = FONSatlasInternal*; 10462 10463 struct FONSatlasInternal { 10464 static struct Node { 10465 short x, y, width; 10466 } 10467 10468 int width, height; 10469 Node* nodes; 10470 int nnodes; 10471 int cnodes; 10472 10473 @disable this (this); 10474 void opAssign() (const scope auto ref FONSatlasInternal a) { static assert(0, "no copies"); } 10475 10476 nothrow @trusted @nogc: 10477 static FONSAtlas create (int w, int h, int nnodes) { 10478 import core.stdc.stdlib : malloc; 10479 import core.stdc.string : memset; 10480 10481 FONSAtlas atlas = cast(FONSAtlas)malloc(FONSatlasInternal.sizeof); 10482 if (atlas is null) assert(0, "FontStash: out of memory"); 10483 memset(atlas, 0, FONSatlasInternal.sizeof); 10484 10485 atlas.width = w; 10486 atlas.height = h; 10487 10488 // allocate space for skyline nodes 10489 atlas.nodes = cast(Node*)malloc(Node.sizeof*nnodes); 10490 if (atlas.nodes is null) assert(0, "FontStash: out of memory"); 10491 memset(atlas.nodes, 0, Node.sizeof*nnodes); 10492 atlas.nnodes = 0; 10493 atlas.cnodes = nnodes; 10494 10495 // init root node 10496 atlas.nodes[0].x = 0; 10497 atlas.nodes[0].y = 0; 10498 atlas.nodes[0].width = cast(short)w; 10499 ++atlas.nnodes; 10500 10501 return atlas; 10502 } 10503 10504 void clear () { 10505 import core.stdc.stdlib : free; 10506 import core.stdc.string : memset; 10507 10508 if (nodes !is null) free(nodes); 10509 memset(&this, 0, this.sizeof); 10510 } 10511 10512 void insertNode (int idx, int x, int y, int w) { 10513 if (nnodes+1 > cnodes) { 10514 import core.stdc.stdlib : realloc; 10515 cnodes = (cnodes == 0 ? 8 : cnodes*2); 10516 nodes = cast(Node*)realloc(nodes, Node.sizeof*cnodes); 10517 if (nodes is null) assert(0, "FontStash: out of memory"); 10518 } 10519 for (int i = nnodes; i > idx; --i) nodes[i] = nodes[i-1]; 10520 nodes[idx].x = cast(short)x; 10521 nodes[idx].y = cast(short)y; 10522 nodes[idx].width = cast(short)w; 10523 ++nnodes; 10524 } 10525 10526 void removeNode (int idx) { 10527 if (nnodes == 0) return; 10528 foreach (immutable int i; idx+1..nnodes) nodes[i-1] = nodes[i]; 10529 --nnodes; 10530 } 10531 10532 // insert node for empty space 10533 void expand (int w, int h) { 10534 if (w > width) insertNode(nnodes, width, 0, w-width); 10535 width = w; 10536 height = h; 10537 } 10538 10539 void reset (int w, int h) { 10540 width = w; 10541 height = h; 10542 nnodes = 0; 10543 // init root node 10544 nodes[0].x = 0; 10545 nodes[0].y = 0; 10546 nodes[0].width = cast(short)w; 10547 ++nnodes; 10548 } 10549 10550 void addSkylineLevel (int idx, int x, int y, int w, int h) { 10551 insertNode(idx, x, y+h, w); 10552 10553 // delete skyline segments that fall under the shadow of the new segment 10554 for (int i = idx+1; i < nnodes; ++i) { 10555 if (nodes[i].x < nodes[i-1].x+nodes[i-1].width) { 10556 int shrink = nodes[i-1].x+nodes[i-1].width-nodes[i].x; 10557 nodes[i].x += cast(short)shrink; 10558 nodes[i].width -= cast(short)shrink; 10559 if (nodes[i].width <= 0) { 10560 removeNode(i); 10561 --i; 10562 } else { 10563 break; 10564 } 10565 } else { 10566 break; 10567 } 10568 } 10569 10570 // Merge same height skyline segments that are next to each other 10571 for (int i = 0; i < nnodes-1; ++i) { 10572 if (nodes[i].y == nodes[i+1].y) { 10573 nodes[i].width += nodes[i+1].width; 10574 removeNode(i+1); 10575 --i; 10576 } 10577 } 10578 } 10579 10580 // checks if there is enough space at the location of skyline span 'i', 10581 // and return the max height of all skyline spans under that at that location, 10582 // (think tetris block being dropped at that position); or -1 if no space found 10583 int rectFits (int i, int w, int h) { 10584 int x = nodes[i].x; 10585 int y = nodes[i].y; 10586 if (x+w > width) return -1; 10587 int spaceLeft = w; 10588 while (spaceLeft > 0) { 10589 if (i == nnodes) return -1; 10590 y = nvg__max(y, nodes[i].y); 10591 if (y+h > height) return -1; 10592 spaceLeft -= nodes[i].width; 10593 ++i; 10594 } 10595 return y; 10596 } 10597 10598 bool addRect (int rw, int rh, int* rx, int* ry) { 10599 int besth = height, bestw = width, besti = -1; 10600 int bestx = -1, besty = -1; 10601 10602 // Bottom left fit heuristic. 10603 for (int i = 0; i < nnodes; ++i) { 10604 int y = rectFits(i, rw, rh); 10605 if (y != -1) { 10606 if (y+rh < besth || (y+rh == besth && nodes[i].width < bestw)) { 10607 besti = i; 10608 bestw = nodes[i].width; 10609 besth = y+rh; 10610 bestx = nodes[i].x; 10611 besty = y; 10612 } 10613 } 10614 } 10615 10616 if (besti == -1) return false; 10617 10618 // perform the actual packing 10619 addSkylineLevel(besti, bestx, besty, rw, rh); 10620 10621 *rx = bestx; 10622 *ry = besty; 10623 10624 return true; 10625 } 10626 } 10627 10628 void kill (ref FONSAtlas atlas) nothrow @trusted @nogc { 10629 if (atlas !is null) { 10630 import core.stdc.stdlib : free; 10631 atlas.clear(); 10632 free(atlas); 10633 atlas = null; 10634 } 10635 } 10636 10637 10638 // ////////////////////////////////////////////////////////////////////////// // 10639 /// FontStash context (internal definition). Don't use it derectly, it was made public only to generate documentation. 10640 /// Group: font_stash 10641 public struct FONScontextInternal { 10642 private: 10643 FONSParams params; 10644 float itw, ith; 10645 ubyte* texData; 10646 int[4] dirtyRect; 10647 FONSfont** fonts; // actually, a simple hash table; can't grow yet 10648 int cfonts; // allocated 10649 int nfonts; // used (so we can track hash table stats) 10650 int* hashidx; // [hsize] items; holds indicies in [fonts] array 10651 int hused, hsize;// used items and total items in [hashidx] 10652 FONSAtlas atlas; 10653 ubyte* scratch; 10654 int nscratch; 10655 FONSstate[FONS_MAX_STATES] states; 10656 int nstates; 10657 10658 void delegate (FONSError error, int val) nothrow @trusted @nogc handleError; 10659 10660 @disable this (this); 10661 void opAssign() (const scope auto ref FONScontextInternal ctx) { static assert(0, "FONS copying is not allowed"); } 10662 10663 private: 10664 static bool strequci (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 10665 if (s0.length != s1.length) return false; 10666 const(char)* sp0 = s0.ptr; 10667 const(char)* sp1 = s1.ptr; 10668 foreach (immutable _; 0..s0.length) { 10669 char c0 = *sp0++; 10670 char c1 = *sp1++; 10671 if (c0 != c1) { 10672 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man tolower 10673 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man tolower 10674 if (c0 != c1) return false; 10675 } 10676 } 10677 return true; 10678 } 10679 10680 inout(FONSstate)* getState () inout pure nothrow @trusted @nogc return { 10681 pragma(inline, true); 10682 return cast(inout)(&states[(nstates > 0 ? nstates-1 : 0)]); 10683 } 10684 10685 // simple linear probing; returns [FONS_INVALID] if not found 10686 int findNameInHash (const(char)[] name) const pure nothrow @trusted @nogc { 10687 if (nfonts == 0) return FONS_INVALID; 10688 auto nhash = FONSfont.djbhash(name); 10689 //{ import core.stdc.stdio; printf("findinhash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10690 auto res = nhash%hsize; 10691 // hash will never be 100% full, so this loop is safe 10692 for (;;) { 10693 int idx = hashidx[res]; 10694 if (idx == -1) break; 10695 auto font = fonts[idx]; 10696 if (font is null) assert(0, "FONS internal error"); 10697 if (font.namehash == nhash && font.nameEqu(name)) return idx; 10698 //{ import core.stdc.stdio; printf("findinhash chained: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10699 res = (res+1)%hsize; 10700 } 10701 return FONS_INVALID; 10702 } 10703 10704 // should be called $(B before) freeing `fonts[fidx]` 10705 void removeIndexFromHash (int fidx) nothrow @trusted @nogc { 10706 if (fidx < 0 || fidx >= nfonts) assert(0, "FONS internal error"); 10707 if (fonts[fidx] is null) assert(0, "FONS internal error"); 10708 if (hused != nfonts) assert(0, "FONS internal error"); 10709 auto nhash = fonts[fidx].namehash; 10710 auto res = nhash%hsize; 10711 // hash will never be 100% full, so this loop is safe 10712 for (;;) { 10713 int idx = hashidx[res]; 10714 if (idx == -1) assert(0, "FONS INTERNAL ERROR"); 10715 if (idx == fidx) { 10716 // i found her! copy rest here 10717 int nidx = (res+1)%hsize; 10718 for (;;) { 10719 if ((hashidx[res] = hashidx[nidx]) == -1) break; // so it will copy `-1` too 10720 res = nidx; 10721 nidx = (nidx+1)%hsize; 10722 } 10723 return; 10724 } 10725 res = (res+1)%hsize; 10726 } 10727 } 10728 10729 // add font with the given index to hash 10730 // prerequisite: font should not exists in hash 10731 void addIndexToHash (int idx) nothrow @trusted @nogc { 10732 if (idx < 0 || idx >= nfonts) assert(0, "FONS internal error"); 10733 if (fonts[idx] is null) assert(0, "FONS internal error"); 10734 import core.stdc.stdlib : realloc; 10735 auto nhash = fonts[idx].namehash; 10736 //{ import core.stdc.stdio; printf("addtohash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10737 // allocate new hash table if there was none 10738 if (hsize == 0) { 10739 enum InitSize = 256; 10740 auto newlist = cast(int*)realloc(null, InitSize*hashidx[0].sizeof); 10741 if (newlist is null) assert(0, "FONS: out of memory"); 10742 newlist[0..InitSize] = -1; 10743 hsize = InitSize; 10744 hused = 0; 10745 hashidx = newlist; 10746 } 10747 int res = cast(int)(nhash%hsize); 10748 // need to rehash? we want our hash table 50% full at max 10749 if (hashidx[res] != -1 && hused >= hsize/2) { 10750 uint nsz = hsize*2; 10751 if (nsz > 1024*1024) assert(0, "FONS: out of memory for fonts"); 10752 auto newlist = cast(int*)realloc(fonts, nsz*hashidx[0].sizeof); 10753 if (newlist is null) assert(0, "FONS: out of memory"); 10754 newlist[0..nsz] = -1; 10755 hused = 0; 10756 // rehash 10757 foreach (immutable fidx, FONSfont* ff; fonts[0..nfonts]) { 10758 if (ff is null) continue; 10759 // find slot for this font (guaranteed to have one) 10760 uint newslot = ff.namehash%nsz; 10761 while (newlist[newslot] != -1) newslot = (newslot+1)%nsz; 10762 newlist[newslot] = cast(int)fidx; 10763 ++hused; 10764 } 10765 hsize = nsz; 10766 hashidx = newlist; 10767 // we added everything, including [idx], so nothing more to do here 10768 } else { 10769 // find slot (guaranteed to have one) 10770 while (hashidx[res] != -1) res = (res+1)%hsize; 10771 // i found her! 10772 hashidx[res] = idx; 10773 ++hused; 10774 } 10775 } 10776 10777 void addWhiteRect (int w, int h) nothrow @trusted @nogc { 10778 int gx, gy; 10779 ubyte* dst; 10780 10781 if (!atlas.addRect(w, h, &gx, &gy)) return; 10782 10783 // Rasterize 10784 dst = &texData[gx+gy*params.width]; 10785 foreach (int y; 0..h) { 10786 foreach (int x; 0..w) { 10787 dst[x] = 0xff; 10788 } 10789 dst += params.width; 10790 } 10791 10792 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], gx); 10793 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], gy); 10794 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], gx+w); 10795 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], gy+h); 10796 } 10797 10798 // returns fid, not hash slot 10799 int allocFontAt (int atidx) nothrow @trusted @nogc { 10800 if (atidx >= 0 && atidx >= nfonts) assert(0, "internal NanoVega fontstash error"); 10801 10802 if (atidx < 0) { 10803 if (nfonts >= cfonts) { 10804 import core.stdc.stdlib : realloc; 10805 import core.stdc.string : memset; 10806 assert(nfonts == cfonts); 10807 int newsz = cfonts+64; 10808 if (newsz > 65535) assert(0, "FONS: too many fonts"); 10809 auto newlist = cast(FONSfont**)realloc(fonts, newsz*(FONSfont*).sizeof); 10810 if (newlist is null) assert(0, "FONS: out of memory"); 10811 memset(newlist+cfonts, 0, (newsz-cfonts)*(FONSfont*).sizeof); 10812 fonts = newlist; 10813 cfonts = newsz; 10814 } 10815 assert(nfonts < cfonts); 10816 } 10817 10818 FONSfont* font = cast(FONSfont*)malloc(FONSfont.sizeof); 10819 if (font is null) assert(0, "FONS: out of memory"); 10820 memset(font, 0, FONSfont.sizeof); 10821 10822 font.glyphs = cast(FONSglyph*)malloc(FONSglyph.sizeof*FONS_INIT_GLYPHS); 10823 if (font.glyphs is null) assert(0, "FONS: out of memory"); 10824 font.cglyphs = FONS_INIT_GLYPHS; 10825 font.nglyphs = 0; 10826 10827 if (atidx < 0) { 10828 fonts[nfonts] = font; 10829 return nfonts++; 10830 } else { 10831 fonts[atidx] = font; 10832 return atidx; 10833 } 10834 } 10835 10836 // 0: ooops 10837 int findGlyphForCP (FONSfont *font, dchar dch, FONSfont** renderfont) nothrow @trusted @nogc { 10838 if (renderfont !is null) *renderfont = font; 10839 if (font is null || font.fdata is null) return 0; 10840 auto g = fons__tt_getGlyphIndex(&font.font, cast(uint)dch); 10841 // try to find the glyph in fallback fonts 10842 if (g == 0) { 10843 foreach (immutable i; 0..font.nfallbacks) { 10844 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]]; 10845 if (fallbackFont !is null) { 10846 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, cast(uint)dch); 10847 if (fallbackIndex != 0) { 10848 if (renderfont !is null) *renderfont = fallbackFont; 10849 return g; 10850 } 10851 } 10852 } 10853 // no char, try to find replacement one 10854 if (dch != 0xFFFD) { 10855 g = fons__tt_getGlyphIndex(&font.font, 0xFFFD); 10856 if (g == 0) { 10857 foreach (immutable i; 0..font.nfallbacks) { 10858 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]]; 10859 if (fallbackFont !is null) { 10860 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, 0xFFFD); 10861 if (fallbackIndex != 0) { 10862 if (renderfont !is null) *renderfont = fallbackFont; 10863 return g; 10864 } 10865 } 10866 } 10867 } 10868 } 10869 } 10870 return g; 10871 } 10872 10873 void clear () nothrow @trusted @nogc { 10874 import core.stdc.stdlib : free; 10875 10876 if (params.renderDelete !is null) params.renderDelete(params.userPtr); 10877 foreach (immutable int i; 0..nfonts) fonts[i].kill(); 10878 10879 if (atlas !is null) atlas.kill(); 10880 if (fonts !is null) free(fonts); 10881 if (texData !is null) free(texData); 10882 if (scratch !is null) free(scratch); 10883 if (hashidx !is null) free(hashidx); 10884 } 10885 10886 // add font from another fontstash 10887 int addCookedFont (FONSfont* font) nothrow @trusted @nogc { 10888 if (font is null || font.fdata is null) return FONS_INVALID; 10889 font.fdata.incref(); 10890 auto res = addFontWithData(font.name[0..font.namelen], font.fdata, !font.font.mono); 10891 if (res == FONS_INVALID) font.fdata.decref(); // oops 10892 return res; 10893 } 10894 10895 // fdata refcount must be already increased; it won't be changed 10896 int addFontWithData (const(char)[] name, FONSfontData* fdata, bool defAA) nothrow @trusted @nogc { 10897 int i, ascent, descent, fh, lineGap; 10898 10899 if (name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID; 10900 if (name.length > 32767) return FONS_INVALID; 10901 if (fdata is null) return FONS_INVALID; 10902 10903 // find a font with the given name 10904 int newidx; 10905 FONSfont* oldfont = null; 10906 int oldidx = findNameInHash(name); 10907 if (oldidx != FONS_INVALID) { 10908 // replacement font 10909 oldfont = fonts[oldidx]; 10910 newidx = oldidx; 10911 } else { 10912 // new font, allocate new bucket 10913 newidx = -1; 10914 } 10915 10916 newidx = allocFontAt(newidx); 10917 FONSfont* font = fonts[newidx]; 10918 font.setName(name); 10919 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; // init hash lookup 10920 font.fdata = fdata; // set the font data (don't change reference count) 10921 fons__tt_setMono(&this, &font.font, !defAA); 10922 10923 // init font 10924 nscratch = 0; 10925 if (!fons__tt_loadFont(&this, &font.font, fdata.data, fdata.dataSize)) { 10926 // we promised to not free data on error, so just clear the data store (it will be freed by the caller) 10927 font.fdata = null; 10928 font.kill(); 10929 if (oldidx != FONS_INVALID) { 10930 assert(oldidx == newidx); 10931 fonts[oldidx] = oldfont; 10932 } else { 10933 assert(newidx == nfonts-1); 10934 fonts[newidx] = null; 10935 --nfonts; 10936 } 10937 return FONS_INVALID; 10938 } else { 10939 // free old font data, if any 10940 if (oldfont !is null) oldfont.kill(); 10941 } 10942 10943 // add font to name hash 10944 if (oldidx == FONS_INVALID) addIndexToHash(newidx); 10945 10946 // store normalized line height 10947 // the real line height is got by multiplying the lineh by font size 10948 fons__tt_getFontVMetrics(&font.font, &ascent, &descent, &lineGap); 10949 fh = ascent-descent; 10950 font.ascender = cast(float)ascent/cast(float)fh; 10951 font.descender = cast(float)descent/cast(float)fh; 10952 font.lineh = cast(float)(fh+lineGap)/cast(float)fh; 10953 10954 //{ import core.stdc.stdio; printf("created font [%.*s] (idx=%d)...\n", cast(uint)name.length, name.ptr, idx); } 10955 return newidx; 10956 } 10957 10958 // isize: size*10 10959 float getVertAlign (FONSfont* font, NVGTextAlign talign, short isize) pure nothrow @trusted @nogc { 10960 if (params.isZeroTopLeft) { 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 } else { 10968 final switch (talign.vertical) { 10969 case NVGTextAlign.V.Top: return -font.ascender*cast(float)isize/10.0f; 10970 case NVGTextAlign.V.Middle: return -(font.ascender+font.descender)/2.0f*cast(float)isize/10.0f; 10971 case NVGTextAlign.V.Baseline: return 0.0f; 10972 case NVGTextAlign.V.Bottom: return -font.descender*cast(float)isize/10.0f; 10973 } 10974 } 10975 assert(0); 10976 } 10977 10978 public: 10979 /** Create new FontStash context. It can be destroyed with `fs.kill()` later. 10980 * 10981 * Note that if you don't plan to rasterize glyphs (i.e. you will use created 10982 * FontStash only to measure text), you can simply pass `FONSParams.init`). 10983 */ 10984 static FONSContext create() (const scope auto ref FONSParams params) nothrow @trusted @nogc { 10985 import core.stdc.string : memcpy; 10986 10987 FONSContext stash = null; 10988 10989 // allocate memory for the font stash 10990 stash = cast(FONSContext)malloc(FONScontextInternal.sizeof); 10991 if (stash is null) goto error; 10992 memset(stash, 0, FONScontextInternal.sizeof); 10993 10994 memcpy(&stash.params, ¶ms, params.sizeof); 10995 if (stash.params.width < 1) stash.params.width = 32; 10996 if (stash.params.height < 1) stash.params.height = 32; 10997 10998 // allocate scratch buffer 10999 stash.scratch = cast(ubyte*)malloc(FONS_SCRATCH_BUF_SIZE); 11000 if (stash.scratch is null) goto error; 11001 11002 // initialize implementation library 11003 if (!fons__tt_init(stash)) goto error; 11004 11005 if (stash.params.renderCreate !is null) { 11006 if (!stash.params.renderCreate(stash.params.userPtr, stash.params.width, stash.params.height)) goto error; 11007 } 11008 11009 stash.atlas = FONSAtlas.create(stash.params.width, stash.params.height, FONS_INIT_ATLAS_NODES); 11010 if (stash.atlas is null) goto error; 11011 11012 // don't allocate space for fonts: hash manager will do that for us later 11013 //stash.cfonts = 0; 11014 //stash.nfonts = 0; 11015 11016 // create texture for the cache 11017 stash.itw = 1.0f/stash.params.width; 11018 stash.ith = 1.0f/stash.params.height; 11019 stash.texData = cast(ubyte*)malloc(stash.params.width*stash.params.height); 11020 if (stash.texData is null) goto error; 11021 memset(stash.texData, 0, stash.params.width*stash.params.height); 11022 11023 stash.dirtyRect.ptr[0] = stash.params.width; 11024 stash.dirtyRect.ptr[1] = stash.params.height; 11025 stash.dirtyRect.ptr[2] = 0; 11026 stash.dirtyRect.ptr[3] = 0; 11027 11028 // add white rect at 0, 0 for debug drawing 11029 stash.addWhiteRect(2, 2); 11030 11031 stash.pushState(); 11032 stash.clearState(); 11033 11034 return stash; 11035 11036 error: 11037 stash.kill(); 11038 return null; 11039 } 11040 11041 public: 11042 /// Add fallback font (FontStash will try to find missing glyph in all fallback fonts before giving up). 11043 bool addFallbackFont (int base, int fallback) nothrow @trusted @nogc { 11044 FONSfont* baseFont = fonts[base]; 11045 if (baseFont !is null && baseFont.nfallbacks < FONS_MAX_FALLBACKS) { 11046 baseFont.fallbacks.ptr[baseFont.nfallbacks++] = fallback; 11047 return true; 11048 } 11049 return false; 11050 } 11051 11052 @property void size (float size) nothrow @trusted @nogc { pragma(inline, true); getState.size = size; } /// Set current font size. 11053 @property float size () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.size; } /// Get current font size. 11054 11055 @property void spacing (float spacing) nothrow @trusted @nogc { pragma(inline, true); getState.spacing = spacing; } /// Set current letter spacing. 11056 @property float spacing () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.spacing; } /// Get current letter spacing. 11057 11058 @property void blur (float blur) nothrow @trusted @nogc { pragma(inline, true); getState.blur = blur; } /// Set current letter blur. 11059 @property float blur () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.blur; } /// Get current letter blur. 11060 11061 @property void textAlign (NVGTextAlign talign) nothrow @trusted @nogc { pragma(inline, true); getState.talign = talign; } /// Set current text align. 11062 @property NVGTextAlign textAlign () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.talign; } /// Get current text align. 11063 11064 @property void fontId (int font) nothrow @trusted @nogc { pragma(inline, true); getState.font = font; } /// Set current font id. 11065 @property int fontId () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.font; } /// Get current font id. 11066 11067 @property void fontId (const(char)[] name) nothrow @trusted @nogc { pragma(inline, true); getState.font = getFontByName(name); } /// Set current font using its name. 11068 11069 /// Check if FontStash has a font with the given name loaded. 11070 bool hasFont (const(char)[] name) const pure nothrow @trusted @nogc { pragma(inline, true); return (getFontByName(name) >= 0); } 11071 11072 /// Get AA for the current font, or for the specified font. 11073 bool getFontAA (int font=-1) nothrow @trusted @nogc { 11074 FONSstate* state = getState; 11075 if (font < 0) font = state.font; 11076 if (font < 0 || font >= nfonts) return false; 11077 FONSfont* f = fonts[font]; 11078 return (f !is null ? !f.font.mono : false); 11079 } 11080 11081 /// Push current state. Returns `false` if state stack overflowed. 11082 bool pushState () nothrow @trusted @nogc { 11083 if (nstates >= FONS_MAX_STATES) { 11084 if (handleError !is null) handleError(FONSError.StatesOverflow, 0); 11085 return false; 11086 } 11087 if (nstates > 0) { 11088 import core.stdc.string : memcpy; 11089 memcpy(&states[nstates], &states[nstates-1], FONSstate.sizeof); 11090 } 11091 ++nstates; 11092 return true; 11093 } 11094 11095 /// Pop current state. Returns `false` if state stack underflowed. 11096 bool popState () nothrow @trusted @nogc { 11097 if (nstates <= 1) { 11098 if (handleError !is null) handleError(FONSError.StatesUnderflow, 0); 11099 return false; 11100 } 11101 --nstates; 11102 return true; 11103 } 11104 11105 /// Clear current state (i.e. set it to some sane defaults). 11106 void clearState () nothrow @trusted @nogc { 11107 FONSstate* state = getState; 11108 state.size = 12.0f; 11109 state.font = 0; 11110 state.blur = 0; 11111 state.spacing = 0; 11112 state.talign.reset; 11113 } 11114 11115 private enum NoAlias = ":noaa"; 11116 11117 /** Add font to FontStash. 11118 * 11119 * Load scalable font from disk, and add it to FontStash. If you will try to load a font 11120 * with same name and path several times, FontStash will load it only once. Also, you can 11121 * load new disk font for any existing logical font. 11122 * 11123 * Params: 11124 * name = logical font name, that will be used to select this font later. 11125 * path = path to disk file with your font. 11126 * defAA = should FontStash use antialiased font rasterizer? 11127 * 11128 * Returns: 11129 * font id or [FONS_INVALID]. 11130 */ 11131 int addFont (const(char)[] name, const(char)[] path, bool defAA=false) nothrow @trusted { 11132 if (path.length == 0 || name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID; 11133 if (path.length > 32768) return FONS_INVALID; // arbitrary limit 11134 11135 // if font path ends with ":noaa", turn off antialiasing 11136 if (path.length >= NoAlias.length && strequci(path[$-NoAlias.length..$], NoAlias)) { 11137 path = path[0..$-NoAlias.length]; 11138 if (path.length == 0) return FONS_INVALID; 11139 defAA = false; 11140 } 11141 11142 // if font name ends with ":noaa", turn off antialiasing 11143 if (name.length > NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) { 11144 name = name[0..$-NoAlias.length]; 11145 defAA = false; 11146 } 11147 11148 // find a font with the given name 11149 int fidx = findNameInHash(name); 11150 //{ import core.stdc.stdio; printf("loading font '%.*s' [%s] (fidx=%d)...\n", cast(uint)path.length, path.ptr, fontnamebuf.ptr, fidx); } 11151 11152 int loadFontFile (const(char)[] path) { 11153 // check if existing font (if any) has the same path 11154 if (fidx >= 0) { 11155 import core.stdc.string : strlen; 11156 auto plen = (fonts[fidx].path !is null ? strlen(fonts[fidx].path) : 0); 11157 version(Posix) { 11158 //{ 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); } 11159 if (plen == path.length && fonts[fidx].path[0..plen] == path) { 11160 //{ import core.stdc.stdio; printf("*** font [%.*s] already loaded from [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)plen, path.ptr); } 11161 // i found her! 11162 return fidx; 11163 } 11164 } else { 11165 if (plen == path.length && strequci(fonts[fidx].path[0..plen], path)) { 11166 // i found her! 11167 return fidx; 11168 } 11169 } 11170 } 11171 version(Windows) { 11172 // special shitdows check: this will reject fontconfig font names (but still allow things like "c:myfont") 11173 foreach (immutable char ch; path[(path.length >= 2 && path[1] == ':' ? 2 : 0)..$]) if (ch == ':') return FONS_INVALID; 11174 } 11175 // either no such font, or different path 11176 //{ import core.stdc.stdio; printf("trying font [%.*s] from file [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)path.length, path.ptr); } 11177 int xres = FONS_INVALID; 11178 try { 11179 import core.stdc.stdlib : free, malloc; 11180 static if (NanoVegaHasIVVFS) { 11181 auto fl = VFile(path); 11182 auto dataSize = fl.size; 11183 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID; 11184 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize); 11185 if (data is null) assert(0, "out of memory in NanoVega fontstash"); 11186 scope(failure) free(data); // oops 11187 fl.rawReadExact(data[0..cast(uint)dataSize]); 11188 fl.close(); 11189 } else { 11190 import core.stdc.stdio : FILE, fopen, fclose, fread, ftell, fseek; 11191 import std.internal.cstring : tempCString; 11192 auto fl = fopen(path.tempCString, "rb"); 11193 if (fl is null) return FONS_INVALID; 11194 scope(exit) fclose(fl); 11195 if (fseek(fl, 0, 2/*SEEK_END*/) != 0) return FONS_INVALID; 11196 auto dataSize = ftell(fl); 11197 if (fseek(fl, 0, 0/*SEEK_SET*/) != 0) return FONS_INVALID; 11198 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID; 11199 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize); 11200 if (data is null) assert(0, "out of memory in NanoVega fontstash"); 11201 scope(failure) free(data); // oops 11202 ubyte* dptr = data; 11203 auto left = cast(uint)dataSize; 11204 while (left > 0) { 11205 auto rd = fread(dptr, 1, left, fl); 11206 if (rd == 0) { free(data); return FONS_INVALID; } // unexpected EOF or reading error, it doesn't matter 11207 dptr += rd; 11208 left -= rd; 11209 } 11210 } 11211 scope(failure) free(data); // oops 11212 // create font data 11213 FONSfontData* fdata = fons__createFontData(data, cast(int)dataSize, true); // free data 11214 fdata.incref(); 11215 xres = addFontWithData(name, fdata, defAA); 11216 if (xres == FONS_INVALID) { 11217 fdata.decref(); // this will free [data] and [fdata] 11218 } else { 11219 // remember path 11220 fonts[xres].setPath(path); 11221 } 11222 } catch (Exception e) { 11223 // oops; sorry 11224 } 11225 return xres; 11226 } 11227 11228 // first try direct path 11229 auto res = loadFontFile(path); 11230 // if loading failed, try fontconfig (if fontconfig is available) 11231 static if (NanoVegaHasFontConfig) { 11232 if (res == FONS_INVALID && fontconfigAvailable) { 11233 // idiotic fontconfig NEVER fails; let's skip it if `path` looks like a path 11234 bool ok = true; 11235 if (path.length > 4 && (path[$-4..$] == ".ttf" || path[$-4..$] == ".ttc")) ok = false; 11236 if (ok) { foreach (immutable char ch; path) if (ch == '/') { ok = false; break; } } 11237 if (ok) { 11238 import std.internal.cstring : tempCString; 11239 FcPattern* pat = FcNameParse(path.tempCString); 11240 if (pat !is null) { 11241 scope(exit) FcPatternDestroy(pat); 11242 if (FcConfigSubstitute(null, pat, FcMatchPattern)) { 11243 FcDefaultSubstitute(pat); 11244 // find the font 11245 FcResult result; 11246 FcPattern* font = FcFontMatch(null, pat, &result); 11247 if (font !is null) { 11248 scope(exit) FcPatternDestroy(font); 11249 char* file = null; 11250 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) { 11251 if (file !is null && file[0]) { 11252 import core.stdc.string : strlen; 11253 res = loadFontFile(file[0..strlen(file)]); 11254 } 11255 } 11256 } 11257 } 11258 } 11259 } 11260 } 11261 } 11262 return res; 11263 } 11264 11265 /** Add font to FontStash, using data from memory. 11266 * 11267 * And already loaded font to FontStash. You can replace existing logical fonts. 11268 * But note that you can't remove logical font by passing "empty" data. 11269 * 11270 * $(WARNING If [FONS_INVALID] returned, `data` won't be freed even if `freeData` is `true`.) 11271 * 11272 * Params: 11273 * name = logical font name, that will be used to select this font later. 11274 * data = font data. 11275 * dataSize = font data size. 11276 * freeData = should FontStash take ownership of the font data? 11277 * defAA = should FontStash use antialiased font rasterizer? 11278 * 11279 * Returns: 11280 * font id or [FONS_INVALID]. 11281 */ 11282 int addFontMem (const(char)[] name, ubyte* data, int dataSize, bool freeData, bool defAA=false) nothrow @trusted @nogc { 11283 if (data is null || dataSize < 16) return FONS_INVALID; 11284 FONSfontData* fdata = fons__createFontData(data, dataSize, freeData); 11285 fdata.incref(); 11286 auto res = addFontWithData(name, fdata, defAA); 11287 if (res == FONS_INVALID) { 11288 // we promised to not free data on error 11289 fdata.freeData = false; 11290 fdata.decref(); // this will free [fdata] 11291 } 11292 return res; 11293 } 11294 11295 /** Add fonts from another FontStash. 11296 * 11297 * This is more effective (and faster) than reloading fonts, because internally font data 11298 * is reference counted. 11299 */ 11300 void addFontsFrom (FONSContext source) nothrow @trusted @nogc { 11301 if (source is null) return; 11302 foreach (FONSfont* font; source.fonts[0..source.nfonts]) { 11303 if (font !is null) { 11304 auto newidx = addCookedFont(font); 11305 FONSfont* newfont = fonts[newidx]; 11306 assert(newfont !is null); 11307 assert(newfont.path is null); 11308 // copy path 11309 if (font.path !is null && font.path[0]) { 11310 import core.stdc.stdlib : malloc; 11311 import core.stdc.string : strcpy, strlen; 11312 newfont.path = cast(char*)malloc(strlen(font.path)+1); 11313 if (newfont.path is null) assert(0, "FONS: out of memory"); 11314 strcpy(newfont.path, font.path); 11315 } 11316 } 11317 } 11318 } 11319 11320 /// Returns logical font name corresponding to the given font id, or `null`. 11321 /// $(WARNING Copy returned name, as name buffer can be invalidated by next FontStash API call!) 11322 const(char)[] getNameById (int idx) const pure nothrow @trusted @nogc { 11323 if (idx < 0 || idx >= nfonts || fonts[idx] is null) return null; 11324 return fonts[idx].name[0..fonts[idx].namelen]; 11325 } 11326 11327 /// Returns font id corresponding to the given logical font name, or [FONS_INVALID]. 11328 int getFontByName (const(char)[] name) const pure nothrow @trusted @nogc { 11329 //{ import core.stdc.stdio; printf("fonsGetFontByName: [%.*s]\n", cast(uint)name.length, name.ptr); } 11330 // remove ":noaa" suffix 11331 if (name.length >= NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) { 11332 name = name[0..$-NoAlias.length]; 11333 } 11334 if (name.length == 0) return FONS_INVALID; 11335 return findNameInHash(name); 11336 } 11337 11338 /** Measures the specified text string. Parameter bounds should be a float[4], 11339 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax] 11340 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn). 11341 */ 11342 float getTextBounds(T) (float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc if (isAnyCharType!T) { 11343 FONSstate* state = getState; 11344 uint codepoint; 11345 uint utf8state = 0; 11346 FONSQuad q; 11347 FONSglyph* glyph = null; 11348 int prevGlyphIndex = -1; 11349 short isize = cast(short)(state.size*10.0f); 11350 short iblur = cast(short)state.blur; 11351 FONSfont* font; 11352 11353 if (state.font < 0 || state.font >= nfonts) return 0; 11354 font = fonts[state.font]; 11355 if (font is null || font.fdata is null) return 0; 11356 11357 float scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 11358 11359 // Align vertically. 11360 y += getVertAlign(font, state.talign, isize); 11361 11362 float minx = x, maxx = x; 11363 float miny = y, maxy = y; 11364 float startx = x; 11365 11366 foreach (T ch; str) { 11367 static if (T.sizeof == 1) { 11368 //if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue; 11369 mixin(DecUtfMixin!("utf8state", "codepoint", "(cast(ubyte)ch)")); 11370 if (utf8state) continue; 11371 } else { 11372 static if (T.sizeof == 4) { 11373 if (ch > dchar.max) ch = 0xFFFD; 11374 } 11375 codepoint = cast(uint)ch; 11376 } 11377 glyph = getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional); 11378 if (glyph !is null) { 11379 getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q); 11380 if (q.x0 < minx) minx = q.x0; 11381 if (q.x1 > maxx) maxx = q.x1; 11382 if (params.isZeroTopLeft) { 11383 if (q.y0 < miny) miny = q.y0; 11384 if (q.y1 > maxy) maxy = q.y1; 11385 } else { 11386 if (q.y1 < miny) miny = q.y1; 11387 if (q.y0 > maxy) maxy = q.y0; 11388 } 11389 prevGlyphIndex = glyph.index; 11390 } else { 11391 //{ import core.stdc.stdio; printf("NO GLYPH FOR 0x%04x\n", cast(uint)codepoint); } 11392 prevGlyphIndex = -1; 11393 } 11394 } 11395 11396 float advance = x-startx; 11397 //{ import core.stdc.stdio; printf("***: x=%g; startx=%g; advance=%g\n", cast(double)x, cast(double)startx, cast(double)advance); } 11398 11399 // Align horizontally 11400 if (state.talign.left) { 11401 // empty 11402 } else if (state.talign.right) { 11403 minx -= advance; 11404 maxx -= advance; 11405 } else if (state.talign.center) { 11406 minx -= advance*0.5f; 11407 maxx -= advance*0.5f; 11408 } 11409 11410 if (bounds.length) { 11411 if (bounds.length > 0) bounds.ptr[0] = minx; 11412 if (bounds.length > 1) bounds.ptr[1] = miny; 11413 if (bounds.length > 2) bounds.ptr[2] = maxx; 11414 if (bounds.length > 3) bounds.ptr[3] = maxy; 11415 } 11416 11417 return advance; 11418 } 11419 11420 /// Returns various font metrics. Any argument can be `null` if you aren't interested in its value. 11421 void getVertMetrics (float* ascender, float* descender, float* lineh) nothrow @trusted @nogc { 11422 FONSstate* state = getState; 11423 if (state.font < 0 || state.font >= nfonts) { 11424 if (ascender !is null) *ascender = 0; 11425 if (descender !is null) *descender = 0; 11426 if (lineh !is null) *lineh = 0; 11427 } else { 11428 FONSfont* font = fonts[state.font]; 11429 if (font is null || font.fdata is null) { 11430 if (ascender !is null) *ascender = 0; 11431 if (descender !is null) *descender = 0; 11432 if (lineh !is null) *lineh = 0; 11433 } else { 11434 short isize = cast(short)(state.size*10.0f); 11435 if (ascender !is null) *ascender = font.ascender*isize/10.0f; 11436 if (descender !is null) *descender = font.descender*isize/10.0f; 11437 if (lineh !is null) *lineh = font.lineh*isize/10.0f; 11438 } 11439 } 11440 } 11441 11442 /// Returns line bounds. Any argument can be `null` if you aren't interested in its value. 11443 void getLineBounds (float y, float* minyp, float* maxyp) nothrow @trusted @nogc { 11444 FONSfont* font; 11445 FONSstate* state = getState; 11446 short isize; 11447 11448 if (minyp !is null) *minyp = 0; 11449 if (maxyp !is null) *maxyp = 0; 11450 11451 if (state.font < 0 || state.font >= nfonts) return; 11452 font = fonts[state.font]; 11453 isize = cast(short)(state.size*10.0f); 11454 if (font is null || font.fdata is null) return; 11455 11456 y += getVertAlign(font, state.talign, isize); 11457 11458 if (params.isZeroTopLeft) { 11459 immutable float miny = y-font.ascender*cast(float)isize/10.0f; 11460 immutable float maxy = miny+font.lineh*isize/10.0f; 11461 if (minyp !is null) *minyp = miny; 11462 if (maxyp !is null) *maxyp = maxy; 11463 } else { 11464 immutable float maxy = y+font.descender*cast(float)isize/10.0f; 11465 immutable float miny = maxy-font.lineh*isize/10.0f; 11466 if (minyp !is null) *minyp = miny; 11467 if (maxyp !is null) *maxyp = maxy; 11468 } 11469 } 11470 11471 /// Returns font line height. 11472 float fontHeight () nothrow @trusted @nogc { 11473 float res = void; 11474 getVertMetrics(null, null, &res); 11475 return res; 11476 } 11477 11478 /// Returns font ascender (positive). 11479 float fontAscender () nothrow @trusted @nogc { 11480 float res = void; 11481 getVertMetrics(&res, null, null); 11482 return res; 11483 } 11484 11485 /// Returns font descender (negative). 11486 float fontDescender () nothrow @trusted @nogc { 11487 float res = void; 11488 getVertMetrics(null, &res, null); 11489 return res; 11490 } 11491 11492 //TODO: document this 11493 const(ubyte)* getTextureData (int* width, int* height) nothrow @trusted @nogc { 11494 if (width !is null) *width = params.width; 11495 if (height !is null) *height = params.height; 11496 return texData; 11497 } 11498 11499 //TODO: document this 11500 bool validateTexture (int* dirty) nothrow @trusted @nogc { 11501 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) { 11502 dirty[0] = dirtyRect.ptr[0]; 11503 dirty[1] = dirtyRect.ptr[1]; 11504 dirty[2] = dirtyRect.ptr[2]; 11505 dirty[3] = dirtyRect.ptr[3]; 11506 // reset dirty rect 11507 dirtyRect.ptr[0] = params.width; 11508 dirtyRect.ptr[1] = params.height; 11509 dirtyRect.ptr[2] = 0; 11510 dirtyRect.ptr[3] = 0; 11511 return true; 11512 } 11513 return false; 11514 } 11515 11516 //TODO: document this 11517 void errorCallback (void delegate (FONSError error, int val) nothrow @trusted @nogc callback) nothrow @trusted @nogc { 11518 handleError = callback; 11519 } 11520 11521 //TODO: document this 11522 void getAtlasSize (int* width, int* height) const pure nothrow @trusted @nogc { 11523 if (width !is null) *width = params.width; 11524 if (height !is null) *height = params.height; 11525 } 11526 11527 //TODO: document this 11528 bool expandAtlas (int width, int height) nothrow @trusted @nogc { 11529 import core.stdc.stdlib : free; 11530 import core.stdc.string : memcpy, memset; 11531 11532 int maxy = 0; 11533 ubyte* data = null; 11534 11535 width = nvg__max(width, params.width); 11536 height = nvg__max(height, params.height); 11537 11538 if (width == params.width && height == params.height) return true; 11539 11540 // Flush pending glyphs. 11541 flush(); 11542 11543 // Create new texture 11544 if (params.renderResize !is null) { 11545 if (params.renderResize(params.userPtr, width, height) == 0) return false; 11546 } 11547 // Copy old texture data over. 11548 data = cast(ubyte*)malloc(width*height); 11549 if (data is null) return 0; 11550 foreach (immutable int i; 0..params.height) { 11551 ubyte* dst = &data[i*width]; 11552 ubyte* src = &texData[i*params.width]; 11553 memcpy(dst, src, params.width); 11554 if (width > params.width) memset(dst+params.width, 0, width-params.width); 11555 } 11556 if (height > params.height) memset(&data[params.height*width], 0, (height-params.height)*width); 11557 11558 free(texData); 11559 texData = data; 11560 11561 // Increase atlas size 11562 atlas.expand(width, height); 11563 11564 // Add existing data as dirty. 11565 foreach (immutable int i; 0..atlas.nnodes) maxy = nvg__max(maxy, atlas.nodes[i].y); 11566 dirtyRect.ptr[0] = 0; 11567 dirtyRect.ptr[1] = 0; 11568 dirtyRect.ptr[2] = params.width; 11569 dirtyRect.ptr[3] = maxy; 11570 11571 params.width = width; 11572 params.height = height; 11573 itw = 1.0f/params.width; 11574 ith = 1.0f/params.height; 11575 11576 return true; 11577 } 11578 11579 //TODO: document this 11580 bool resetAtlas (int width, int height) nothrow @trusted @nogc { 11581 import core.stdc.stdlib : realloc; 11582 import core.stdc.string : memcpy, memset; 11583 11584 // flush pending glyphs 11585 flush(); 11586 11587 // create new texture 11588 if (params.renderResize !is null) { 11589 if (params.renderResize(params.userPtr, width, height) == 0) return false; 11590 } 11591 11592 // reset atlas 11593 atlas.reset(width, height); 11594 11595 // clear texture data 11596 texData = cast(ubyte*)realloc(texData, width*height); 11597 if (texData is null) assert(0, "FONS: out of memory"); 11598 memset(texData, 0, width*height); 11599 11600 // reset dirty rect 11601 dirtyRect.ptr[0] = width; 11602 dirtyRect.ptr[1] = height; 11603 dirtyRect.ptr[2] = 0; 11604 dirtyRect.ptr[3] = 0; 11605 11606 // Reset cached glyphs 11607 foreach (FONSfont* font; fonts[0..nfonts]) { 11608 if (font !is null) { 11609 font.nglyphs = 0; 11610 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; 11611 } 11612 } 11613 11614 params.width = width; 11615 params.height = height; 11616 itw = 1.0f/params.width; 11617 ith = 1.0f/params.height; 11618 11619 // Add white rect at 0, 0 for debug drawing. 11620 addWhiteRect(2, 2); 11621 11622 return true; 11623 } 11624 11625 //TODO: document this 11626 bool getPathBounds (dchar dch, float[] bounds) nothrow @trusted @nogc { 11627 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 11628 static if (is(typeof(&fons__nvg__bounds))) { 11629 FONSstate* state = getState; 11630 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; } 11631 FONSfont* font; 11632 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11633 if (g == 0) { bounds[] = 0; return false; } 11634 assert(font !is null); 11635 return fons__nvg__bounds(&font.font, g, bounds); 11636 } else { 11637 bounds[] = 0; 11638 return false; 11639 } 11640 } 11641 11642 //TODO: document this 11643 bool toPath() (NVGContext vg, dchar dch, float[] bounds=null) nothrow @trusted @nogc { 11644 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 11645 static if (is(typeof(&fons__nvg__toPath))) { 11646 if (vg is null) { bounds[] = 0; return false; } 11647 FONSstate* state = getState; 11648 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; } 11649 FONSfont* font; 11650 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11651 if (g == 0) { bounds[] = 0; return false; } 11652 assert(font !is null); 11653 return fons__nvg__toPath(vg, &font.font, g, bounds); 11654 } else { 11655 bounds[] = 0; 11656 return false; 11657 } 11658 } 11659 11660 //TODO: document this 11661 bool toOutline (dchar dch, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 11662 if (ol is null) return false; 11663 static if (is(typeof(&fons__nvg__toOutline))) { 11664 FONSstate* state = getState; 11665 if (state.font < 0 || state.font >= nfonts) return false; 11666 FONSfont* font; 11667 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11668 if (g == 0) return false; 11669 assert(font !is null); 11670 return fons__nvg__toOutline(&font.font, g, ol); 11671 } else { 11672 return false; 11673 } 11674 } 11675 11676 //TODO: document this 11677 FONSglyph* getGlyph (FONSfont* font, uint codepoint, short isize, short iblur, FONSBitmapFlag bitmapOption) nothrow @trusted @nogc { 11678 static uint fons__hashint() (uint a) pure nothrow @safe @nogc { 11679 pragma(inline, true); 11680 a += ~(a<<15); 11681 a ^= (a>>10); 11682 a += (a<<3); 11683 a ^= (a>>6); 11684 a += ~(a<<11); 11685 a ^= (a>>16); 11686 return a; 11687 } 11688 11689 // based on Exponential blur, Jani Huhtanen, 2006 11690 enum APREC = 16; 11691 enum ZPREC = 7; 11692 11693 static void fons__blurCols (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc { 11694 foreach (immutable int y; 0..h) { 11695 int z = 0; // force zero border 11696 foreach (int x; 1..w) { 11697 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC; 11698 dst[x] = cast(ubyte)(z>>ZPREC); 11699 } 11700 dst[w-1] = 0; // force zero border 11701 z = 0; 11702 for (int x = w-2; x >= 0; --x) { 11703 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC; 11704 dst[x] = cast(ubyte)(z>>ZPREC); 11705 } 11706 dst[0] = 0; // force zero border 11707 dst += dstStride; 11708 } 11709 } 11710 11711 static void fons__blurRows (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc { 11712 foreach (immutable int x; 0..w) { 11713 int z = 0; // force zero border 11714 for (int y = dstStride; y < h*dstStride; y += dstStride) { 11715 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC; 11716 dst[y] = cast(ubyte)(z>>ZPREC); 11717 } 11718 dst[(h-1)*dstStride] = 0; // force zero border 11719 z = 0; 11720 for (int y = (h-2)*dstStride; y >= 0; y -= dstStride) { 11721 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC; 11722 dst[y] = cast(ubyte)(z>>ZPREC); 11723 } 11724 dst[0] = 0; // force zero border 11725 ++dst; 11726 } 11727 } 11728 11729 static void fons__blur (ubyte* dst, int w, int h, int dstStride, int blur) nothrow @trusted @nogc { 11730 import std.math : expf = exp; 11731 if (blur < 1) return; 11732 // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity) 11733 immutable float sigma = cast(float)blur*0.57735f; // 1/sqrt(3) 11734 int alpha = cast(int)((1<<APREC)*(1.0f-expf(-2.3f/(sigma+1.0f)))); 11735 fons__blurRows(dst, w, h, dstStride, alpha); 11736 fons__blurCols(dst, w, h, dstStride, alpha); 11737 fons__blurRows(dst, w, h, dstStride, alpha); 11738 fons__blurCols(dst, w, h, dstStride, alpha); 11739 //fons__blurrows(dst, w, h, dstStride, alpha); 11740 //fons__blurcols(dst, w, h, dstStride, alpha); 11741 } 11742 11743 int advance, lsb, x0, y0, x1, y1, gx, gy; 11744 FONSglyph* glyph = null; 11745 float size = isize/10.0f; 11746 FONSfont* renderFont = font; 11747 11748 if (isize < 2) return null; 11749 if (iblur > 20) iblur = 20; 11750 int pad = iblur+2; 11751 11752 // Reset allocator. 11753 nscratch = 0; 11754 11755 // Find code point and size. 11756 uint h = fons__hashint(codepoint)&(FONS_HASH_LUT_SIZE-1); 11757 int i = font.lut.ptr[h]; 11758 while (i != -1) { 11759 //if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) return &font.glyphs[i]; 11760 if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) { 11761 glyph = &font.glyphs[i]; 11762 // Negative coordinate indicates there is no bitmap data created. 11763 if (bitmapOption == FONSBitmapFlag.Optional || (glyph.x0 >= 0 && glyph.y0 >= 0)) return glyph; 11764 // At this point, glyph exists but the bitmap data is not yet created. 11765 break; 11766 } 11767 i = font.glyphs[i].next; 11768 } 11769 11770 // Create a new glyph or rasterize bitmap data for a cached glyph. 11771 //scale = fons__tt_getPixelHeightScale(&font.font, size); 11772 int g = findGlyphForCP(font, cast(dchar)codepoint, &renderFont); 11773 // It is possible that we did not find a fallback glyph. 11774 // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph. 11775 11776 float scale = fons__tt_getPixelHeightScale(&renderFont.font, size); 11777 fons__tt_buildGlyphBitmap(&renderFont.font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1); 11778 int gw = x1-x0+pad*2; 11779 int gh = y1-y0+pad*2; 11780 11781 // Determines the spot to draw glyph in the atlas. 11782 if (bitmapOption == FONSBitmapFlag.Required) { 11783 // Find free spot for the rect in the atlas. 11784 bool added = atlas.addRect(gw, gh, &gx, &gy); 11785 if (!added && handleError !is null) { 11786 // Atlas is full, let the user to resize the atlas (or not), and try again. 11787 handleError(FONSError.AtlasFull, 0); 11788 added = atlas.addRect(gw, gh, &gx, &gy); 11789 } 11790 if (!added) return null; 11791 } else { 11792 // Negative coordinate indicates there is no bitmap data created. 11793 gx = -1; 11794 gy = -1; 11795 } 11796 11797 // Init glyph. 11798 if (glyph is null) { 11799 glyph = font.allocGlyph(); 11800 glyph.codepoint = codepoint; 11801 glyph.size = isize; 11802 glyph.blur = iblur; 11803 glyph.next = 0; 11804 11805 // Insert char to hash lookup. 11806 glyph.next = font.lut.ptr[h]; 11807 font.lut.ptr[h] = font.nglyphs-1; 11808 } 11809 glyph.index = g; 11810 glyph.x0 = cast(short)gx; 11811 glyph.y0 = cast(short)gy; 11812 glyph.x1 = cast(short)(glyph.x0+gw); 11813 glyph.y1 = cast(short)(glyph.y0+gh); 11814 glyph.xadv = cast(short)(scale*advance*10.0f); 11815 glyph.xoff = cast(short)(x0-pad); 11816 glyph.yoff = cast(short)(y0-pad); 11817 11818 if (bitmapOption == FONSBitmapFlag.Optional) return glyph; 11819 11820 // Rasterize 11821 ubyte* dst = &texData[(glyph.x0+pad)+(glyph.y0+pad)*params.width]; 11822 fons__tt_renderGlyphBitmap(&font.font, dst, gw-pad*2, gh-pad*2, params.width, scale, scale, g); 11823 11824 // Make sure there is one pixel empty border. 11825 dst = &texData[glyph.x0+glyph.y0*params.width]; 11826 foreach (immutable int y; 0..gh) { 11827 dst[y*params.width] = 0; 11828 dst[gw-1+y*params.width] = 0; 11829 } 11830 foreach (immutable int x; 0..gw) { 11831 dst[x] = 0; 11832 dst[x+(gh-1)*params.width] = 0; 11833 } 11834 11835 // Debug code to color the glyph background 11836 version(none) { 11837 foreach (immutable yy; 0..gh) { 11838 foreach (immutable xx; 0..gw) { 11839 int a = cast(int)dst[xx+yy*params.width]+42; 11840 if (a > 255) a = 255; 11841 dst[xx+yy*params.width] = cast(ubyte)a; 11842 } 11843 } 11844 } 11845 11846 // Blur 11847 if (iblur > 0) { 11848 nscratch = 0; 11849 ubyte* bdst = &texData[glyph.x0+glyph.y0*params.width]; 11850 fons__blur(bdst, gw, gh, params.width, iblur); 11851 } 11852 11853 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], glyph.x0); 11854 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], glyph.y0); 11855 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], glyph.x1); 11856 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], glyph.y1); 11857 11858 return glyph; 11859 } 11860 11861 //TODO: document this 11862 void getQuad (FONSfont* font, int prevGlyphIndex, FONSglyph* glyph, float size, float scale, float spacing, float* x, float* y, FONSQuad* q) nothrow @trusted @nogc { 11863 if (prevGlyphIndex >= 0) { 11864 immutable float adv = fons__tt_getGlyphKernAdvance(&font.font, size, prevGlyphIndex, glyph.index)/**scale*/; //k8: do we really need scale here? 11865 //if (adv != 0) { import core.stdc.stdio; printf("adv=%g (scale=%g; spacing=%g)\n", cast(double)adv, cast(double)scale, cast(double)spacing); } 11866 *x += cast(int)(adv+spacing /*+0.5f*/); //k8: for me, it looks better this way (with non-aa fonts) 11867 } 11868 11869 // Each glyph has 2px border to allow good interpolation, 11870 // one pixel to prevent leaking, and one to allow good interpolation for rendering. 11871 // Inset the texture region by one pixel for correct interpolation. 11872 immutable float xoff = cast(short)(glyph.xoff+1); 11873 immutable float yoff = cast(short)(glyph.yoff+1); 11874 immutable float x0 = cast(float)(glyph.x0+1); 11875 immutable float y0 = cast(float)(glyph.y0+1); 11876 immutable float x1 = cast(float)(glyph.x1-1); 11877 immutable float y1 = cast(float)(glyph.y1-1); 11878 11879 if (params.isZeroTopLeft) { 11880 immutable float rx = cast(float)cast(int)(*x+xoff); 11881 immutable float ry = cast(float)cast(int)(*y+yoff); 11882 11883 q.x0 = rx; 11884 q.y0 = ry; 11885 q.x1 = rx+x1-x0; 11886 q.y1 = ry+y1-y0; 11887 11888 q.s0 = x0*itw; 11889 q.t0 = y0*ith; 11890 q.s1 = x1*itw; 11891 q.t1 = y1*ith; 11892 } else { 11893 immutable float rx = cast(float)cast(int)(*x+xoff); 11894 immutable float ry = cast(float)cast(int)(*y-yoff); 11895 11896 q.x0 = rx; 11897 q.y0 = ry; 11898 q.x1 = rx+x1-x0; 11899 q.y1 = ry-y1+y0; 11900 11901 q.s0 = x0*itw; 11902 q.t0 = y0*ith; 11903 q.s1 = x1*itw; 11904 q.t1 = y1*ith; 11905 } 11906 11907 *x += cast(int)(glyph.xadv/10.0f+0.5f); 11908 } 11909 11910 void flush () nothrow @trusted @nogc { 11911 // flush texture 11912 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) { 11913 if (params.renderUpdate !is null) params.renderUpdate(params.userPtr, dirtyRect.ptr, texData); 11914 // reset dirty rect 11915 dirtyRect.ptr[0] = params.width; 11916 dirtyRect.ptr[1] = params.height; 11917 dirtyRect.ptr[2] = 0; 11918 dirtyRect.ptr[3] = 0; 11919 } 11920 } 11921 } 11922 11923 /// Free all resources used by the `stash`, and `stash` itself. 11924 /// Group: font_stash 11925 public void kill (ref FONSContext stash) nothrow @trusted @nogc { 11926 import core.stdc.stdlib : free; 11927 if (stash is null) return; 11928 stash.clear(); 11929 free(stash); 11930 stash = null; 11931 } 11932 11933 11934 // ////////////////////////////////////////////////////////////////////////// // 11935 void* fons__tmpalloc (usize size, void* up) nothrow @trusted @nogc { 11936 ubyte* ptr; 11937 FONSContext stash = cast(FONSContext)up; 11938 // 16-byte align the returned pointer 11939 size = (size+0xf)&~0xf; 11940 if (stash.nscratch+cast(int)size > FONS_SCRATCH_BUF_SIZE) { 11941 if (stash.handleError !is null) stash.handleError(FONSError.ScratchFull, stash.nscratch+cast(int)size); 11942 return null; 11943 } 11944 ptr = stash.scratch+stash.nscratch; 11945 stash.nscratch += cast(int)size; 11946 return ptr; 11947 } 11948 11949 void fons__tmpfree (void* ptr, void* up) nothrow @trusted @nogc { 11950 // empty 11951 } 11952 11953 11954 // ////////////////////////////////////////////////////////////////////////// // 11955 // Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de> 11956 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. 11957 11958 enum FONS_UTF8_ACCEPT = 0; 11959 enum FONS_UTF8_REJECT = 12; 11960 11961 static immutable ubyte[364] utf8d = [ 11962 // The first part of the table maps bytes to character classes that 11963 // to reduce the size of the transition table and create bitmasks. 11964 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, 11965 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, 11966 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, 11967 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, 11968 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, 11969 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, 11970 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, 11971 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, 11972 11973 // The second part is a transition table that maps a combination 11974 // of a state of the automaton and a character class to a state. 11975 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 11976 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, 11977 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 11978 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 11979 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 11980 ]; 11981 11982 private enum DecUtfMixin(string state, string codep, string byte_) = 11983 `{ 11984 uint type_ = utf8d.ptr[`~byte_~`]; 11985 `~codep~` = (`~state~` != FONS_UTF8_ACCEPT ? (`~byte_~`&0x3fu)|(`~codep~`<<6) : (0xff>>type_)&`~byte_~`); 11986 if ((`~state~` = utf8d.ptr[256+`~state~`+type_]) == FONS_UTF8_REJECT) { 11987 `~state~` = FONS_UTF8_ACCEPT; 11988 `~codep~` = 0xFFFD; 11989 } 11990 }`; 11991 11992 /* 11993 uint fons__decutf8 (uint* state, uint* codep, uint byte_) { 11994 pragma(inline, true); 11995 uint type = utf8d.ptr[byte_]; 11996 *codep = (*state != FONS_UTF8_ACCEPT ? (byte_&0x3fu)|(*codep<<6) : (0xff>>type)&byte_); 11997 *state = utf8d.ptr[256 + *state+type]; 11998 return *state; 11999 } 12000 */ 12001 12002 12003 // ////////////////////////////////////////////////////////////////////////// // 12004 /// This iterator can be used to do text measurement. 12005 /// $(WARNING Don't add new fonts to stash while you are iterating, or you WILL get segfault!) 12006 /// Group: font_stash 12007 public struct FONSTextBoundsIterator { 12008 private: 12009 FONSContext stash; 12010 FONSstate state; 12011 uint codepoint = 0xFFFD; 12012 uint utf8state = 0; 12013 int prevGlyphIndex = -1; 12014 short isize, iblur; 12015 float scale = 0; 12016 FONSfont* font; 12017 float startx = 0, x = 0, y = 0; 12018 float minx = 0, miny = 0, maxx = 0, maxy = 0; 12019 12020 private: 12021 void clear () nothrow @trusted @nogc { 12022 import core.stdc.string : memset; 12023 memset(&this, 0, this.sizeof); 12024 this.prevGlyphIndex = -1; 12025 this.codepoint = 0xFFFD; 12026 } 12027 12028 public: 12029 /// Initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator. 12030 this (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { reset(astash, ax, ay); } 12031 12032 /// (Re)initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator. 12033 void reset (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { 12034 clear(); 12035 12036 if (astash is null || astash.nstates == 0) return; 12037 12038 stash = astash; 12039 state = *stash.getState; 12040 12041 if (state.font < 0 || state.font >= stash.nfonts) { clear(); return; } 12042 font = stash.fonts[state.font]; 12043 if (font is null || font.fdata is null) { clear(); return; } 12044 12045 x = ax; 12046 y = ay; 12047 isize = cast(short)(state.size*10.0f); 12048 iblur = cast(short)state.blur; 12049 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 12050 12051 // align vertically 12052 y += astash.getVertAlign(font, state.talign, isize); 12053 12054 minx = maxx = x; 12055 miny = maxy = y; 12056 startx = x; 12057 } 12058 12059 /// Can this iterator be used? 12060 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null); } 12061 12062 /// Put some text into iterator, calculate new values. 12063 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { 12064 enum DoCodePointMixin = q{ 12065 glyph = stash.getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional); 12066 if (glyph !is null) { 12067 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q); 12068 if (q.x0 < minx) minx = q.x0; 12069 if (q.x1 > maxx) maxx = q.x1; 12070 if (stash.params.isZeroTopLeft) { 12071 if (q.y0 < miny) miny = q.y0; 12072 if (q.y1 > maxy) maxy = q.y1; 12073 } else { 12074 if (q.y1 < miny) miny = q.y1; 12075 if (q.y0 > maxy) maxy = q.y0; 12076 } 12077 prevGlyphIndex = glyph.index; 12078 } else { 12079 prevGlyphIndex = -1; 12080 } 12081 }; 12082 12083 if (stash is null || str.length == 0) return; // alas 12084 12085 FONSQuad q; 12086 FONSglyph* glyph; 12087 12088 static if (is(T == char)) { 12089 foreach (char ch; str) { 12090 mixin(DecUtfMixin!("utf8state", "codepoint", "cast(ubyte)ch")); 12091 if (utf8state) continue; // full char is not collected yet 12092 mixin(DoCodePointMixin); 12093 } 12094 } else { 12095 if (utf8state) { 12096 utf8state = 0; 12097 codepoint = 0xFFFD; 12098 mixin(DoCodePointMixin); 12099 } 12100 foreach (T dch; str) { 12101 static if (is(T == dchar)) { 12102 if (dch > dchar.max) dch = 0xFFFD; 12103 } 12104 codepoint = cast(uint)dch; 12105 mixin(DoCodePointMixin); 12106 } 12107 } 12108 } 12109 12110 /// Returns current advance. 12111 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null ? x-startx : 0); } 12112 12113 /// Returns current text bounds. 12114 void getBounds (ref float[4] bounds) const pure nothrow @safe @nogc { 12115 if (stash is null) { bounds[] = 0; return; } 12116 float lminx = minx, lmaxx = maxx; 12117 // align horizontally 12118 if (state.talign.left) { 12119 // empty 12120 } else if (state.talign.right) { 12121 float ca = advance; 12122 lminx -= ca; 12123 lmaxx -= ca; 12124 } else if (state.talign.center) { 12125 float ca = advance*0.5f; 12126 lminx -= ca; 12127 lmaxx -= ca; 12128 } 12129 bounds[0] = lminx; 12130 bounds[1] = miny; 12131 bounds[2] = lmaxx; 12132 bounds[3] = maxy; 12133 } 12134 12135 /// Returns current horizontal text bounds. 12136 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc { 12137 if (stash !is null) { 12138 float lminx = minx, lmaxx = maxx; 12139 // align horizontally 12140 if (state.talign.left) { 12141 // empty 12142 } else if (state.talign.right) { 12143 float ca = advance; 12144 lminx -= ca; 12145 lmaxx -= ca; 12146 } else if (state.talign.center) { 12147 float ca = advance*0.5f; 12148 lminx -= ca; 12149 lmaxx -= ca; 12150 } 12151 xmin = lminx; 12152 xmax = lmaxx; 12153 } else { 12154 xmin = xmax = 0; 12155 } 12156 } 12157 12158 /// Returns current vertical text bounds. 12159 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc { 12160 pragma(inline, true); 12161 if (stash !is null) { 12162 ymin = miny; 12163 ymax = maxy; 12164 } else { 12165 ymin = ymax = 0; 12166 } 12167 } 12168 12169 /// Returns font line height. 12170 float lineHeight () nothrow @trusted @nogc { 12171 pragma(inline, true); 12172 return (stash !is null ? stash.fonts[state.font].lineh*cast(short)(state.size*10.0f)/10.0f : 0); 12173 } 12174 12175 /// Returns font ascender (positive). 12176 float ascender () nothrow @trusted @nogc { 12177 pragma(inline, true); 12178 return (stash !is null ? stash.fonts[state.font].ascender*cast(short)(state.size*10.0f)/10.0f : 0); 12179 } 12180 12181 /// Returns font descender (negative). 12182 float descender () nothrow @trusted @nogc { 12183 pragma(inline, true); 12184 return (stash !is null ? stash.fonts[state.font].descender*cast(short)(state.size*10.0f)/10.0f : 0); 12185 } 12186 } 12187 12188 12189 // ////////////////////////////////////////////////////////////////////////// // 12190 // backgl 12191 // ////////////////////////////////////////////////////////////////////////// // 12192 import core.stdc.stdlib : malloc, realloc, free; 12193 import core.stdc.string : memcpy, memset; 12194 12195 static if (__VERSION__ < 2076) { 12196 private auto DGNoThrowNoGC(T) (scope T t) /*if (isFunctionPointer!T || isDelegate!T)*/ { 12197 import std.traits; 12198 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_; 12199 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 12200 } 12201 } 12202 12203 12204 //import arsd.simpledisplay; 12205 version(nanovg_bindbc_opengl_bindings) { 12206 import bindbc.opengl; 12207 } else version(nanovg_builtin_opengl_bindings) { 12208 import arsd.simpledisplay; 12209 12210 /++ 12211 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. 12212 12213 History: 12214 Added January 22, 2021 (version 9.2 release) 12215 +/ 12216 public class NVGWindow : SimpleWindow { 12217 NVGContext nvg; 12218 12219 /++ 12220 12221 +/ 12222 this(int width, int height, string title) { 12223 setOpenGLContextVersion(3, 0); 12224 super(width, height, title, OpenGlOptions.yes, Resizability.allowResizing); 12225 12226 this.onClosing = delegate() { 12227 nvg.kill(); 12228 }; 12229 12230 this.visibleForTheFirstTime = delegate() { 12231 this.setAsCurrentOpenGlContext(); 12232 nvg = nvgCreateContext(); 12233 if(nvg is null) throw new Exception("cannot initialize NanoVega"); 12234 }; 12235 12236 this.redrawOpenGlScene = delegate() { 12237 if(redrawNVGScene is null) 12238 return; 12239 glViewport(0, 0, this.width, this.height); 12240 if(clearOnEachFrame) { 12241 glClearColor(0, 0, 0, 0); 12242 glClear(glNVGClearFlags); 12243 } 12244 12245 nvg.beginFrame(this.width, this.height); 12246 scope(exit) nvg.endFrame(); 12247 12248 redrawNVGScene(nvg); 12249 }; 12250 12251 this.setEventHandlers( 12252 &redrawOpenGlSceneNow, 12253 (KeyEvent ke) { 12254 if(ke.key == Key.Escape || ke.key == Key.Q) 12255 this.close(); 12256 } 12257 ); 12258 } 12259 12260 /++ 12261 12262 +/ 12263 bool clearOnEachFrame = true; 12264 12265 /++ 12266 12267 +/ 12268 void delegate(NVGContext nvg) redrawNVGScene; 12269 12270 /++ 12271 12272 +/ 12273 void redrawNVGSceneNow() { 12274 redrawOpenGlSceneNow(); 12275 } 12276 } 12277 12278 } else { 12279 import iv.glbinds; 12280 } 12281 12282 private: 12283 // sdpy is missing that yet 12284 static if (!is(typeof(GL_STENCIL_BUFFER_BIT))) enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 12285 12286 12287 12288 version(bindbc){ 12289 private extern(System) nothrow @nogc: 12290 // this definition doesn't exist in regular OpenGL (?) 12291 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 12292 private void nanovgInitOpenGL () { 12293 // i'm not aware of calling the load multiple times having negative side effects, so i don't do an initialization check 12294 GLSupport support = loadOpenGL(); 12295 if (support == GLSupport.noLibrary) 12296 assert(0, "OpenGL initialization failed: shared library failed to load"); 12297 else if (support == GLSupport.badLibrary) 12298 assert(0, "OpenGL initialization failed: a context-independent symbol failed to load"); 12299 else if (support == GLSupport.noContext) 12300 assert(0, "OpenGL initialization failed: a context needs to be created prior to initialization"); 12301 } 12302 } else { // OpenGL API missing from simpledisplay 12303 private void nanovgInitOpenGL () @nogc nothrow { 12304 __gshared bool initialized = false; 12305 if (initialized) return; 12306 12307 try 12308 gl3.loadDynamicLibrary(); 12309 catch(Exception) 12310 assert(0, "GL 3 failed to load"); 12311 12312 initialized = true; 12313 } 12314 } 12315 12316 12317 12318 /// Context creation flags. 12319 /// Group: context_management 12320 public enum NVGContextFlag : int { 12321 /// Nothing special, i.e. empty flag. 12322 None = 0, 12323 /// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA). 12324 Antialias = 1U<<0, 12325 /** Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little 12326 * slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. */ 12327 StencilStrokes = 1U<<1, 12328 /// Flag indicating that additional debug checks are done. 12329 Debug = 1U<<2, 12330 /// Filter (antialias) fonts 12331 FontAA = 1U<<7, 12332 /// Don't filter (antialias) fonts 12333 FontNoAA = 1U<<8, 12334 /// You can use this as a substitute for default flags, for cases like this: `nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);`. 12335 Default = 1U<<31, 12336 } 12337 12338 public enum NANOVG_GL_USE_STATE_FILTER = true; 12339 12340 /// Returns flags for glClear(). 12341 /// Group: context_management 12342 public uint glNVGClearFlags () pure nothrow @safe @nogc { 12343 pragma(inline, true); 12344 return (GL_COLOR_BUFFER_BIT|/*GL_DEPTH_BUFFER_BIT|*/GL_STENCIL_BUFFER_BIT); 12345 } 12346 12347 12348 // ////////////////////////////////////////////////////////////////////////// // 12349 private: 12350 12351 version = nanovega_shared_stencil; 12352 //version = nanovega_debug_clipping; 12353 12354 enum GLNVGuniformLoc { 12355 ViewSize, 12356 Tex, 12357 Frag, 12358 TMat, 12359 TTr, 12360 ClipTex, 12361 } 12362 12363 alias GLNVGshaderType = int; 12364 enum /*GLNVGshaderType*/ { 12365 NSVG_SHADER_FILLCOLOR, 12366 NSVG_SHADER_FILLGRAD, 12367 NSVG_SHADER_FILLIMG, 12368 NSVG_SHADER_SIMPLE, // also used for clipfill 12369 NSVG_SHADER_IMG, 12370 } 12371 12372 struct GLNVGshader { 12373 GLuint prog; 12374 GLuint frag; 12375 GLuint vert; 12376 GLint[GLNVGuniformLoc.max+1] loc; 12377 } 12378 12379 struct GLNVGtexture { 12380 int id; 12381 GLuint tex; 12382 int width, height; 12383 NVGtexture type; 12384 int flags; 12385 shared int rc; // this can be 0 with tex != 0 -- postponed deletion 12386 int nextfree; 12387 } 12388 12389 struct GLNVGblend { 12390 bool simple; 12391 GLenum srcRGB; 12392 GLenum dstRGB; 12393 GLenum srcAlpha; 12394 GLenum dstAlpha; 12395 } 12396 12397 alias GLNVGcallType = int; 12398 enum /*GLNVGcallType*/ { 12399 GLNVG_NONE = 0, 12400 GLNVG_FILL, 12401 GLNVG_CONVEXFILL, 12402 GLNVG_STROKE, 12403 GLNVG_TRIANGLES, 12404 GLNVG_AFFINE, // change affine transformation matrix 12405 GLNVG_PUSHCLIP, 12406 GLNVG_POPCLIP, 12407 GLNVG_RESETCLIP, 12408 GLNVG_CLIP_DDUMP_ON, 12409 GLNVG_CLIP_DDUMP_OFF, 12410 } 12411 12412 struct GLNVGcall { 12413 int type; 12414 int evenOdd; // for fill 12415 int image; 12416 int pathOffset; 12417 int pathCount; 12418 int triangleOffset; 12419 int triangleCount; 12420 int uniformOffset; 12421 NVGMatrix affine; 12422 GLNVGblend blendFunc; 12423 NVGClipMode clipmode; 12424 } 12425 12426 struct GLNVGpath { 12427 int fillOffset; 12428 int fillCount; 12429 int strokeOffset; 12430 int strokeCount; 12431 } 12432 12433 align(1) struct GLNVGfragUniforms { 12434 align(1): 12435 enum UNIFORM_ARRAY_SIZE = 13; 12436 // note: after modifying layout or size of uniform array, 12437 // don't forget to also update the fragment shader source! 12438 align(1) union { 12439 align(1): 12440 align(1) struct { 12441 align(1): 12442 float[12] scissorMat; // matrices are actually 3 vec4s 12443 float[12] paintMat; 12444 NVGColor innerCol; 12445 NVGColor middleCol; 12446 NVGColor outerCol; 12447 float[2] scissorExt; 12448 float[2] scissorScale; 12449 float[2] extent; 12450 float radius; 12451 float feather; 12452 float strokeMult; 12453 float strokeThr; 12454 float texType; 12455 float type; 12456 float doclip; 12457 float midp; // for gradients 12458 float unused2, unused3; 12459 } 12460 float[4][UNIFORM_ARRAY_SIZE] uniformArray; 12461 } 12462 } 12463 12464 enum GLMaskState { 12465 DontMask = -1, 12466 Uninitialized = 0, 12467 Initialized = 1, 12468 JustCleared = 2, 12469 } 12470 12471 import core.sync.mutex; 12472 __gshared Mutex GLNVGTextureLocker; 12473 shared static this() { 12474 GLNVGTextureLocker = new Mutex(); 12475 } 12476 12477 struct GLNVGcontext { 12478 private import core.thread : ThreadID; 12479 12480 GLNVGshader shader; 12481 GLNVGtexture* textures; 12482 float[2] view; 12483 int freetexid; // -1: none 12484 int ntextures; 12485 int ctextures; 12486 GLuint vertBuf; 12487 int fragSize; 12488 int flags; 12489 // FBOs for masks 12490 GLuint[NVG_MAX_STATES] fbo; 12491 GLuint[2][NVG_MAX_STATES] fboTex; // FBO textures: [0] is color, [1] is stencil 12492 int fboWidth, fboHeight; 12493 GLMaskState[NVG_MAX_STATES] maskStack; 12494 int msp; // mask stack pointer; starts from `0`; points to next free item; see below for logic description 12495 int lastClipFBO; // -666: cache invalidated; -1: don't mask 12496 int lastClipUniOfs; 12497 bool doClipUnion; // specal mode 12498 GLNVGshader shaderFillFBO; 12499 GLNVGshader shaderCopyFBO; 12500 12501 bool inFrame; // will be `true` if we can perform OpenGL operations (used in texture deletion) 12502 shared bool mustCleanTextures; // will be `true` if we should delete some textures 12503 ThreadID mainTID; 12504 uint mainFBO; 12505 12506 // Per frame buffers 12507 GLNVGcall* calls; 12508 int ccalls; 12509 int ncalls; 12510 GLNVGpath* paths; 12511 int cpaths; 12512 int npaths; 12513 NVGVertex* verts; 12514 int cverts; 12515 int nverts; 12516 ubyte* uniforms; 12517 int cuniforms; 12518 int nuniforms; 12519 NVGMatrix lastAffine; 12520 12521 // cached state 12522 static if (NANOVG_GL_USE_STATE_FILTER) { 12523 GLuint boundTexture; 12524 GLuint stencilMask; 12525 GLenum stencilFunc; 12526 GLint stencilFuncRef; 12527 GLuint stencilFuncMask; 12528 GLNVGblend blendFunc; 12529 } 12530 } 12531 12532 int glnvg__maxi() (int a, int b) { pragma(inline, true); return (a > b ? a : b); } 12533 12534 void glnvg__bindTexture (GLNVGcontext* gl, GLuint tex) nothrow @trusted @nogc { 12535 static if (NANOVG_GL_USE_STATE_FILTER) { 12536 if (gl.boundTexture != tex) { 12537 gl.boundTexture = tex; 12538 glBindTexture(GL_TEXTURE_2D, tex); 12539 } 12540 } else { 12541 glBindTexture(GL_TEXTURE_2D, tex); 12542 } 12543 } 12544 12545 void glnvg__stencilMask (GLNVGcontext* gl, GLuint mask) nothrow @trusted @nogc { 12546 static if (NANOVG_GL_USE_STATE_FILTER) { 12547 if (gl.stencilMask != mask) { 12548 gl.stencilMask = mask; 12549 glStencilMask(mask); 12550 } 12551 } else { 12552 glStencilMask(mask); 12553 } 12554 } 12555 12556 void glnvg__stencilFunc (GLNVGcontext* gl, GLenum func, GLint ref_, GLuint mask) nothrow @trusted @nogc { 12557 static if (NANOVG_GL_USE_STATE_FILTER) { 12558 if (gl.stencilFunc != func || gl.stencilFuncRef != ref_ || gl.stencilFuncMask != mask) { 12559 gl.stencilFunc = func; 12560 gl.stencilFuncRef = ref_; 12561 gl.stencilFuncMask = mask; 12562 glStencilFunc(func, ref_, mask); 12563 } 12564 } else { 12565 glStencilFunc(func, ref_, mask); 12566 } 12567 } 12568 12569 // texture id is never zero 12570 // sets refcount to one 12571 GLNVGtexture* glnvg__allocTexture (GLNVGcontext* gl) nothrow @trusted @nogc { 12572 GLNVGtexture* tex = null; 12573 12574 int tid = gl.freetexid; 12575 if (tid == -1) { 12576 if (gl.ntextures >= gl.ctextures) { 12577 assert(gl.ntextures == gl.ctextures); 12578 //pragma(msg, GLNVGtexture.sizeof*32); 12579 int ctextures = (gl.ctextures == 0 ? 32 : gl.ctextures+gl.ctextures/2); // 1.5x overallocate 12580 GLNVGtexture* textures = cast(GLNVGtexture*)realloc(gl.textures, GLNVGtexture.sizeof*ctextures); 12581 if (textures is null) assert(0, "NanoVega: out of memory for textures"); 12582 memset(&textures[gl.ctextures], 0, (ctextures-gl.ctextures)*GLNVGtexture.sizeof); 12583 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated more textures (n=%d; c=%d; nc=%d)\n", gl.ntextures, gl.ctextures, ctextures); }} 12584 gl.textures = textures; 12585 gl.ctextures = ctextures; 12586 } 12587 assert(gl.ntextures+1 <= gl.ctextures); 12588 tid = gl.ntextures++; 12589 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf(" got next free texture id %d, ntextures=%d\n", tid+1, gl.ntextures); }} 12590 } else { 12591 gl.freetexid = gl.textures[tid].nextfree; 12592 } 12593 assert(tid <= gl.ntextures); 12594 12595 assert(gl.textures[tid].id == 0); 12596 tex = &gl.textures[tid]; 12597 memset(tex, 0, (*tex).sizeof); 12598 tex.id = tid+1; 12599 tex.rc = 1; 12600 tex.nextfree = -1; 12601 12602 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated texture with id %d (%d)\n", tex.id, tid+1); }} 12603 12604 return tex; 12605 } 12606 12607 GLNVGtexture* glnvg__findTexture (GLNVGcontext* gl, int id) nothrow @trusted @nogc { 12608 if (id <= 0 || id > gl.ntextures) return null; 12609 if (gl.textures[id-1].id == 0) return null; // free one 12610 assert(gl.textures[id-1].id == id); 12611 return &gl.textures[id-1]; 12612 } 12613 12614 bool glnvg__deleteTexture (GLNVGcontext* gl, ref int id) nothrow @trusted @nogc { 12615 if (id <= 0 || id > gl.ntextures) return false; 12616 auto tx = &gl.textures[id-1]; 12617 if (tx.id == 0) { id = 0; return false; } // free one 12618 assert(tx.id == id); 12619 assert(tx.tex != 0); 12620 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("decrefing texture with id %d (%d)\n", tx.id, id); }} 12621 import core.atomic : atomicOp; 12622 if (atomicOp!"-="(tx.rc, 1) == 0) { 12623 import core.thread : ThreadID; 12624 ThreadID mytid; 12625 static if (__VERSION__ < 2076) { 12626 DGNoThrowNoGC(() { 12627 import core.thread; mytid = Thread.getThis.id; 12628 })(); 12629 } else { 12630 try { import core.thread; mytid = Thread.getThis.id; } catch (Exception e) {} 12631 } 12632 if (gl.mainTID == mytid && gl.inFrame) { 12633 // can delete it right now 12634 if ((tx.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tx.tex); 12635 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** deleted texture with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }} 12636 memset(tx, 0, (*tx).sizeof); 12637 //{ import core.stdc.stdio; printf("deleting texture with id %d\n", id); } 12638 tx.nextfree = gl.freetexid; 12639 gl.freetexid = id-1; 12640 } else { 12641 // alas, we aren't doing frame business, so we should postpone deletion 12642 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** POSTPONED texture deletion with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }} 12643 version(aliced) { 12644 { 12645 GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow; 12646 tx.id = 0; // mark it as dead 12647 gl.mustCleanTextures = true; // set "need cleanup" flag 12648 } 12649 } else { 12650 try { 12651 GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow; 12652 tx.id = 0; // mark it as dead 12653 gl.mustCleanTextures = true; // set "need cleanup" flag 12654 } catch (Exception e) {} 12655 } 12656 } 12657 } 12658 id = 0; 12659 return true; 12660 } 12661 12662 void glnvg__dumpShaderError (GLuint shader, const(char)* name, const(char)* type) nothrow @trusted @nogc { 12663 import core.stdc.stdio : fprintf, stderr; 12664 GLchar[512+1] str = 0; 12665 GLsizei len = 0; 12666 glGetShaderInfoLog(shader, 512, &len, str.ptr); 12667 if (len > 512) len = 512; 12668 str[len] = '\0'; 12669 fprintf(stderr, "Shader %s/%s error:\n%s\n", name, type, str.ptr); 12670 } 12671 12672 void glnvg__dumpProgramError (GLuint prog, const(char)* name) nothrow @trusted @nogc { 12673 import core.stdc.stdio : fprintf, stderr; 12674 GLchar[512+1] str = 0; 12675 GLsizei len = 0; 12676 glGetProgramInfoLog(prog, 512, &len, str.ptr); 12677 if (len > 512) len = 512; 12678 str[len] = '\0'; 12679 fprintf(stderr, "Program %s error:\n%s\n", name, str.ptr); 12680 } 12681 12682 void glnvg__resetError(bool force=false) (GLNVGcontext* gl) nothrow @trusted @nogc { 12683 static if (!force) { 12684 if ((gl.flags&NVGContextFlag.Debug) == 0) return; 12685 } 12686 glGetError(); 12687 } 12688 12689 void glnvg__checkError(bool force=false) (GLNVGcontext* gl, const(char)* str) nothrow @trusted @nogc { 12690 GLenum err; 12691 static if (!force) { 12692 if ((gl.flags&NVGContextFlag.Debug) == 0) return; 12693 } 12694 err = glGetError(); 12695 if (err != GL_NO_ERROR) { 12696 import core.stdc.stdio : fprintf, stderr; 12697 fprintf(stderr, "Error %08x after %s\n", err, str); 12698 return; 12699 } 12700 } 12701 12702 bool glnvg__createShader (GLNVGshader* shader, const(char)* name, const(char)* header, const(char)* opts, const(char)* vshader, const(char)* fshader) nothrow @trusted @nogc { 12703 GLint status; 12704 GLuint prog, vert, frag; 12705 const(char)*[3] str; 12706 12707 memset(shader, 0, (*shader).sizeof); 12708 12709 prog = glCreateProgram(); 12710 vert = glCreateShader(GL_VERTEX_SHADER); 12711 frag = glCreateShader(GL_FRAGMENT_SHADER); 12712 str[0] = header; 12713 str[1] = (opts !is null ? opts : ""); 12714 str[2] = vshader; 12715 glShaderSource(vert, 3, cast(const(char)**)str.ptr, null); 12716 12717 glCompileShader(vert); 12718 glGetShaderiv(vert, GL_COMPILE_STATUS, &status); 12719 if (status != GL_TRUE) { 12720 glnvg__dumpShaderError(vert, name, "vert"); 12721 return false; 12722 } 12723 12724 str[0] = header; 12725 str[1] = (opts !is null ? opts : ""); 12726 str[2] = fshader; 12727 glShaderSource(frag, 3, cast(const(char)**)str.ptr, null); 12728 12729 glCompileShader(frag); 12730 glGetShaderiv(frag, GL_COMPILE_STATUS, &status); 12731 if (status != GL_TRUE) { 12732 glnvg__dumpShaderError(frag, name, "frag"); 12733 return false; 12734 } 12735 12736 glAttachShader(prog, vert); 12737 glAttachShader(prog, frag); 12738 12739 glBindAttribLocation(prog, 0, "vertex"); 12740 glBindAttribLocation(prog, 1, "tcoord"); 12741 12742 glLinkProgram(prog); 12743 glGetProgramiv(prog, GL_LINK_STATUS, &status); 12744 if (status != GL_TRUE) { 12745 glnvg__dumpProgramError(prog, name); 12746 return false; 12747 } 12748 12749 shader.prog = prog; 12750 shader.vert = vert; 12751 shader.frag = frag; 12752 12753 return true; 12754 } 12755 12756 void glnvg__deleteShader (GLNVGshader* shader) nothrow @trusted @nogc { 12757 if (shader.prog != 0) glDeleteProgram(shader.prog); 12758 if (shader.vert != 0) glDeleteShader(shader.vert); 12759 if (shader.frag != 0) glDeleteShader(shader.frag); 12760 } 12761 12762 void glnvg__getUniforms (GLNVGshader* shader) nothrow @trusted @nogc { 12763 shader.loc[GLNVGuniformLoc.ViewSize] = glGetUniformLocation(shader.prog, "viewSize"); 12764 shader.loc[GLNVGuniformLoc.Tex] = glGetUniformLocation(shader.prog, "tex"); 12765 shader.loc[GLNVGuniformLoc.Frag] = glGetUniformLocation(shader.prog, "frag"); 12766 shader.loc[GLNVGuniformLoc.TMat] = glGetUniformLocation(shader.prog, "tmat"); 12767 shader.loc[GLNVGuniformLoc.TTr] = glGetUniformLocation(shader.prog, "ttr"); 12768 shader.loc[GLNVGuniformLoc.ClipTex] = glGetUniformLocation(shader.prog, "clipTex"); 12769 } 12770 12771 void glnvg__killFBOs (GLNVGcontext* gl) nothrow @trusted @nogc { 12772 foreach (immutable fidx, ref GLuint fbo; gl.fbo[]) { 12773 if (fbo != 0) { 12774 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); 12775 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); 12776 foreach (ref GLuint tid; gl.fboTex.ptr[fidx][]) if (tid != 0) { glDeleteTextures(1, &tid); tid = 0; } 12777 glDeleteFramebuffers(1, &fbo); 12778 fbo = 0; 12779 } 12780 } 12781 gl.fboWidth = gl.fboHeight = 0; 12782 } 12783 12784 // returns `true` is new FBO was created 12785 // will not unbind buffer, if it was created 12786 bool glnvg__allocFBO (GLNVGcontext* gl, int fidx, bool doclear=true) nothrow @trusted @nogc { 12787 assert(fidx >= 0 && fidx < gl.fbo.length); 12788 assert(gl.fboWidth > 0); 12789 assert(gl.fboHeight > 0); 12790 12791 if (gl.fbo.ptr[fidx] != 0) return false; // nothing to do, this FBO is already initialized 12792 12793 glnvg__resetError(gl); 12794 12795 // allocate FBO object 12796 GLuint fbo = 0; 12797 glGenFramebuffers(1, &fbo); 12798 if (fbo == 0) assert(0, "NanoVega: cannot create FBO"); 12799 glnvg__checkError(gl, "glnvg__allocFBO: glGenFramebuffers"); 12800 glBindFramebuffer(GL_FRAMEBUFFER, fbo); 12801 //scope(exit) glBindFramebuffer(GL_FRAMEBUFFER, 0); 12802 12803 // attach 2D texture to this FBO 12804 GLuint tidColor = 0; 12805 glGenTextures(1, &tidColor); 12806 if (tidColor == 0) assert(0, "NanoVega: cannot create RGBA texture for FBO"); 12807 glBindTexture(GL_TEXTURE_2D, tidColor); 12808 //scope(exit) glBindTexture(GL_TEXTURE_2D, 0); 12809 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 12810 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_S"); 12811 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 12812 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_T"); 12813 //FIXME: linear or nearest? 12814 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 12815 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 12816 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MIN_FILTER"); 12817 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 12818 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 12819 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MAG_FILTER"); 12820 // empty texture 12821 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 12822 // create texture with only one color channel 12823 glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, gl.fboWidth, gl.fboHeight, 0, GL_RED, GL_UNSIGNED_BYTE, null); 12824 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 12825 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (color)"); 12826 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tidColor, 0); 12827 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (color)"); 12828 12829 // attach stencil texture to this FBO 12830 GLuint tidStencil = 0; 12831 version(nanovega_shared_stencil) { 12832 if (gl.fboTex.ptr[0].ptr[0] == 0) { 12833 glGenTextures(1, &tidStencil); 12834 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO"); 12835 gl.fboTex.ptr[0].ptr[0] = tidStencil; 12836 } else { 12837 tidStencil = gl.fboTex.ptr[0].ptr[0]; 12838 } 12839 if (fidx != 0) gl.fboTex.ptr[fidx].ptr[1] = 0; // stencil texture is shared among FBOs 12840 } else { 12841 glGenTextures(1, &tidStencil); 12842 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO"); 12843 gl.fboTex.ptr[0].ptr[0] = tidStencil; 12844 } 12845 glBindTexture(GL_TEXTURE_2D, tidStencil); 12846 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, gl.fboWidth, gl.fboHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, null); 12847 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (stencil)"); 12848 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tidStencil, 0); 12849 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (stencil)"); 12850 12851 { 12852 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); 12853 if (status != GL_FRAMEBUFFER_COMPLETE) { 12854 version(all) { 12855 import core.stdc.stdio; 12856 if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) printf("fucked attachement\n"); 12857 if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) printf("fucked dimensions\n"); 12858 if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) printf("missing attachement\n"); 12859 if (status == GL_FRAMEBUFFER_UNSUPPORTED) printf("unsupported\n"); 12860 } 12861 assert(0, "NanoVega: framebuffer creation failed"); 12862 } 12863 } 12864 12865 // clear 'em all 12866 if (doclear) { 12867 glClearColor(0, 0, 0, 0); 12868 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 12869 } 12870 12871 // save texture ids 12872 gl.fbo.ptr[fidx] = fbo; 12873 gl.fboTex.ptr[fidx].ptr[0] = tidColor; 12874 version(nanovega_shared_stencil) {} else { 12875 gl.fboTex.ptr[fidx].ptr[1] = tidStencil; 12876 } 12877 12878 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture); 12879 12880 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): created with index %d\n", gl.msp-1, fidx); } 12881 12882 return true; 12883 } 12884 12885 // will not unbind buffer 12886 void glnvg__clearFBO (GLNVGcontext* gl, int fidx) nothrow @trusted @nogc { 12887 assert(fidx >= 0 && fidx < gl.fbo.length); 12888 assert(gl.fboWidth > 0); 12889 assert(gl.fboHeight > 0); 12890 assert(gl.fbo.ptr[fidx] != 0); 12891 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[fidx]); 12892 glClearColor(0, 0, 0, 0); 12893 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 12894 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cleared with index %d\n", gl.msp-1, fidx); } 12895 } 12896 12897 // will not unbind buffer 12898 void glnvg__copyFBOToFrom (GLNVGcontext* gl, int didx, int sidx) nothrow @trusted @nogc { 12899 import core.stdc.string : memset; 12900 assert(didx >= 0 && didx < gl.fbo.length); 12901 assert(sidx >= 0 && sidx < gl.fbo.length); 12902 assert(gl.fboWidth > 0); 12903 assert(gl.fboHeight > 0); 12904 assert(gl.fbo.ptr[didx] != 0); 12905 assert(gl.fbo.ptr[sidx] != 0); 12906 if (didx == sidx) return; 12907 12908 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copy FBO: %d -> %d\n", gl.msp-1, sidx, didx); } 12909 12910 glUseProgram(gl.shaderCopyFBO.prog); 12911 12912 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[didx]); 12913 glDisable(GL_CULL_FACE); 12914 glDisable(GL_BLEND); 12915 glDisable(GL_SCISSOR_TEST); 12916 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[sidx].ptr[0]); 12917 // copy texture by drawing full quad 12918 enum x = 0; 12919 enum y = 0; 12920 immutable int w = gl.fboWidth; 12921 immutable int h = gl.fboHeight; 12922 immutable(NVGVertex[4]) vertices = 12923 [NVGVertex(x, y), // top-left 12924 NVGVertex(w, y), // top-right 12925 NVGVertex(w, h), // bottom-right 12926 NVGVertex(x, h)]; // bottom-left 12927 12928 glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices); 12929 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 12930 12931 // restore state (but don't unbind FBO) 12932 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture); 12933 glEnable(GL_CULL_FACE); 12934 glEnable(GL_BLEND); 12935 glUseProgram(gl.shader.prog); 12936 } 12937 12938 void glnvg__resetFBOClipTextureCache (GLNVGcontext* gl) nothrow @trusted @nogc { 12939 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): texture cache invalidated (%d)\n", gl.msp-1, gl.lastClipFBO); } 12940 /* 12941 if (gl.lastClipFBO >= 0) { 12942 glActiveTexture(GL_TEXTURE1); 12943 glBindTexture(GL_TEXTURE_2D, 0); 12944 glActiveTexture(GL_TEXTURE0); 12945 } 12946 */ 12947 gl.lastClipFBO = -666; 12948 } 12949 12950 void glnvg__setFBOClipTexture (GLNVGcontext* gl, GLNVGfragUniforms* frag) nothrow @trusted @nogc { 12951 //assert(gl.msp > 0 && gl.msp <= gl.maskStack.length); 12952 if (gl.lastClipFBO != -666) { 12953 // cached 12954 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cached (%d)\n", gl.msp-1, gl.lastClipFBO); } 12955 frag.doclip = (gl.lastClipFBO >= 0 ? 1 : 0); 12956 return; 12957 } 12958 12959 // no cache 12960 int fboidx = -1; 12961 mainloop: foreach_reverse (immutable sp, GLMaskState mst; gl.maskStack.ptr[0..gl.msp]/*; reverse*/) { 12962 final switch (mst) { 12963 case GLMaskState.DontMask: fboidx = -1; break mainloop; 12964 case GLMaskState.Uninitialized: break; 12965 case GLMaskState.Initialized: fboidx = cast(int)sp; break mainloop; 12966 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__setFBOClipTexture()` internal error"); 12967 } 12968 } 12969 12970 if (fboidx < 0) { 12971 // don't mask 12972 gl.lastClipFBO = -1; 12973 frag.doclip = 0; 12974 } else { 12975 // do masking 12976 assert(gl.fbo.ptr[fboidx] != 0); 12977 gl.lastClipFBO = fboidx; 12978 frag.doclip = 1; 12979 } 12980 12981 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): new cache (new:%d)\n", gl.msp-1, gl.lastClipFBO); } 12982 12983 if (gl.lastClipFBO >= 0) { 12984 assert(gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 12985 glActiveTexture(GL_TEXTURE1); 12986 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 12987 glActiveTexture(GL_TEXTURE0); 12988 } 12989 } 12990 12991 // returns index in `gl.fbo`, or -1 for "don't mask" 12992 int glnvg__generateFBOClipTexture (GLNVGcontext* gl) nothrow @trusted @nogc { 12993 assert(gl.msp > 0 && gl.msp <= gl.maskStack.length); 12994 // we need initialized FBO, even for "don't mask" case 12995 // for this, look back in stack, and either copy initialized FBO, 12996 // or stop at first uninitialized one, and clear it 12997 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.Initialized) { 12998 // shortcut 12999 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); } 13000 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[gl.msp-1]); 13001 return gl.msp-1; 13002 } 13003 foreach_reverse (immutable sp; 0..gl.msp/*; reverse*/) { 13004 final switch (gl.maskStack.ptr[sp]) { 13005 case GLMaskState.DontMask: 13006 // clear it 13007 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture\n", gl.msp-1); } 13008 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1); 13009 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared; 13010 return gl.msp-1; 13011 case GLMaskState.Uninitialized: break; // do nothing 13012 case GLMaskState.Initialized: 13013 // i found her! copy to TOS 13014 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copying texture from %d\n", gl.msp-1, cast(int)sp); } 13015 glnvg__allocFBO(gl, gl.msp-1, false); 13016 glnvg__copyFBOToFrom(gl, gl.msp-1, sp); 13017 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized; 13018 return gl.msp-1; 13019 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__generateFBOClipTexture()` internal error"); 13020 } 13021 } 13022 // nothing was initialized, lol 13023 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture (first one)\n", gl.msp-1); } 13024 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1); 13025 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared; 13026 return gl.msp-1; 13027 } 13028 13029 void glnvg__renderPushClip (void* uptr) nothrow @trusted @nogc { 13030 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13031 GLNVGcall* call = glnvg__allocCall(gl); 13032 if (call is null) return; 13033 call.type = GLNVG_PUSHCLIP; 13034 } 13035 13036 void glnvg__renderPopClip (void* uptr) nothrow @trusted @nogc { 13037 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13038 GLNVGcall* call = glnvg__allocCall(gl); 13039 if (call is null) return; 13040 call.type = GLNVG_POPCLIP; 13041 } 13042 13043 void glnvg__renderResetClip (void* uptr) nothrow @trusted @nogc { 13044 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13045 GLNVGcall* call = glnvg__allocCall(gl); 13046 if (call is null) return; 13047 call.type = GLNVG_RESETCLIP; 13048 } 13049 13050 void glnvg__clipDebugDump (void* uptr, bool doit) nothrow @trusted @nogc { 13051 version(nanovega_debug_clipping) { 13052 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13053 GLNVGcall* call = glnvg__allocCall(gl); 13054 call.type = (doit ? GLNVG_CLIP_DDUMP_ON : GLNVG_CLIP_DDUMP_OFF); 13055 } 13056 } 13057 13058 bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc { 13059 import core.stdc.stdio : snprintf; 13060 13061 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13062 enum align_ = 4; 13063 13064 char[64] shaderHeader = void; 13065 //enum shaderHeader = "#define UNIFORM_ARRAY_SIZE 12\n"; 13066 snprintf(shaderHeader.ptr, shaderHeader.length, "#define UNIFORM_ARRAY_SIZE %u\n", cast(uint)GLNVGfragUniforms.UNIFORM_ARRAY_SIZE); 13067 13068 enum fillVertShader = q{ 13069 uniform vec2 viewSize; 13070 attribute vec2 vertex; 13071 attribute vec2 tcoord; 13072 varying vec2 ftcoord; 13073 varying vec2 fpos; 13074 uniform vec4 tmat; /* abcd of affine matrix: xyzw */ 13075 uniform vec2 ttr; /* tx and ty of affine matrix */ 13076 void main (void) { 13077 /* affine transformation */ 13078 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x; 13079 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y; 13080 ftcoord = tcoord; 13081 fpos = vec2(nx, ny); 13082 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1); 13083 } 13084 }; 13085 13086 enum fillFragShader = ` 13087 uniform vec4 frag[UNIFORM_ARRAY_SIZE]; 13088 uniform sampler2D tex; 13089 uniform sampler2D clipTex; 13090 uniform vec2 viewSize; 13091 varying vec2 ftcoord; 13092 varying vec2 fpos; 13093 #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz) 13094 #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz) 13095 #define innerCol frag[6] 13096 #define middleCol frag[7] 13097 #define outerCol frag[7+1] 13098 #define scissorExt frag[8+1].xy 13099 #define scissorScale frag[8+1].zw 13100 #define extent frag[9+1].xy 13101 #define radius frag[9+1].z 13102 #define feather frag[9+1].w 13103 #define strokeMult frag[10+1].x 13104 #define strokeThr frag[10+1].y 13105 #define texType int(frag[10+1].z) 13106 #define type int(frag[10+1].w) 13107 #define doclip int(frag[11+1].x) 13108 #define midp frag[11+1].y 13109 13110 float sdroundrect (in vec2 pt, in vec2 ext, in float rad) { 13111 vec2 ext2 = ext-vec2(rad, rad); 13112 vec2 d = abs(pt)-ext2; 13113 return min(max(d.x, d.y), 0.0)+length(max(d, 0.0))-rad; 13114 } 13115 13116 // Scissoring 13117 float scissorMask (in vec2 p) { 13118 vec2 sc = (abs((scissorMat*vec3(p, 1.0)).xy)-scissorExt); 13119 sc = vec2(0.5, 0.5)-sc*scissorScale; 13120 return clamp(sc.x, 0.0, 1.0)*clamp(sc.y, 0.0, 1.0); 13121 } 13122 13123 #ifdef EDGE_AA 13124 // Stroke - from [0..1] to clipped pyramid, where the slope is 1px. 13125 float strokeMask () { 13126 return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult)*min(1.0, ftcoord.y); 13127 } 13128 #endif 13129 13130 void main (void) { 13131 // clipping 13132 if (doclip != 0) { 13133 /*vec4 clr = texelFetch(clipTex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);*/ 13134 vec4 clr = texture2D(clipTex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y)); 13135 if (clr.r == 0.0) discard; 13136 } 13137 float scissor = scissorMask(fpos); 13138 if (scissor <= 0.0) discard; //k8: is it really faster? 13139 #ifdef EDGE_AA 13140 float strokeAlpha = strokeMask(); 13141 if (strokeAlpha < strokeThr) discard; 13142 #else 13143 float strokeAlpha = 1.0; 13144 #endif 13145 // rendering 13146 vec4 color; 13147 if (type == 0) { /* NSVG_SHADER_FILLCOLOR */ 13148 color = innerCol; 13149 // Combine alpha 13150 color *= strokeAlpha*scissor; 13151 } else if (type == 1) { /* NSVG_SHADER_FILLGRAD */ 13152 // Gradient 13153 // Calculate gradient color using box gradient 13154 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy; 13155 float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0); 13156 if (midp <= 0.0) { 13157 color = mix(innerCol, outerCol, d); 13158 } else { 13159 float gdst = min(midp, 1.0); 13160 if (d < gdst) { 13161 color = mix(innerCol, middleCol, d/gdst); 13162 } else { 13163 color = mix(middleCol, outerCol, (d-gdst)/gdst); 13164 } 13165 } 13166 // Combine alpha 13167 color *= strokeAlpha*scissor; 13168 } else if (type == 2) { /* NSVG_SHADER_FILLIMG */ 13169 // Image 13170 // Calculate color from texture 13171 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy/extent; 13172 color = texture2D(tex, pt); 13173 if (texType == 1) color = vec4(color.xyz*color.w, color.w); 13174 if (texType == 2) color = vec4(color.x); 13175 // Apply color tint and alpha 13176 color *= innerCol; 13177 // Combine alpha 13178 color *= strokeAlpha*scissor; 13179 } else if (type == 3) { /* NSVG_SHADER_SIMPLE */ 13180 // Stencil fill 13181 color = vec4(1, 1, 1, 1); 13182 } else if (type == 4) { /* NSVG_SHADER_IMG */ 13183 // Textured tris 13184 color = texture2D(tex, ftcoord); 13185 if (texType == 1) color = vec4(color.xyz*color.w, color.w); 13186 if (texType == 2) color = vec4(color.x); 13187 color *= scissor; 13188 color *= innerCol; // Apply color tint 13189 } 13190 gl_FragColor = color; 13191 } 13192 `; 13193 13194 enum clipVertShaderFill = q{ 13195 uniform vec2 viewSize; 13196 attribute vec2 vertex; 13197 uniform vec4 tmat; /* abcd of affine matrix: xyzw */ 13198 uniform vec2 ttr; /* tx and ty of affine matrix */ 13199 void main (void) { 13200 /* affine transformation */ 13201 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x; 13202 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y; 13203 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1); 13204 } 13205 }; 13206 13207 enum clipFragShaderFill = q{ 13208 uniform vec2 viewSize; 13209 void main (void) { 13210 gl_FragColor = vec4(1, 1, 1, 1); 13211 } 13212 }; 13213 13214 enum clipVertShaderCopy = q{ 13215 uniform vec2 viewSize; 13216 attribute vec2 vertex; 13217 void main (void) { 13218 gl_Position = vec4(2.0*vertex.x/viewSize.x-1.0, 1.0-2.0*vertex.y/viewSize.y, 0, 1); 13219 } 13220 }; 13221 13222 enum clipFragShaderCopy = q{ 13223 uniform sampler2D tex; 13224 uniform vec2 viewSize; 13225 void main (void) { 13226 //gl_FragColor = texelFetch(tex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0); 13227 gl_FragColor = texture2D(tex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y)); 13228 } 13229 }; 13230 13231 glnvg__checkError(gl, "init"); 13232 13233 string defines = (gl.flags&NVGContextFlag.Antialias ? "#define EDGE_AA 1\n" : null); 13234 if (!glnvg__createShader(&gl.shader, "shader", shaderHeader.ptr, defines.ptr, fillVertShader, fillFragShader)) return false; 13235 if (!glnvg__createShader(&gl.shaderFillFBO, "shaderFillFBO", shaderHeader.ptr, defines.ptr, clipVertShaderFill, clipFragShaderFill)) return false; 13236 if (!glnvg__createShader(&gl.shaderCopyFBO, "shaderCopyFBO", shaderHeader.ptr, defines.ptr, clipVertShaderCopy, clipFragShaderCopy)) return false; 13237 13238 glnvg__checkError(gl, "uniform locations"); 13239 glnvg__getUniforms(&gl.shader); 13240 glnvg__getUniforms(&gl.shaderFillFBO); 13241 glnvg__getUniforms(&gl.shaderCopyFBO); 13242 13243 // Create dynamic vertex array 13244 glGenBuffers(1, &gl.vertBuf); 13245 13246 gl.fragSize = GLNVGfragUniforms.sizeof+align_-GLNVGfragUniforms.sizeof%align_; 13247 13248 glnvg__checkError(gl, "create done"); 13249 13250 glFinish(); 13251 13252 return true; 13253 } 13254 13255 int glnvg__renderCreateTexture (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc { 13256 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13257 GLNVGtexture* tex = glnvg__allocTexture(gl); 13258 13259 if (tex is null) return 0; 13260 13261 glGenTextures(1, &tex.tex); 13262 tex.width = w; 13263 tex.height = h; 13264 tex.type = type; 13265 tex.flags = imageFlags; 13266 glnvg__bindTexture(gl, tex.tex); 13267 13268 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("created texture with id %d; glid=%u\n", tex.id, tex.tex); }} 13269 13270 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 13271 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width); 13272 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13273 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13274 13275 13276 13277 immutable ttype = (type == NVGtexture.RGBA ? GL_RGBA : GL_RED); 13278 glTexImage2D(GL_TEXTURE_2D, 0, ttype, w, h, 0, ttype, GL_UNSIGNED_BYTE, data); 13279 // GL 3.0 and later have support for a dedicated function for generating mipmaps 13280 // it needs to be called after the glTexImage2D call 13281 if (imageFlags & NVGImageFlag.GenerateMipmaps) 13282 glGenerateMipmap(GL_TEXTURE_2D); 13283 13284 immutable tfmin = 13285 (imageFlags & NVGImageFlag.GenerateMipmaps ? GL_LINEAR_MIPMAP_LINEAR : 13286 imageFlags & NVGImageFlag.NoFiltering ? GL_NEAREST : 13287 GL_LINEAR); 13288 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.0); 13289 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tfmin); 13290 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST : GL_LINEAR)); 13291 13292 int flag; 13293 if (imageFlags&NVGImageFlag.RepeatX) 13294 flag = GL_REPEAT; 13295 else if (imageFlags&NVGImageFlag.ClampToBorderX) 13296 flag = GL_CLAMP_TO_BORDER; 13297 else 13298 flag = GL_CLAMP_TO_EDGE; 13299 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, flag); 13300 13301 13302 if (imageFlags&NVGImageFlag.RepeatY) 13303 flag = GL_REPEAT; 13304 else if (imageFlags&NVGImageFlag.ClampToBorderY) 13305 flag = GL_CLAMP_TO_BORDER; 13306 else 13307 flag = GL_CLAMP_TO_EDGE; 13308 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, flag); 13309 13310 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 13311 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 13312 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13313 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13314 13315 glnvg__checkError(gl, "create tex"); 13316 glnvg__bindTexture(gl, 0); 13317 13318 return tex.id; 13319 } 13320 13321 bool glnvg__renderDeleteTexture (void* uptr, int image) nothrow @trusted @nogc { 13322 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13323 return glnvg__deleteTexture(gl, image); 13324 } 13325 13326 bool glnvg__renderTextureIncRef (void* uptr, int image) nothrow @trusted @nogc { 13327 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13328 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13329 if (tex is null) { 13330 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT incref texture with id %d\n", image); }} 13331 return false; 13332 } 13333 import core.atomic : atomicOp; 13334 atomicOp!"+="(tex.rc, 1); 13335 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("texture #%d: incref; newref=%d\n", image, tex.rc); }} 13336 return true; 13337 } 13338 13339 bool glnvg__renderUpdateTexture (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc { 13340 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13341 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13342 13343 if (tex is null) { 13344 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT update texture with id %d\n", image); }} 13345 return false; 13346 } 13347 13348 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("updated texture with id %d; glid=%u\n", tex.id, image, tex.tex); }} 13349 13350 glnvg__bindTexture(gl, tex.tex); 13351 13352 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 13353 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width); 13354 glPixelStorei(GL_UNPACK_SKIP_PIXELS, x); 13355 glPixelStorei(GL_UNPACK_SKIP_ROWS, y); 13356 13357 immutable ttype = (tex.type == NVGtexture.RGBA ? GL_RGBA : GL_RED); 13358 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, ttype, GL_UNSIGNED_BYTE, data); 13359 13360 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 13361 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 13362 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13363 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13364 13365 glnvg__bindTexture(gl, 0); 13366 13367 return true; 13368 } 13369 13370 bool glnvg__renderGetTextureSize (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc { 13371 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13372 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13373 if (tex is null) { 13374 if (w !is null) *w = 0; 13375 if (h !is null) *h = 0; 13376 return false; 13377 } else { 13378 if (w !is null) *w = tex.width; 13379 if (h !is null) *h = tex.height; 13380 return true; 13381 } 13382 } 13383 13384 void glnvg__xformToMat3x4 (float[] m3, const(float)[] t) nothrow @trusted @nogc { 13385 assert(t.length >= 6); 13386 assert(m3.length >= 12); 13387 m3.ptr[0] = t.ptr[0]; 13388 m3.ptr[1] = t.ptr[1]; 13389 m3.ptr[2] = 0.0f; 13390 m3.ptr[3] = 0.0f; 13391 m3.ptr[4] = t.ptr[2]; 13392 m3.ptr[5] = t.ptr[3]; 13393 m3.ptr[6] = 0.0f; 13394 m3.ptr[7] = 0.0f; 13395 m3.ptr[8] = t.ptr[4]; 13396 m3.ptr[9] = t.ptr[5]; 13397 m3.ptr[10] = 1.0f; 13398 m3.ptr[11] = 0.0f; 13399 } 13400 13401 NVGColor glnvg__premulColor() (const scope auto ref NVGColor c) nothrow @trusted @nogc { 13402 //pragma(inline, true); 13403 NVGColor res = void; 13404 res.r = c.r*c.a; 13405 res.g = c.g*c.a; 13406 res.b = c.b*c.a; 13407 res.a = c.a; 13408 return res; 13409 } 13410 13411 bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* paint, NVGscissor* scissor, float width, float fringe, float strokeThr) nothrow @trusted @nogc { 13412 import core.stdc.math : sqrtf; 13413 GLNVGtexture* tex = null; 13414 NVGMatrix invxform = void; 13415 13416 memset(frag, 0, (*frag).sizeof); 13417 13418 frag.innerCol = glnvg__premulColor(paint.innerColor); 13419 frag.middleCol = glnvg__premulColor(paint.middleColor); 13420 frag.outerCol = glnvg__premulColor(paint.outerColor); 13421 frag.midp = paint.midp; 13422 13423 if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) { 13424 memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof); 13425 frag.scissorExt.ptr[0] = 1.0f; 13426 frag.scissorExt.ptr[1] = 1.0f; 13427 frag.scissorScale.ptr[0] = 1.0f; 13428 frag.scissorScale.ptr[1] = 1.0f; 13429 } else { 13430 //nvgTransformInverse(invxform[], scissor.xform[]); 13431 invxform = scissor.xform.inverted; 13432 glnvg__xformToMat3x4(frag.scissorMat[], invxform.mat[]); 13433 frag.scissorExt.ptr[0] = scissor.extent.ptr[0]; 13434 frag.scissorExt.ptr[1] = scissor.extent.ptr[1]; 13435 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; 13436 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; 13437 } 13438 13439 memcpy(frag.extent.ptr, paint.extent.ptr, frag.extent.sizeof); 13440 frag.strokeMult = (width*0.5f+fringe*0.5f)/fringe; 13441 frag.strokeThr = strokeThr; 13442 13443 if (paint.image.valid) { 13444 tex = glnvg__findTexture(gl, paint.image.id); 13445 if (tex is null) return false; 13446 if ((tex.flags&NVGImageFlag.FlipY) != 0) { 13447 /* 13448 NVGMatrix flipped; 13449 nvgTransformScale(flipped[], 1.0f, -1.0f); 13450 nvgTransformMultiply(flipped[], paint.xform[]); 13451 nvgTransformInverse(invxform[], flipped[]); 13452 */ 13453 /* 13454 NVGMatrix m1 = void, m2 = void; 13455 nvgTransformTranslate(m1[], 0.0f, frag.extent.ptr[1]*0.5f); 13456 nvgTransformMultiply(m1[], paint.xform[]); 13457 nvgTransformScale(m2[], 1.0f, -1.0f); 13458 nvgTransformMultiply(m2[], m1[]); 13459 nvgTransformTranslate(m1[], 0.0f, -frag.extent.ptr[1]*0.5f); 13460 nvgTransformMultiply(m1[], m2[]); 13461 nvgTransformInverse(invxform[], m1[]); 13462 */ 13463 NVGMatrix m1 = NVGMatrix.Translated(0.0f, frag.extent.ptr[1]*0.5f); 13464 m1.mul(paint.xform); 13465 NVGMatrix m2 = NVGMatrix.Scaled(1.0f, -1.0f); 13466 m2.mul(m1); 13467 m1 = NVGMatrix.Translated(0.0f, -frag.extent.ptr[1]*0.5f); 13468 m1.mul(m2); 13469 invxform = m1.inverted; 13470 } else { 13471 //nvgTransformInverse(invxform[], paint.xform[]); 13472 invxform = paint.xform.inverted; 13473 } 13474 frag.type = NSVG_SHADER_FILLIMG; 13475 13476 if (tex.type == NVGtexture.RGBA) { 13477 frag.texType = (tex.flags&NVGImageFlag.Premultiplied ? 0 : 1); 13478 } else { 13479 frag.texType = 2; 13480 } 13481 //printf("frag.texType = %d\n", frag.texType); 13482 } else { 13483 frag.type = (paint.simpleColor ? NSVG_SHADER_FILLCOLOR : NSVG_SHADER_FILLGRAD); 13484 frag.radius = paint.radius; 13485 frag.feather = paint.feather; 13486 //nvgTransformInverse(invxform[], paint.xform[]); 13487 invxform = paint.xform.inverted; 13488 } 13489 13490 glnvg__xformToMat3x4(frag.paintMat[], invxform.mat[]); 13491 13492 return true; 13493 } 13494 13495 void glnvg__setUniforms (GLNVGcontext* gl, int uniformOffset, int image) nothrow @trusted @nogc { 13496 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); 13497 glnvg__setFBOClipTexture(gl, frag); 13498 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.Frag], frag.UNIFORM_ARRAY_SIZE, &(frag.uniformArray.ptr[0].ptr[0])); 13499 glnvg__checkError(gl, "glnvg__setUniforms"); 13500 if (image != 0) { 13501 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13502 glnvg__bindTexture(gl, (tex !is null ? tex.tex : 0)); 13503 glnvg__checkError(gl, "tex paint tex"); 13504 } else { 13505 glnvg__bindTexture(gl, 0); 13506 } 13507 } 13508 13509 void glnvg__finishClip (GLNVGcontext* gl, NVGClipMode clipmode) nothrow @trusted @nogc { 13510 assert(clipmode != NVGClipMode.None); 13511 13512 // fill FBO, clear stencil buffer 13513 //TODO: optimize with bounds? 13514 version(all) { 13515 //glnvg__resetAffine(gl); 13516 //glUseProgram(gl.shaderFillFBO.prog); 13517 glDisable(GL_CULL_FACE); 13518 glDisable(GL_BLEND); 13519 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13520 glEnable(GL_STENCIL_TEST); 13521 if (gl.doClipUnion) { 13522 // for "and" we should clear everything that is NOT stencil-masked 13523 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13524 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13525 } else { 13526 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x00, 0xff); 13527 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13528 } 13529 13530 immutable(NVGVertex[4]) vertices = 13531 [NVGVertex(0, 0, 0, 0), 13532 NVGVertex(0, gl.fboHeight, 0, 0), 13533 NVGVertex(gl.fboWidth, gl.fboHeight, 0, 0), 13534 NVGVertex(gl.fboWidth, 0, 0, 0)]; 13535 13536 glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices); 13537 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 13538 13539 //glnvg__restoreAffine(gl); 13540 } 13541 13542 glBindFramebuffer(GL_FRAMEBUFFER, gl.mainFBO); 13543 glDisable(GL_COLOR_LOGIC_OP); 13544 //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // done above 13545 glEnable(GL_BLEND); 13546 glDisable(GL_STENCIL_TEST); 13547 glEnable(GL_CULL_FACE); 13548 glUseProgram(gl.shader.prog); 13549 13550 // set current FBO as used one 13551 assert(gl.msp > 0 && gl.fbo.ptr[gl.msp-1] > 0 && gl.fboTex.ptr[gl.msp-1].ptr[0] > 0); 13552 if (gl.lastClipFBO != gl.msp-1) { 13553 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); } 13554 gl.lastClipFBO = gl.msp-1; 13555 glActiveTexture(GL_TEXTURE1); 13556 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 13557 glActiveTexture(GL_TEXTURE0); 13558 } 13559 } 13560 13561 void glnvg__setClipUniforms (GLNVGcontext* gl, int uniformOffset, NVGClipMode clipmode) nothrow @trusted @nogc { 13562 assert(clipmode != NVGClipMode.None); 13563 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); 13564 // save uniform offset for `glnvg__finishClip()` 13565 gl.lastClipUniOfs = uniformOffset; 13566 // get FBO index, bind this FBO 13567 immutable int clipTexId = glnvg__generateFBOClipTexture(gl); 13568 assert(clipTexId >= 0); 13569 glUseProgram(gl.shaderFillFBO.prog); 13570 glnvg__checkError(gl, "use"); 13571 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[clipTexId]); 13572 // set logic op for clip 13573 gl.doClipUnion = false; 13574 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.JustCleared) { 13575 // it is cleared to zero, we can just draw a path 13576 glDisable(GL_COLOR_LOGIC_OP); 13577 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized; 13578 } else { 13579 glEnable(GL_COLOR_LOGIC_OP); 13580 final switch (clipmode) { 13581 case NVGClipMode.None: assert(0, "wtf?!"); 13582 case NVGClipMode.Union: glLogicOp(GL_CLEAR); gl.doClipUnion = true; break; // use `GL_CLEAR` to avoid adding another shader mode 13583 case NVGClipMode.Or: glLogicOp(GL_COPY); break; // GL_OR 13584 case NVGClipMode.Xor: glLogicOp(GL_XOR); break; 13585 case NVGClipMode.Sub: glLogicOp(GL_CLEAR); break; 13586 case NVGClipMode.Replace: glLogicOp(GL_COPY); break; 13587 } 13588 } 13589 // set affine matrix 13590 glUniform4fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TMat], 1, gl.lastAffine.mat.ptr); 13591 glnvg__checkError(gl, "affine 0"); 13592 glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TTr], 1, gl.lastAffine.mat.ptr+4); 13593 glnvg__checkError(gl, "affine 1"); 13594 // setup common OpenGL parameters 13595 glDisable(GL_BLEND); 13596 glDisable(GL_CULL_FACE); 13597 glEnable(GL_STENCIL_TEST); 13598 glnvg__stencilMask(gl, 0xff); 13599 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13600 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 13601 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13602 } 13603 13604 void glnvg__renderViewport (void* uptr, int width, int height) nothrow @trusted @nogc { 13605 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13606 gl.inFrame = true; 13607 gl.view.ptr[0] = cast(float)width; 13608 gl.view.ptr[1] = cast(float)height; 13609 // kill FBOs if we need to create new ones (flushing will recreate 'em if necessary) 13610 if (width != gl.fboWidth || height != gl.fboHeight) { 13611 glnvg__killFBOs(gl); 13612 gl.fboWidth = width; 13613 gl.fboHeight = height; 13614 } 13615 gl.msp = 1; 13616 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13617 // texture cleanup 13618 import core.atomic : atomicLoad; 13619 if (atomicLoad(gl.mustCleanTextures)) { 13620 try { 13621 import core.thread : Thread; 13622 static if (__VERSION__ < 2076) { 13623 DGNoThrowNoGC(() { 13624 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13625 })(); 13626 } else { 13627 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13628 } 13629 { 13630 GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow; 13631 gl.mustCleanTextures = false; 13632 foreach (immutable tidx, ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) { 13633 // no need to use atomic ops here, as we're locked 13634 if (tex.rc == 0 && tex.tex != 0 && tex.id == 0) { 13635 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** cleaned up texture with glid=%u\n", tex.tex); }} 13636 import core.stdc.string : memset; 13637 if ((tex.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tex.tex); 13638 memset(&tex, 0, tex.sizeof); 13639 tex.nextfree = gl.freetexid; 13640 gl.freetexid = cast(int)tidx; 13641 } 13642 } 13643 } 13644 } catch (Exception e) {} 13645 } 13646 } 13647 13648 void glnvg__fill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13649 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13650 int npaths = call.pathCount; 13651 13652 if (call.clipmode == NVGClipMode.None) { 13653 // Draw shapes 13654 glEnable(GL_STENCIL_TEST); 13655 glnvg__stencilMask(gl, 0xffU); 13656 glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xffU); 13657 13658 glnvg__setUniforms(gl, call.uniformOffset, 0); 13659 glnvg__checkError(gl, "fill simple"); 13660 13661 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13662 if (call.evenOdd) { 13663 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT); 13664 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT); 13665 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); 13666 } else { 13667 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 13668 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 13669 } 13670 glDisable(GL_CULL_FACE); 13671 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13672 glEnable(GL_CULL_FACE); 13673 13674 // Draw anti-aliased pixels 13675 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13676 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image); 13677 glnvg__checkError(gl, "fill fill"); 13678 13679 if (gl.flags&NVGContextFlag.Antialias) { 13680 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xffU); 13681 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13682 // Draw fringes 13683 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13684 } 13685 13686 // Draw fill 13687 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xffU); 13688 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13689 if (call.evenOdd) { 13690 glDisable(GL_CULL_FACE); 13691 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount); 13692 //foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13693 glEnable(GL_CULL_FACE); 13694 } else { 13695 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount); 13696 } 13697 13698 glDisable(GL_STENCIL_TEST); 13699 } else { 13700 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); // this activates our FBO 13701 glnvg__checkError(gl, "fillclip simple"); 13702 glnvg__stencilFunc(gl, GL_ALWAYS, 0x00, 0xffU); 13703 if (call.evenOdd) { 13704 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT); 13705 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT); 13706 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); 13707 } else { 13708 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 13709 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 13710 } 13711 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13712 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13713 } 13714 } 13715 13716 void glnvg__convexFill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13717 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13718 int npaths = call.pathCount; 13719 13720 if (call.clipmode == NVGClipMode.None) { 13721 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13722 glnvg__checkError(gl, "convex fill"); 13723 if (call.evenOdd) glDisable(GL_CULL_FACE); 13724 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13725 if (gl.flags&NVGContextFlag.Antialias) { 13726 // Draw fringes 13727 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13728 } 13729 if (call.evenOdd) glEnable(GL_CULL_FACE); 13730 } else { 13731 glnvg__setClipUniforms(gl, call.uniformOffset, call.clipmode); // this activates our FBO 13732 glnvg__checkError(gl, "clip convex fill"); 13733 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13734 if (gl.flags&NVGContextFlag.Antialias) { 13735 // Draw fringes 13736 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13737 } 13738 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13739 } 13740 } 13741 13742 void glnvg__stroke (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13743 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13744 int npaths = call.pathCount; 13745 13746 if (call.clipmode == NVGClipMode.None) { 13747 if (gl.flags&NVGContextFlag.StencilStrokes) { 13748 glEnable(GL_STENCIL_TEST); 13749 glnvg__stencilMask(gl, 0xff); 13750 13751 // Fill the stroke base without overlap 13752 glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff); 13753 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 13754 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image); 13755 glnvg__checkError(gl, "stroke fill 0"); 13756 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13757 13758 // Draw anti-aliased pixels. 13759 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13760 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13761 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13762 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13763 13764 // Clear stencil buffer. 13765 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13766 glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff); 13767 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13768 glnvg__checkError(gl, "stroke fill 1"); 13769 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13770 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13771 13772 glDisable(GL_STENCIL_TEST); 13773 13774 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f); 13775 } else { 13776 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13777 glnvg__checkError(gl, "stroke fill"); 13778 // Draw Strokes 13779 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13780 } 13781 } else { 13782 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); 13783 glnvg__checkError(gl, "stroke fill 0"); 13784 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13785 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13786 } 13787 } 13788 13789 void glnvg__triangles (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13790 if (call.clipmode == NVGClipMode.None) { 13791 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13792 glnvg__checkError(gl, "triangles fill"); 13793 glDrawArrays(GL_TRIANGLES, call.triangleOffset, call.triangleCount); 13794 } else { 13795 //TODO(?): use texture as mask? 13796 } 13797 } 13798 13799 void glnvg__affine (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13800 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, call.affine.mat.ptr); 13801 glnvg__checkError(gl, "affine"); 13802 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, call.affine.mat.ptr+4); 13803 glnvg__checkError(gl, "affine"); 13804 //glnvg__setUniforms(gl, call.uniformOffset, call.image); 13805 } 13806 13807 void glnvg__renderCancelInternal (GLNVGcontext* gl, bool clearTextures) nothrow @trusted @nogc { 13808 scope(exit) gl.inFrame = false; 13809 if (clearTextures && gl.inFrame) { 13810 try { 13811 import core.thread : Thread; 13812 static if (__VERSION__ < 2076) { 13813 DGNoThrowNoGC(() { 13814 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13815 })(); 13816 } else { 13817 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13818 } 13819 } catch (Exception e) {} 13820 foreach (ref GLNVGcall c; gl.calls[0..gl.ncalls]) if (c.image > 0) glnvg__deleteTexture(gl, c.image); 13821 } 13822 gl.nverts = 0; 13823 gl.npaths = 0; 13824 gl.ncalls = 0; 13825 gl.nuniforms = 0; 13826 gl.msp = 1; 13827 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13828 } 13829 13830 void glnvg__renderCancel (void* uptr) nothrow @trusted @nogc { 13831 glnvg__renderCancelInternal(cast(GLNVGcontext*)uptr, true); 13832 } 13833 13834 GLenum glnvg_convertBlendFuncFactor (NVGBlendFactor factor) pure nothrow @trusted @nogc { 13835 if (factor == NVGBlendFactor.Zero) return GL_ZERO; 13836 if (factor == NVGBlendFactor.One) return GL_ONE; 13837 if (factor == NVGBlendFactor.SrcColor) return GL_SRC_COLOR; 13838 if (factor == NVGBlendFactor.OneMinusSrcColor) return GL_ONE_MINUS_SRC_COLOR; 13839 if (factor == NVGBlendFactor.DstColor) return GL_DST_COLOR; 13840 if (factor == NVGBlendFactor.OneMinusDstColor) return GL_ONE_MINUS_DST_COLOR; 13841 if (factor == NVGBlendFactor.SrcAlpha) return GL_SRC_ALPHA; 13842 if (factor == NVGBlendFactor.OneMinusSrcAlpha) return GL_ONE_MINUS_SRC_ALPHA; 13843 if (factor == NVGBlendFactor.DstAlpha) return GL_DST_ALPHA; 13844 if (factor == NVGBlendFactor.OneMinusDstAlpha) return GL_ONE_MINUS_DST_ALPHA; 13845 if (factor == NVGBlendFactor.SrcAlphaSaturate) return GL_SRC_ALPHA_SATURATE; 13846 return GL_INVALID_ENUM; 13847 } 13848 13849 GLNVGblend glnvg__buildBlendFunc (NVGCompositeOperationState op) pure nothrow @trusted @nogc { 13850 GLNVGblend res; 13851 res.simple = op.simple; 13852 res.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB); 13853 res.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB); 13854 res.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha); 13855 res.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha); 13856 if (res.simple) { 13857 if (res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) { 13858 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM; 13859 } 13860 } else { 13861 if (res.srcRGB == GL_INVALID_ENUM || res.dstRGB == GL_INVALID_ENUM || res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) { 13862 res.simple = true; 13863 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM; 13864 } 13865 } 13866 return res; 13867 } 13868 13869 void glnvg__blendCompositeOperation() (GLNVGcontext* gl, const scope auto ref GLNVGblend op) nothrow @trusted @nogc { 13870 //glBlendFuncSeparate(glnvg_convertBlendFuncFactor(op.srcRGB), glnvg_convertBlendFuncFactor(op.dstRGB), glnvg_convertBlendFuncFactor(op.srcAlpha), glnvg_convertBlendFuncFactor(op.dstAlpha)); 13871 static if (NANOVG_GL_USE_STATE_FILTER) { 13872 if (gl.blendFunc.simple == op.simple) { 13873 if (op.simple) { 13874 if (gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return; 13875 } else { 13876 if (gl.blendFunc.srcRGB == op.srcRGB && gl.blendFunc.dstRGB == op.dstRGB && gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return; 13877 } 13878 } 13879 gl.blendFunc = op; 13880 } 13881 if (op.simple) { 13882 if (op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) { 13883 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13884 } else { 13885 glBlendFunc(op.srcAlpha, op.dstAlpha); 13886 } 13887 } else { 13888 if (op.srcRGB == GL_INVALID_ENUM || op.dstRGB == GL_INVALID_ENUM || op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) { 13889 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13890 } else { 13891 glBlendFuncSeparate(op.srcRGB, op.dstRGB, op.srcAlpha, op.dstAlpha); 13892 } 13893 } 13894 } 13895 13896 void glnvg__renderSetAffine (void* uptr, const scope ref NVGMatrix mat) nothrow @trusted @nogc { 13897 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13898 GLNVGcall* call; 13899 // if last operation was GLNVG_AFFINE, simply replace the matrix 13900 if (gl.ncalls > 0 && gl.calls[gl.ncalls-1].type == GLNVG_AFFINE) { 13901 call = &gl.calls[gl.ncalls-1]; 13902 } else { 13903 call = glnvg__allocCall(gl); 13904 if (call is null) return; 13905 call.type = GLNVG_AFFINE; 13906 } 13907 call.affine.mat.ptr[0..6] = mat.mat.ptr[0..6]; 13908 } 13909 13910 version(nanovega_debug_clipping) public __gshared bool nanovegaClipDebugDump = false; 13911 13912 void glnvg__renderFlush (void* uptr) nothrow @trusted @nogc { 13913 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13914 if (!gl.inFrame) assert(0, "NanoVega: internal driver error"); 13915 try { 13916 import core.thread : Thread; 13917 static if (__VERSION__ < 2076) { 13918 DGNoThrowNoGC(() { 13919 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13920 })(); 13921 } else { 13922 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13923 } 13924 } catch (Exception e) {} 13925 scope(exit) gl.inFrame = false; 13926 13927 glnvg__resetError!true(gl); 13928 { 13929 int vv = 0; 13930 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &vv); 13931 if (glGetError() || vv < 0) vv = 0; 13932 gl.mainFBO = cast(uint)vv; 13933 } 13934 13935 enum ShaderType { None, Fill, Clip } 13936 auto lastShader = ShaderType.None; 13937 if (gl.ncalls > 0) { 13938 gl.msp = 1; 13939 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13940 13941 // Setup require GL state. 13942 glUseProgram(gl.shader.prog); 13943 13944 glActiveTexture(GL_TEXTURE1); 13945 glBindTexture(GL_TEXTURE_2D, 0); 13946 glActiveTexture(GL_TEXTURE0); 13947 glnvg__resetFBOClipTextureCache(gl); 13948 13949 //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13950 static if (NANOVG_GL_USE_STATE_FILTER) { 13951 gl.blendFunc.simple = true; 13952 gl.blendFunc.srcRGB = gl.blendFunc.dstRGB = gl.blendFunc.srcAlpha = gl.blendFunc.dstAlpha = GL_INVALID_ENUM; 13953 } 13954 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // just in case 13955 glEnable(GL_CULL_FACE); 13956 glCullFace(GL_BACK); 13957 glFrontFace(GL_CCW); 13958 glEnable(GL_BLEND); 13959 glDisable(GL_DEPTH_TEST); 13960 glDisable(GL_SCISSOR_TEST); 13961 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13962 glStencilMask(0xffffffff); 13963 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13964 glStencilFunc(GL_ALWAYS, 0, 0xffffffff); 13965 glActiveTexture(GL_TEXTURE0); 13966 glBindTexture(GL_TEXTURE_2D, 0); 13967 static if (NANOVG_GL_USE_STATE_FILTER) { 13968 gl.boundTexture = 0; 13969 gl.stencilMask = 0xffffffff; 13970 gl.stencilFunc = GL_ALWAYS; 13971 gl.stencilFuncRef = 0; 13972 gl.stencilFuncMask = 0xffffffff; 13973 } 13974 glnvg__checkError(gl, "OpenGL setup"); 13975 13976 // Upload vertex data 13977 glBindBuffer(GL_ARRAY_BUFFER, gl.vertBuf); 13978 // ensure that there's space for at least 4 vertices in case we need to draw a quad (e. g. framebuffer copy) 13979 glBufferData(GL_ARRAY_BUFFER, (gl.nverts < 4 ? 4 : gl.nverts) * NVGVertex.sizeof, gl.verts, GL_STREAM_DRAW); 13980 glEnableVertexAttribArray(0); 13981 glEnableVertexAttribArray(1); 13982 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)cast(usize)0); 13983 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)(0+2*float.sizeof)); 13984 glnvg__checkError(gl, "vertex data uploading"); 13985 13986 // Set view and texture just once per frame. 13987 glUniform1i(gl.shader.loc[GLNVGuniformLoc.Tex], 0); 13988 if (gl.shader.loc[GLNVGuniformLoc.ClipTex] != -1) { 13989 //{ import core.stdc.stdio; printf("%d\n", gl.shader.loc[GLNVGuniformLoc.ClipTex]); } 13990 glUniform1i(gl.shader.loc[GLNVGuniformLoc.ClipTex], 1); 13991 } 13992 if (gl.shader.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shader.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 13993 glnvg__checkError(gl, "render shader setup"); 13994 13995 // Reset affine transformations. 13996 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, NVGMatrix.IdentityMat.ptr); 13997 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, NVGMatrix.IdentityMat.ptr+4); 13998 glnvg__checkError(gl, "affine setup"); 13999 14000 // set clip shaders params 14001 // fill 14002 glUseProgram(gl.shaderFillFBO.prog); 14003 glnvg__checkError(gl, "clip shaders setup (fill 0)"); 14004 if (gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 14005 glnvg__checkError(gl, "clip shaders setup (fill 1)"); 14006 // copy 14007 glUseProgram(gl.shaderCopyFBO.prog); 14008 glnvg__checkError(gl, "clip shaders setup (copy 0)"); 14009 if (gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 14010 glnvg__checkError(gl, "clip shaders setup (copy 1)"); 14011 //glUniform1i(gl.shaderFillFBO.loc[GLNVGuniformLoc.Tex], 0); 14012 glUniform1i(gl.shaderCopyFBO.loc[GLNVGuniformLoc.Tex], 0); 14013 glnvg__checkError(gl, "clip shaders setup (copy 2)"); 14014 // restore render shader 14015 glUseProgram(gl.shader.prog); 14016 14017 //{ 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]); } 14018 14019 gl.lastAffine.identity; 14020 14021 foreach (int i; 0..gl.ncalls) { 14022 GLNVGcall* call = &gl.calls[i]; 14023 switch (call.type) { 14024 case GLNVG_FILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__fill(gl, call); break; 14025 case GLNVG_CONVEXFILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__convexFill(gl, call); break; 14026 case GLNVG_STROKE: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__stroke(gl, call); break; 14027 case GLNVG_TRIANGLES: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__triangles(gl, call); break; 14028 case GLNVG_AFFINE: gl.lastAffine = call.affine; glnvg__affine(gl, call); break; 14029 // clip region management 14030 case GLNVG_PUSHCLIP: 14031 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]); } 14032 if (gl.msp >= gl.maskStack.length) assert(0, "NanoVega: mask stack overflow in OpenGL backend"); 14033 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.DontMask) { 14034 gl.maskStack.ptr[gl.msp++] = GLMaskState.DontMask; 14035 } else { 14036 gl.maskStack.ptr[gl.msp++] = GLMaskState.Uninitialized; 14037 } 14038 // no need to reset FBO cache here, as nothing was changed 14039 break; 14040 case GLNVG_POPCLIP: 14041 if (gl.msp <= 1) assert(0, "NanoVega: mask stack underflow in OpenGL backend"); 14042 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]); } 14043 --gl.msp; 14044 assert(gl.msp > 0); 14045 //{ import core.stdc.stdio; printf("popped; new msp is %d; state is %d\n", gl.msp, gl.maskStack.ptr[gl.msp]); } 14046 // check popped item 14047 final switch (gl.maskStack.ptr[gl.msp]) { 14048 case GLMaskState.DontMask: 14049 // if last FBO was "don't mask", reset cache if current is not "don't mask" 14050 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) { 14051 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14052 glnvg__resetFBOClipTextureCache(gl); 14053 } 14054 break; 14055 case GLMaskState.Uninitialized: 14056 // if last FBO texture was uninitialized, it means that nothing was changed, 14057 // so we can keep using cached FBO 14058 break; 14059 case GLMaskState.Initialized: 14060 // if last FBO was initialized, it means that something was definitely changed 14061 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14062 glnvg__resetFBOClipTextureCache(gl); 14063 break; 14064 case GLMaskState.JustCleared: assert(0, "NanoVega: internal FBO stack error"); 14065 } 14066 break; 14067 case GLNVG_RESETCLIP: 14068 // mark current mask as "don't mask" 14069 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]); } 14070 if (gl.msp > 0) { 14071 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) { 14072 gl.maskStack.ptr[gl.msp-1] = GLMaskState.DontMask; 14073 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14074 glnvg__resetFBOClipTextureCache(gl); 14075 } 14076 } 14077 break; 14078 case GLNVG_CLIP_DDUMP_ON: 14079 version(nanovega_debug_clipping) nanovegaClipDebugDump = true; 14080 break; 14081 case GLNVG_CLIP_DDUMP_OFF: 14082 version(nanovega_debug_clipping) nanovegaClipDebugDump = false; 14083 break; 14084 case GLNVG_NONE: break; 14085 default: 14086 { 14087 import core.stdc.stdio; stderr.fprintf("NanoVega FATAL: invalid command in OpenGL backend: %d\n", call.type); 14088 } 14089 assert(0, "NanoVega: invalid command in OpenGL backend (fatal internal error)"); 14090 } 14091 // and free texture, why not 14092 glnvg__deleteTexture(gl, call.image); 14093 } 14094 14095 glDisableVertexAttribArray(0); 14096 glDisableVertexAttribArray(1); 14097 glDisable(GL_CULL_FACE); 14098 glBindBuffer(GL_ARRAY_BUFFER, 0); 14099 glUseProgram(0); 14100 glnvg__bindTexture(gl, 0); 14101 } 14102 14103 // this will do all necessary cleanup 14104 glnvg__renderCancelInternal(gl, false); // no need to clear textures 14105 } 14106 14107 int glnvg__maxVertCount (const(NVGpath)* paths, int npaths) nothrow @trusted @nogc { 14108 int count = 0; 14109 foreach (int i; 0..npaths) { 14110 count += paths[i].nfill; 14111 count += paths[i].nstroke; 14112 } 14113 return count; 14114 } 14115 14116 GLNVGcall* glnvg__allocCall (GLNVGcontext* gl) nothrow @trusted @nogc { 14117 GLNVGcall* ret = null; 14118 if (gl.ncalls+1 > gl.ccalls) { 14119 GLNVGcall* calls; 14120 int ccalls = glnvg__maxi(gl.ncalls+1, 128)+gl.ccalls/2; // 1.5x Overallocate 14121 calls = cast(GLNVGcall*)realloc(gl.calls, GLNVGcall.sizeof*ccalls); 14122 if (calls is null) return null; 14123 gl.calls = calls; 14124 gl.ccalls = ccalls; 14125 } 14126 ret = &gl.calls[gl.ncalls++]; 14127 memset(ret, 0, GLNVGcall.sizeof); 14128 return ret; 14129 } 14130 14131 int glnvg__allocPaths (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14132 int ret = 0; 14133 if (gl.npaths+n > gl.cpaths) { 14134 GLNVGpath* paths; 14135 int cpaths = glnvg__maxi(gl.npaths+n, 128)+gl.cpaths/2; // 1.5x Overallocate 14136 paths = cast(GLNVGpath*)realloc(gl.paths, GLNVGpath.sizeof*cpaths); 14137 if (paths is null) return -1; 14138 gl.paths = paths; 14139 gl.cpaths = cpaths; 14140 } 14141 ret = gl.npaths; 14142 gl.npaths += n; 14143 return ret; 14144 } 14145 14146 int glnvg__allocVerts (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14147 int ret = 0; 14148 if (gl.nverts+n > gl.cverts) { 14149 NVGVertex* verts; 14150 int cverts = glnvg__maxi(gl.nverts+n, 4096)+gl.cverts/2; // 1.5x Overallocate 14151 verts = cast(NVGVertex*)realloc(gl.verts, NVGVertex.sizeof*cverts); 14152 if (verts is null) return -1; 14153 gl.verts = verts; 14154 gl.cverts = cverts; 14155 } 14156 ret = gl.nverts; 14157 gl.nverts += n; 14158 return ret; 14159 } 14160 14161 int glnvg__allocFragUniforms (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14162 int ret = 0, structSize = gl.fragSize; 14163 if (gl.nuniforms+n > gl.cuniforms) { 14164 ubyte* uniforms; 14165 int cuniforms = glnvg__maxi(gl.nuniforms+n, 128)+gl.cuniforms/2; // 1.5x Overallocate 14166 uniforms = cast(ubyte*)realloc(gl.uniforms, structSize*cuniforms); 14167 if (uniforms is null) return -1; 14168 gl.uniforms = uniforms; 14169 gl.cuniforms = cuniforms; 14170 } 14171 ret = gl.nuniforms*structSize; 14172 gl.nuniforms += n; 14173 return ret; 14174 } 14175 14176 GLNVGfragUniforms* nvg__fragUniformPtr (GLNVGcontext* gl, int i) nothrow @trusted @nogc { 14177 return cast(GLNVGfragUniforms*)&gl.uniforms[i]; 14178 } 14179 14180 void glnvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc { 14181 vtx.x = x; 14182 vtx.y = y; 14183 vtx.u = u; 14184 vtx.v = v; 14185 } 14186 14187 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 { 14188 if (npaths < 1) return; 14189 14190 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14191 GLNVGcall* call = glnvg__allocCall(gl); 14192 NVGVertex* quad; 14193 GLNVGfragUniforms* frag; 14194 int maxverts, offset; 14195 14196 if (call is null) return; 14197 14198 call.type = GLNVG_FILL; 14199 call.evenOdd = evenOdd; 14200 call.clipmode = clipmode; 14201 //if (clipmode != NVGClipMode.None) { import core.stdc.stdio; printf("CLIP!\n"); } 14202 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14203 call.triangleCount = 4; 14204 call.pathOffset = glnvg__allocPaths(gl, npaths); 14205 if (call.pathOffset == -1) goto error; 14206 call.pathCount = npaths; 14207 call.image = paint.image.id; 14208 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14209 14210 if (npaths == 1 && paths[0].convex) { 14211 call.type = GLNVG_CONVEXFILL; 14212 call.triangleCount = 0; // Bounding box fill quad not needed for convex fill 14213 } 14214 14215 // Allocate vertices for all the paths. 14216 maxverts = glnvg__maxVertCount(paths, npaths)+call.triangleCount; 14217 offset = glnvg__allocVerts(gl, maxverts); 14218 if (offset == -1) goto error; 14219 14220 foreach (int i; 0..npaths) { 14221 GLNVGpath* copy = &gl.paths[call.pathOffset+i]; 14222 const(NVGpath)* path = &paths[i]; 14223 memset(copy, 0, GLNVGpath.sizeof); 14224 if (path.nfill > 0) { 14225 copy.fillOffset = offset; 14226 copy.fillCount = path.nfill; 14227 memcpy(&gl.verts[offset], path.fill, NVGVertex.sizeof*path.nfill); 14228 offset += path.nfill; 14229 } 14230 if (path.nstroke > 0) { 14231 copy.strokeOffset = offset; 14232 copy.strokeCount = path.nstroke; 14233 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke); 14234 offset += path.nstroke; 14235 } 14236 } 14237 14238 // Setup uniforms for draw calls 14239 if (call.type == GLNVG_FILL) { 14240 import core.stdc.string : memcpy; 14241 // Quad 14242 call.triangleOffset = offset; 14243 quad = &gl.verts[call.triangleOffset]; 14244 glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f); 14245 glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f); 14246 glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f); 14247 glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f); 14248 // Get uniform 14249 call.uniformOffset = glnvg__allocFragUniforms(gl, 2); 14250 if (call.uniformOffset == -1) goto error; 14251 // Simple shader for stencil 14252 frag = nvg__fragUniformPtr(gl, call.uniformOffset); 14253 memset(frag, 0, (*frag).sizeof); 14254 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f); 14255 memcpy(nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), frag, (*frag).sizeof); 14256 frag.strokeThr = -1.0f; 14257 frag.type = NSVG_SHADER_SIMPLE; 14258 // Fill shader 14259 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, fringe, fringe, -1.0f); 14260 } else { 14261 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14262 if (call.uniformOffset == -1) goto error; 14263 // Fill shader 14264 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f); 14265 } 14266 14267 return; 14268 14269 error: 14270 // We get here if call alloc was ok, but something else is not. 14271 // Roll back the last call to prevent drawing it. 14272 if (gl.ncalls > 0) --gl.ncalls; 14273 } 14274 14275 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 { 14276 if (npaths < 1) return; 14277 14278 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14279 GLNVGcall* call = glnvg__allocCall(gl); 14280 int maxverts, offset; 14281 14282 if (call is null) return; 14283 14284 call.type = GLNVG_STROKE; 14285 call.clipmode = clipmode; 14286 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14287 call.pathOffset = glnvg__allocPaths(gl, npaths); 14288 if (call.pathOffset == -1) goto error; 14289 call.pathCount = npaths; 14290 call.image = paint.image.id; 14291 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14292 14293 // Allocate vertices for all the paths. 14294 maxverts = glnvg__maxVertCount(paths, npaths); 14295 offset = glnvg__allocVerts(gl, maxverts); 14296 if (offset == -1) goto error; 14297 14298 foreach (int i; 0..npaths) { 14299 GLNVGpath* copy = &gl.paths[call.pathOffset+i]; 14300 const(NVGpath)* path = &paths[i]; 14301 memset(copy, 0, GLNVGpath.sizeof); 14302 if (path.nstroke) { 14303 copy.strokeOffset = offset; 14304 copy.strokeCount = path.nstroke; 14305 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke); 14306 offset += path.nstroke; 14307 } 14308 } 14309 14310 if (gl.flags&NVGContextFlag.StencilStrokes) { 14311 // Fill shader 14312 call.uniformOffset = glnvg__allocFragUniforms(gl, 2); 14313 if (call.uniformOffset == -1) goto error; 14314 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); 14315 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f); 14316 } else { 14317 // Fill shader 14318 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14319 if (call.uniformOffset == -1) goto error; 14320 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); 14321 } 14322 14323 return; 14324 14325 error: 14326 // We get here if call alloc was ok, but something else is not. 14327 // Roll back the last call to prevent drawing it. 14328 if (gl.ncalls > 0) --gl.ncalls; 14329 } 14330 14331 void glnvg__renderTriangles (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc { 14332 if (nverts < 1) return; 14333 14334 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14335 GLNVGcall* call = glnvg__allocCall(gl); 14336 GLNVGfragUniforms* frag; 14337 14338 if (call is null) return; 14339 14340 call.type = GLNVG_TRIANGLES; 14341 call.clipmode = clipmode; 14342 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14343 call.image = paint.image.id; 14344 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14345 14346 // Allocate vertices for all the paths. 14347 call.triangleOffset = glnvg__allocVerts(gl, nverts); 14348 if (call.triangleOffset == -1) goto error; 14349 call.triangleCount = nverts; 14350 14351 memcpy(&gl.verts[call.triangleOffset], verts, NVGVertex.sizeof*nverts); 14352 14353 // Fill shader 14354 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14355 if (call.uniformOffset == -1) goto error; 14356 frag = nvg__fragUniformPtr(gl, call.uniformOffset); 14357 glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f); 14358 frag.type = NSVG_SHADER_IMG; 14359 14360 return; 14361 14362 error: 14363 // We get here if call alloc was ok, but something else is not. 14364 // Roll back the last call to prevent drawing it. 14365 if (gl.ncalls > 0) --gl.ncalls; 14366 } 14367 14368 void glnvg__renderDelete (void* uptr) nothrow @trusted @nogc { 14369 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14370 if (gl is null) return; 14371 14372 glnvg__killFBOs(gl); 14373 glnvg__deleteShader(&gl.shader); 14374 glnvg__deleteShader(&gl.shaderFillFBO); 14375 glnvg__deleteShader(&gl.shaderCopyFBO); 14376 14377 if (gl.vertBuf != 0) glDeleteBuffers(1, &gl.vertBuf); 14378 14379 foreach (ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) { 14380 if (tex.id != 0 && (tex.flags&NVGImageFlag.NoDelete) == 0) { 14381 assert(tex.tex != 0); 14382 glDeleteTextures(1, &tex.tex); 14383 } 14384 } 14385 free(gl.textures); 14386 14387 free(gl.paths); 14388 free(gl.verts); 14389 free(gl.uniforms); 14390 free(gl.calls); 14391 14392 free(gl); 14393 } 14394 14395 14396 /** Creates NanoVega contexts for OpenGL2+. 14397 * 14398 * Specify creation flags as additional arguments, like this: 14399 * `nvgCreateContext(NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes);` 14400 * 14401 * If you won't specify any flags, defaults will be used: 14402 * `[NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes]`. 14403 * 14404 * Group: context_management 14405 */ 14406 public NVGContext nvgCreateContext (const(NVGContextFlag)[] flagList...) nothrow @trusted @nogc { 14407 version(aliced) { 14408 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA; 14409 } else { 14410 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes; 14411 } 14412 uint flags = 0; 14413 if (flagList.length != 0) { 14414 foreach (immutable flg; flagList) flags |= (flg != NVGContextFlag.Default ? flg : DefaultFlags); 14415 } else { 14416 flags = DefaultFlags; 14417 } 14418 NVGparams params = void; 14419 NVGContext ctx = null; 14420 version(nanovg_builtin_opengl_bindings) nanovgInitOpenGL(); // why not? 14421 version(nanovg_bindbc_opengl_bindings) nanovgInitOpenGL(); 14422 GLNVGcontext* gl = cast(GLNVGcontext*)malloc(GLNVGcontext.sizeof); 14423 if (gl is null) goto error; 14424 memset(gl, 0, GLNVGcontext.sizeof); 14425 14426 memset(¶ms, 0, params.sizeof); 14427 params.renderCreate = &glnvg__renderCreate; 14428 params.renderCreateTexture = &glnvg__renderCreateTexture; 14429 params.renderTextureIncRef = &glnvg__renderTextureIncRef; 14430 params.renderDeleteTexture = &glnvg__renderDeleteTexture; 14431 params.renderUpdateTexture = &glnvg__renderUpdateTexture; 14432 params.renderGetTextureSize = &glnvg__renderGetTextureSize; 14433 params.renderViewport = &glnvg__renderViewport; 14434 params.renderCancel = &glnvg__renderCancel; 14435 params.renderFlush = &glnvg__renderFlush; 14436 params.renderPushClip = &glnvg__renderPushClip; 14437 params.renderPopClip = &glnvg__renderPopClip; 14438 params.renderResetClip = &glnvg__renderResetClip; 14439 params.renderFill = &glnvg__renderFill; 14440 params.renderStroke = &glnvg__renderStroke; 14441 params.renderTriangles = &glnvg__renderTriangles; 14442 params.renderSetAffine = &glnvg__renderSetAffine; 14443 params.renderDelete = &glnvg__renderDelete; 14444 params.userPtr = gl; 14445 params.edgeAntiAlias = (flags&NVGContextFlag.Antialias ? true : false); 14446 if (flags&(NVGContextFlag.FontAA|NVGContextFlag.FontNoAA)) { 14447 params.fontAA = (flags&NVGContextFlag.FontNoAA ? NVG_INVERT_FONT_AA : !NVG_INVERT_FONT_AA); 14448 } else { 14449 params.fontAA = NVG_INVERT_FONT_AA; 14450 } 14451 14452 gl.flags = flags; 14453 gl.freetexid = -1; 14454 14455 ctx = createInternal(¶ms); 14456 if (ctx is null) goto error; 14457 14458 static if (__VERSION__ < 2076) { 14459 DGNoThrowNoGC(() { import core.thread; gl.mainTID = Thread.getThis.id; })(); 14460 } else { 14461 try { import core.thread; gl.mainTID = Thread.getThis.id; } catch (Exception e) {} 14462 } 14463 14464 return ctx; 14465 14466 error: 14467 // 'gl' is freed by nvgDeleteInternal. 14468 if (ctx !is null) ctx.deleteInternal(); 14469 return null; 14470 } 14471 14472 /// Using OpenGL texture id creates GLNVGtexture and return its id. 14473 /// Group: images 14474 public int glCreateImageFromHandleGL2 (NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc { 14475 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr; 14476 GLNVGtexture* tex = glnvg__allocTexture(gl); 14477 14478 if (tex is null) return 0; 14479 14480 tex.type = NVGtexture.RGBA; 14481 tex.tex = textureId; 14482 tex.flags = imageFlags; 14483 tex.width = w; 14484 tex.height = h; 14485 14486 return tex.id; 14487 } 14488 14489 /// Create NVGImage from OpenGL texture id. 14490 /// Group: images 14491 public NVGImage glCreateImageFromOpenGLTexture(NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc { 14492 auto id = glCreateImageFromHandleGL2(ctx, textureId, w, h, imageFlags); 14493 14494 NVGImage res; 14495 if (id > 0) { 14496 res.id = id; 14497 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); } 14498 res.ctx = ctx; 14499 ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount 14500 } 14501 return res; 14502 } 14503 14504 /// Returns OpenGL texture id for NanoVega image. 14505 /// Group: images 14506 public GLuint glImageHandleGL2 (NVGContext ctx, int image) nothrow @trusted @nogc { 14507 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr; 14508 GLNVGtexture* tex = glnvg__findTexture(gl, image); 14509 return tex.tex; 14510 } 14511 14512 14513 // ////////////////////////////////////////////////////////////////////////// // 14514 private: 14515 14516 static if (NanoVegaHasFontConfig) { 14517 version(nanovg_builtin_fontconfig_bindings) { 14518 pragma(lib, "fontconfig"); 14519 14520 private extern(C) nothrow @trusted @nogc { 14521 enum FC_FILE = "file"; /* String */ 14522 alias FcBool = int; 14523 alias FcChar8 = char; 14524 struct FcConfig; 14525 struct FcPattern; 14526 alias FcMatchKind = int; 14527 enum : FcMatchKind { 14528 FcMatchPattern, 14529 FcMatchFont, 14530 FcMatchScan 14531 } 14532 alias FcResult = int; 14533 enum : FcResult { 14534 FcResultMatch, 14535 FcResultNoMatch, 14536 FcResultTypeMismatch, 14537 FcResultNoId, 14538 FcResultOutOfMemory 14539 } 14540 FcBool FcInit (); 14541 FcBool FcConfigSubstituteWithPat (FcConfig* config, FcPattern* p, FcPattern* p_pat, FcMatchKind kind); 14542 void FcDefaultSubstitute (FcPattern* pattern); 14543 FcBool FcConfigSubstitute (FcConfig* config, FcPattern* p, FcMatchKind kind); 14544 FcPattern* FcFontMatch (FcConfig* config, FcPattern* p, FcResult* result); 14545 FcPattern* FcNameParse (const(FcChar8)* name); 14546 void FcPatternDestroy (FcPattern* p); 14547 FcResult FcPatternGetString (const(FcPattern)* p, const(char)* object, int n, FcChar8** s); 14548 } 14549 } 14550 14551 __gshared bool fontconfigAvailable = false; 14552 // initialize fontconfig 14553 shared static this () { 14554 if (FcInit()) { 14555 fontconfigAvailable = true; 14556 } else { 14557 import core.stdc.stdio : stderr, fprintf; 14558 stderr.fprintf("***NanoVega WARNING: cannot init fontconfig!\n"); 14559 } 14560 } 14561 } 14562 14563 14564 // ////////////////////////////////////////////////////////////////////////// // 14565 public enum BaphometDims = 512.0f; // baphomet icon is 512x512 ([0..511]) 14566 14567 private static immutable ubyte[7641] baphometPath = [ 14568 0x01,0x04,0x06,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x08,0xa0,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43, 14569 0x00,0x80,0xff,0x43,0xa2,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x08,0x00,0x80,0xff, 14570 0x43,0x7a,0x89,0xe5,0x42,0xa0,0x1d,0xc6,0x43,0x00,0x00,0x00,0x00,0x30,0x89,0x7f,0x43,0x00,0x00,0x00, 14571 0x00,0x08,0x7a,0x89,0xe5,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7a,0x89,0xe5,0x42,0x00,0x00, 14572 0x00,0x00,0x30,0x89,0x7f,0x43,0x08,0x00,0x00,0x00,0x00,0xa2,0x1d,0xc6,0x43,0x7a,0x89,0xe5,0x42,0x00, 14573 0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x09,0x06,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd, 14574 0x43,0x08,0x16,0x68,0xb3,0x43,0x72,0x87,0xdd,0x43,0x71,0x87,0xdd,0x43,0x17,0x68,0xb3,0x43,0x71,0x87, 14575 0xdd,0x43,0x30,0x89,0x7f,0x43,0x08,0x71,0x87,0xdd,0x43,0xd2,0x2f,0x18,0x43,0x16,0x68,0xb3,0x43,0x35, 14576 0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x35,0xe2,0x87,0x42,0x08,0xd1,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42, 14577 0x35,0xe2,0x87,0x42,0xd2,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x08,0x35,0xe2,0x87, 14578 0x42,0x17,0x68,0xb3,0x43,0xd1,0x2f,0x18,0x43,0x72,0x87,0xdd,0x43,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd, 14579 0x43,0x09,0x06,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x07,0xa4,0x3f,0x7f,0x43,0x0b,0x86,0xdc,0x43, 14580 0x07,0x6c,0xb9,0xb2,0x43,0xe8,0xd1,0xca,0x42,0x07,0x6e,0x4d,0xa0,0x42,0xa9,0x10,0x9c,0x43,0x07,0xb7, 14581 0x40,0xd7,0x43,0xa9,0x10,0x9c,0x43,0x07,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x09,0x06,0x98,0x42, 14582 0x74,0x43,0xb1,0x8d,0x68,0x43,0x08,0xd7,0x24,0x79,0x43,0xba,0x83,0x6e,0x43,0xa9,0x16,0x7c,0x43,0x56, 14583 0xa1,0x76,0x43,0x74,0x2a,0x7d,0x43,0x44,0x73,0x80,0x43,0x08,0x55,0xd1,0x7e,0x43,0xe3,0xea,0x76,0x43, 14584 0xbc,0x18,0x81,0x43,0x7f,0xa8,0x6e,0x43,0x8f,0x0a,0x84,0x43,0x02,0xfc,0x68,0x43,0x09,0x06,0x92,0x29, 14585 0x8d,0x43,0x73,0xc3,0x67,0x43,0x08,0xa4,0xd9,0x8e,0x43,0xf2,0xa6,0x7a,0x43,0x8f,0x22,0x88,0x43,0x75, 14586 0x2a,0x7d,0x43,0x42,0x7f,0x82,0x43,0x08,0xc8,0x88,0x43,0x09,0x06,0xc1,0x79,0x74,0x43,0x50,0x64,0x89, 14587 0x43,0x08,0x68,0x2d,0x72,0x43,0xee,0x21,0x81,0x43,0xcd,0x97,0x55,0x43,0xe6,0xf1,0x7b,0x43,0x91,0xec, 14588 0x5d,0x43,0xa8,0xc7,0x6a,0x43,0x09,0x06,0xfa,0xa5,0x52,0x43,0x60,0x97,0x7c,0x43,0x08,0x19,0xff,0x50, 14589 0x43,0xe9,0x6e,0x8a,0x43,0xb0,0xbd,0x70,0x43,0x4c,0x51,0x82,0x43,0x04,0xeb,0x69,0x43,0x66,0x0f,0x8e, 14590 0x43,0x09,0x06,0x17,0xbf,0x71,0x43,0x2c,0x58,0x94,0x43,0x08,0x1c,0x96,0x6e,0x43,0x61,0x68,0x99,0x43, 14591 0x2d,0x3a,0x6e,0x43,0xc8,0x81,0x9e,0x43,0xb7,0x9b,0x72,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0x30,0xdb, 14592 0x82,0x43,0xdb,0xe9,0x93,0x43,0x08,0x11,0x82,0x84,0x43,0x61,0x68,0x99,0x43,0xe8,0x4a,0x84,0x43,0x8e, 14593 0xa6,0x9e,0x43,0x42,0x7f,0x82,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0xc4,0x02,0x85,0x43,0xd1,0x0b,0x92, 14594 0x43,0x08,0xd6,0xb2,0x86,0x43,0x34,0x1e,0x92,0x43,0x4f,0x58,0x87,0x43,0xa4,0xf1,0x92,0x43,0x03,0xd9, 14595 0x87,0x43,0x7b,0xc6,0x94,0x43,0x09,0x06,0x87,0x3e,0x64,0x43,0x31,0x3b,0x93,0x43,0x08,0x3b,0xbf,0x64, 14596 0x43,0x6f,0xf9,0x91,0x43,0x96,0x0b,0x67,0x43,0xc5,0x4a,0x91,0x43,0xcf,0xfe,0x6a,0x43,0x31,0x2f,0x91, 14597 0x43,0x09,0x06,0x16,0x74,0xb5,0x43,0x08,0xec,0x8e,0x43,0x08,0x1b,0x4b,0xb2,0x43,0xee,0x5d,0x8b,0x43, 14598 0x48,0x4d,0xad,0x43,0x12,0xa6,0x8a,0x43,0xf3,0xd7,0xa7,0x43,0x74,0xb8,0x8a,0x43,0x08,0x8c,0xb2,0xa0, 14599 0x43,0xcd,0xf8,0x8a,0x43,0x68,0x46,0x9b,0x43,0x79,0x8f,0x87,0x43,0x49,0xc9,0x96,0x43,0xe9,0x3e,0x82, 14600 0x43,0x08,0x60,0x5c,0x97,0x43,0xa1,0xde,0x8b,0x43,0x4e,0xa0,0x93,0x43,0x31,0x3b,0x93,0x43,0x9f,0xea, 14601 0x8d,0x43,0x27,0x8d,0x99,0x43,0x08,0x07,0xe0,0x8c,0x43,0x06,0x34,0x9b,0x43,0x38,0xe9,0x8c,0x43,0x46, 14602 0x0a,0x9e,0x43,0x3d,0xcc,0x8b,0x43,0xb2,0x06,0xa2,0x43,0x08,0xf1,0x40,0x8a,0x43,0xb0,0x12,0xa4,0x43, 14603 0x39,0xd1,0x88,0x43,0x76,0x43,0xa6,0x43,0xfa,0x06,0x88,0x43,0xa4,0x75,0xa9,0x43,0x08,0x19,0x6c,0x88, 14604 0x43,0x9f,0x9e,0xac,0x43,0x66,0xeb,0x87,0x43,0x44,0x76,0xb0,0x43,0x6b,0xce,0x86,0x43,0x3b,0xbc,0xb4, 14605 0x43,0x08,0xa9,0x8c,0x85,0x43,0x06,0xd0,0xb5,0x43,0xfa,0xee,0x83,0x43,0x74,0xa3,0xb6,0x43,0x3d,0x90, 14606 0x81,0x43,0x31,0xf6,0xb6,0x43,0x08,0x9d,0x61,0x7d,0x43,0xee,0x48,0xb7,0x43,0x3b,0x1f,0x75,0x43,0xcf, 14607 0xe3,0xb6,0x43,0xee,0x6f,0x6d,0x43,0x68,0xe2,0xb5,0x43,0x08,0xd4,0xed,0x6b,0x43,0x87,0x2f,0xb2,0x43, 14608 0x0e,0xc9,0x6b,0x43,0xa7,0x7c,0xae,0x43,0x98,0xfa,0x67,0x43,0xab,0x53,0xab,0x43,0x08,0x25,0x2c,0x64, 14609 0x43,0x33,0xa2,0xa8,0x43,0x40,0x96,0x61,0x43,0xc3,0xc2,0xa5,0x43,0x64,0xde,0x60,0x43,0xfa,0xa2,0xa2, 14610 0x43,0x08,0xb0,0x5d,0x60,0x43,0x06,0x4c,0x9f,0x43,0x9a,0xca,0x5f,0x43,0x38,0x3d,0x9b,0x43,0x3b,0x8f, 14611 0x5c,0x43,0x85,0xb0,0x98,0x43,0x08,0x42,0x36,0x51,0x43,0x3d,0xf0,0x91,0x43,0xcd,0x4f,0x49,0x43,0xdb, 14612 0xb9,0x8b,0x43,0xe0,0xdb,0x44,0x43,0x42,0x8b,0x84,0x43,0x08,0x7e,0xc9,0x44,0x43,0x8a,0x57,0x8d,0x43, 14613 0xbc,0x6c,0x0f,0x43,0x23,0x62,0x8e,0x43,0xf5,0x17,0x07,0x43,0xc5,0x3e,0x8f,0x43,0x09,0x06,0xe0,0xea, 14614 0x76,0x43,0xab,0xef,0xc5,0x43,0x08,0x12,0x00,0x79,0x43,0xab,0xcb,0xbf,0x43,0x79,0xb9,0x6d,0x43,0x7e, 14615 0x8d,0xba,0x43,0xee,0x6f,0x6d,0x43,0x98,0xeb,0xb5,0x43,0x08,0xe0,0x02,0x7b,0x43,0x5f,0x1c,0xb8,0x43, 14616 0x85,0x2c,0x82,0x43,0xe9,0x65,0xb8,0x43,0xd6,0xb2,0x86,0x43,0xc6,0x05,0xb5,0x43,0x08,0x03,0xcd,0x85, 14617 0x43,0x5a,0x39,0xb9,0x43,0xe4,0x4f,0x81,0x43,0xdb,0xd4,0xbf,0x43,0xdf,0x6c,0x82,0x43,0xbc,0x93,0xc5, 14618 0x43,0x09,0x06,0xf0,0xd0,0x22,0x43,0x5d,0x19,0x08,0x43,0x08,0xbc,0xab,0x49,0x43,0x4a,0x35,0x29,0x43, 14619 0xcb,0xf7,0x65,0x43,0xce,0x37,0x45,0x43,0x0e,0x99,0x63,0x43,0x67,0xc6,0x5c,0x43,0x09,0x06,0x05,0x94, 14620 0xab,0x43,0xc2,0x13,0x04,0x43,0x08,0x9f,0x26,0x98,0x43,0x11,0x42,0x25,0x43,0x97,0x00,0x8a,0x43,0x32, 14621 0x32,0x41,0x43,0xf5,0x2f,0x8b,0x43,0xc7,0xc0,0x58,0x43,0x09,0x06,0x8f,0x85,0x48,0x43,0xe0,0xa8,0x8c, 14622 0x43,0x08,0x55,0xaa,0x48,0x43,0xe0,0xa8,0x8c,0x43,0x6b,0x3d,0x49,0x43,0xc1,0x43,0x8c,0x43,0x31,0x62, 14623 0x49,0x43,0xc1,0x43,0x8c,0x43,0x08,0x2f,0xe3,0x2f,0x43,0xad,0xe7,0x98,0x43,0xff,0x0d,0x0d,0x43,0xad, 14624 0xf3,0x9a,0x43,0xf0,0xaf,0xcc,0x42,0x74,0x00,0x97,0x43,0x08,0xbb,0xa2,0xf7,0x42,0x93,0x4d,0x93,0x43, 14625 0x5e,0x19,0x08,0x43,0x5a,0x2a,0x87,0x43,0x23,0x6e,0x10,0x43,0x42,0x97,0x86,0x43,0x08,0xca,0xe8,0x33, 14626 0x43,0x1b,0x3c,0x80,0x43,0x80,0xe8,0x4d,0x43,0xda,0xf4,0x70,0x43,0xae,0x0e,0x4f,0x43,0x2b,0x1b,0x65, 14627 0x43,0x08,0x66,0x96,0x54,0x43,0xa3,0xe1,0x3b,0x43,0x4e,0xc4,0x19,0x43,0xa0,0x1a,0x16,0x43,0x10,0xe2, 14628 0x14,0x43,0x26,0x14,0xe0,0x42,0x08,0x5c,0x91,0x1c,0x43,0xcb,0x27,0xee,0x42,0xa9,0x40,0x24,0x43,0x71, 14629 0x3b,0xfc,0x42,0xf3,0xef,0x2b,0x43,0x8b,0x27,0x05,0x43,0x08,0xe2,0x4b,0x2c,0x43,0x48,0x86,0x07,0x43, 14630 0x79,0x62,0x2f,0x43,0x05,0xe5,0x09,0x43,0x55,0x32,0x34,0x43,0xa0,0xd2,0x09,0x43,0x08,0x74,0xa3,0x36, 14631 0x43,0x3a,0xd1,0x08,0x43,0x7e,0x81,0x38,0x43,0x09,0xd4,0x0a,0x43,0x0d,0xba,0x39,0x43,0xa0,0xea,0x0d, 14632 0x43,0x08,0x6f,0xe4,0x3d,0x43,0x43,0xc7,0x0e,0x43,0xd6,0xe5,0x3e,0x43,0xc4,0x4a,0x11,0x43,0x55,0x7a, 14633 0x40,0x43,0x59,0x72,0x13,0x43,0x08,0x55,0x92,0x44,0x43,0xbf,0x73,0x14,0x43,0x23,0x95,0x46,0x43,0xa5, 14634 0x09,0x17,0x43,0xe0,0xf3,0x48,0x43,0xfe,0x55,0x19,0x43,0x08,0xcd,0x4f,0x49,0x43,0xaa,0x10,0x1c,0x43, 14635 0x61,0x77,0x4b,0x43,0xfe,0x6d,0x1d,0x43,0x80,0xe8,0x4d,0x43,0x2b,0x94,0x1e,0x43,0x08,0x58,0xc9,0x51, 14636 0x43,0x41,0x27,0x1f,0x43,0x9b,0x82,0x53,0x43,0x35,0x72,0x20,0x43,0x53,0xf2,0x54,0x43,0x88,0xcf,0x21, 14637 0x43,0x08,0x7b,0x29,0x55,0x43,0xe8,0x0a,0x25,0x43,0xb2,0x2d,0x58,0x43,0xef,0xe8,0x26,0x43,0x9b,0xb2, 14638 0x5b,0x43,0xd0,0x8f,0x28,0x43,0x08,0x5f,0xef,0x5f,0x43,0xeb,0x11,0x2a,0x43,0xfd,0xdc,0x5f,0x43,0x6e, 14639 0x95,0x2c,0x43,0x3b,0xa7,0x60,0x43,0x2b,0xf4,0x2e,0x43,0x08,0x06,0xbb,0x61,0x43,0xfd,0xe5,0x31,0x43, 14640 0xe7,0x61,0x63,0x43,0xef,0x30,0x33,0x43,0x53,0x52,0x65,0x43,0xa3,0xb1,0x33,0x43,0x08,0x12,0xa0,0x68, 14641 0x43,0x7f,0x69,0x34,0x43,0x40,0xc6,0x69,0x43,0x64,0xff,0x36,0x43,0x7e,0x90,0x6a,0x43,0x71,0xcc,0x39, 14642 0x43,0x08,0xbc,0x5a,0x6b,0x43,0x51,0x73,0x3b,0x43,0xc1,0x49,0x6c,0x43,0xa5,0xd0,0x3c,0x43,0xe0,0xba, 14643 0x6e,0x43,0xb8,0x74,0x3c,0x43,0x08,0x6b,0x1c,0x73,0x43,0x13,0xc1,0x3e,0x43,0x40,0xf6,0x71,0x43,0xce, 14644 0x1f,0x41,0x43,0x55,0x89,0x72,0x43,0x8d,0x7e,0x43,0x43,0x08,0x68,0x2d,0x72,0x43,0x89,0xae,0x4b,0x43, 14645 0xc1,0x79,0x74,0x43,0xcb,0x78,0x4c,0x43,0x55,0xa1,0x76,0x43,0x5b,0xb1,0x4d,0x43,0x08,0xa2,0x38,0x7a, 14646 0x43,0xd1,0x56,0x4e,0x43,0x85,0xb6,0x78,0x43,0xb1,0x15,0x54,0x43,0x83,0xc7,0x77,0x43,0x89,0x0e,0x5c, 14647 0x43,0x08,0xcf,0x46,0x77,0x43,0x0f,0x81,0x5f,0x43,0x1a,0xde,0x7a,0x43,0xce,0xc7,0x5d,0x43,0x42,0x73, 14648 0x80,0x43,0x99,0xc3,0x5a,0x43,0x08,0x85,0x2c,0x82,0x43,0xf6,0xe6,0x59,0x43,0x81,0x3d,0x81,0x43,0x16, 14649 0x10,0x50,0x43,0xd6,0x8e,0x80,0x43,0x5b,0x99,0x49,0x43,0x08,0xc4,0xea,0x80,0x43,0x22,0x95,0x46,0x43, 14650 0xfa,0xe2,0x81,0x43,0xda,0xec,0x43,0x43,0x78,0x77,0x83,0x43,0xe4,0xb2,0x41,0x43,0x08,0x8a,0x27,0x85, 14651 0x43,0x86,0x77,0x3e,0x43,0x0c,0x9f,0x85,0x43,0x07,0xf4,0x3b,0x43,0x8f,0x16,0x86,0x43,0xe6,0x82,0x39, 14652 0x43,0x08,0x85,0x44,0x86,0x43,0x37,0xd9,0x35,0x43,0x1e,0x4f,0x87,0x43,0xe1,0x7b,0x34,0x43,0xdf,0x90, 14653 0x88,0x43,0xb6,0x55,0x33,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0xe5,0x31,0x43,0xfa,0x12,0x8a,0x43,0xbf, 14654 0x03,0x2d,0x43,0x19,0x78,0x8a,0x43,0x45,0x5e,0x2c,0x43,0x08,0x03,0xf1,0x8b,0x43,0xac,0x47,0x29,0x43, 14655 0x2f,0x17,0x8d,0x43,0x45,0x46,0x28,0x43,0xc8,0x21,0x8e,0x43,0x30,0xb3,0x27,0x43,0x08,0xa9,0xc8,0x8f, 14656 0x43,0xef,0xe8,0x26,0x43,0xbf,0x5b,0x90,0x43,0x5b,0xc1,0x24,0x43,0x10,0xca,0x90,0x43,0xa0,0x62,0x22, 14657 0x43,0x08,0x26,0x5d,0x91,0x43,0xbb,0xcc,0x1f,0x43,0xf0,0x70,0x92,0x43,0x78,0x13,0x1e,0x43,0x77,0xd7, 14658 0x93,0x43,0x73,0x24,0x1d,0x43,0x08,0x65,0x3f,0x96,0x43,0xce,0x58,0x1b,0x43,0xbe,0x7f,0x96,0x43,0xbf, 14659 0x8b,0x18,0x43,0x60,0x5c,0x97,0x43,0xb6,0xad,0x16,0x43,0x08,0xba,0xa8,0x99,0x43,0x78,0xcb,0x11,0x43, 14660 0x49,0xe1,0x9a,0x43,0x78,0xcb,0x11,0x43,0x01,0x51,0x9c,0x43,0x73,0xdc,0x10,0x43,0x08,0x72,0x24,0x9d, 14661 0x43,0xd2,0xff,0x0f,0x43,0x1c,0xd3,0x9d,0x43,0x07,0xec,0x0e,0x43,0xeb,0xc9,0x9d,0x43,0xe8,0x7a,0x0c, 14662 0x43,0x08,0x60,0x80,0x9d,0x43,0xd7,0xbe,0x08,0x43,0x4d,0xe8,0x9f,0x43,0x86,0x50,0x08,0x43,0x25,0xbd, 14663 0xa1,0x43,0x5b,0x2a,0x07,0x43,0x08,0x99,0x7f,0xa3,0x43,0xc9,0xf1,0x05,0x43,0x48,0x1d,0xa5,0x43,0x86, 14664 0x38,0x04,0x43,0x6c,0x71,0xa6,0x43,0x18,0x59,0x01,0x43,0x08,0x32,0x96,0xa6,0x43,0x6e,0x64,0xff,0x42, 14665 0x48,0x29,0xa7,0x43,0xed,0xcf,0xfd,0x42,0x5f,0xbc,0xa7,0x43,0x71,0x3b,0xfc,0x42,0x08,0xf3,0xe3,0xa9, 14666 0x43,0xf7,0x7d,0xf7,0x42,0xd8,0x6d,0xaa,0x43,0x45,0xe5,0xf2,0x42,0x48,0x41,0xab,0x43,0xcb,0x27,0xee, 14667 0x42,0x08,0x24,0xf9,0xab,0x43,0x52,0x6a,0xe9,0x42,0xee,0x0c,0xad,0x43,0x4c,0x8c,0xe7,0x42,0x1b,0x33, 14668 0xae,0x43,0xcc,0xf7,0xe5,0x42,0x08,0xaa,0x6b,0xaf,0x43,0xe8,0x61,0xe3,0x42,0x90,0xf5,0xaf,0x43,0xc9, 14669 0xf0,0xe0,0x42,0xe0,0x63,0xb0,0x43,0xe5,0x5a,0xde,0x42,0x08,0xaa,0x83,0xb3,0x43,0x29,0x2d,0x09,0x43, 14670 0x6a,0xfe,0x8e,0x43,0xb8,0x74,0x3c,0x43,0xd5,0x06,0x95,0x43,0xe6,0x79,0x67,0x43,0x08,0x2f,0x53,0x97, 14671 0x43,0xe9,0xb0,0x74,0x43,0xa8,0x28,0xa0,0x43,0x43,0xfd,0x76,0x43,0x83,0x28,0xad,0x43,0x17,0x59,0x81, 14672 0x43,0x08,0x3d,0xe7,0xbf,0x43,0x4b,0x8d,0x8c,0x43,0xae,0x96,0xba,0x43,0x66,0x27,0x92,0x43,0x15,0xe0, 14673 0xc7,0x43,0x6f,0x11,0x96,0x43,0x08,0x7e,0x5d,0xb2,0x43,0xdb,0x01,0x98,0x43,0x9e,0x56,0xa0,0x43,0x80, 14674 0xc1,0x97,0x43,0x69,0x2e,0x97,0x43,0x31,0x17,0x8d,0x43,0x09,0x06,0xab,0xa7,0x39,0x43,0x67,0x0f,0x0e, 14675 0x43,0x08,0xdb,0xbc,0x3b,0x43,0xe8,0x92,0x10,0x43,0xb5,0x85,0x3b,0x43,0x97,0x3c,0x14,0x43,0xab,0xa7, 14676 0x39,0x43,0x0c,0x0b,0x18,0x43,0x09,0x06,0xca,0x30,0x40,0x43,0x30,0x3b,0x13,0x43,0x08,0x17,0xc8,0x43, 14677 0x43,0xa5,0x09,0x17,0x43,0x7e,0xc9,0x44,0x43,0x1a,0xd8,0x1a,0x43,0x9d,0x22,0x43,0x43,0x8d,0xa6,0x1e, 14678 0x43,0x09,0x06,0xc8,0x78,0x4c,0x43,0xed,0xc9,0x1d,0x43,0x08,0x0b,0x32,0x4e,0x43,0x22,0xce,0x20,0x43, 14679 0x23,0xc5,0x4e,0x43,0x58,0xd2,0x23,0x43,0x0b,0x32,0x4e,0x43,0x2b,0xc4,0x26,0x43,0x09,0x06,0xec,0x08, 14680 0x58,0x43,0xc7,0xb1,0x26,0x43,0x08,0x02,0x9c,0x58,0x43,0xef,0x00,0x2b,0x43,0xd9,0x64,0x58,0x43,0x02, 14681 0xbd,0x2e,0x43,0x10,0x51,0x57,0x43,0x37,0xc1,0x31,0x43,0x09,0x06,0xcb,0xdf,0x61,0x43,0x4a,0x65,0x31, 14682 0x43,0x08,0xbe,0x2a,0x63,0x43,0xbd,0x33,0x35,0x43,0x32,0xe1,0x62,0x43,0x56,0x4a,0x38,0x43,0xde,0x83, 14683 0x61,0x43,0x3c,0xe0,0x3a,0x43,0x09,0x06,0x1c,0x7e,0x6a,0x43,0x5b,0x39,0x39,0x43,0x08,0x31,0x11,0x6b, 14684 0x43,0x0c,0xd2,0x3d,0x43,0x1c,0x7e,0x6a,0x43,0x13,0xd9,0x42,0x43,0xd9,0xc4,0x68,0x43,0xcb,0x60,0x48, 14685 0x43,0x09,0x06,0xe5,0xc1,0x73,0x43,0x16,0xf8,0x4b,0x43,0x08,0xa6,0xf7,0x72,0x43,0xb1,0xfd,0x4f,0x43, 14686 0x3b,0x07,0x71,0x43,0x4a,0x14,0x53,0x43,0xa2,0xf0,0x6d,0x43,0x7c,0x29,0x55,0x43,0x09,0x06,0x00,0x8d, 14687 0xa6,0x43,0xef,0x21,0x01,0x43,0x08,0x52,0xfb,0xa6,0x43,0xce,0xc8,0x02,0x43,0xe6,0x16,0xa7,0x43,0x51, 14688 0x4c,0x05,0x43,0x3b,0x68,0xa6,0x43,0x4c,0x75,0x08,0x43,0x09,0x06,0xde,0x20,0xa1,0x43,0x86,0x50,0x08, 14689 0x43,0x08,0xd4,0x4e,0xa1,0x43,0xd3,0xe7,0x0b,0x43,0xb5,0xe9,0xa0,0x43,0x59,0x5a,0x0f,0x43,0xba,0xcc, 14690 0x9f,0x43,0x54,0x83,0x12,0x43,0x09,0x06,0x77,0xfb,0x99,0x43,0x6c,0x16,0x13,0x43,0x08,0xde,0xfc,0x9a, 14691 0x43,0x4a,0xbd,0x14,0x43,0x06,0x34,0x9b,0x43,0xfe,0x55,0x19,0x43,0x13,0xe9,0x99,0x43,0x41,0x27,0x1f, 14692 0x43,0x09,0x06,0x46,0xce,0x93,0x43,0x26,0xa5,0x1d,0x43,0x08,0xe7,0xaa,0x94,0x43,0xbb,0xcc,0x1f,0x43, 14693 0x18,0xb4,0x94,0x43,0xa8,0x40,0x24,0x43,0xe2,0xbb,0x93,0x43,0x21,0xfe,0x28,0x43,0x09,0x06,0xb1,0x8e, 14694 0x8d,0x43,0xa8,0x58,0x28,0x43,0x08,0x19,0x90,0x8e,0x43,0x54,0x13,0x2b,0x43,0xa4,0xd9,0x8e,0x43,0x84, 14695 0x40,0x31,0x43,0x46,0xaa,0x8d,0x43,0x29,0x24,0x37,0x43,0x09,0x06,0xd6,0xbe,0x88,0x43,0xef,0x30,0x33, 14696 0x43,0x08,0x0c,0xb7,0x89,0x43,0x0e,0xa2,0x35,0x43,0xc0,0x37,0x8a,0x43,0x7a,0xaa,0x3b,0x43,0xbb,0x48, 14697 0x89,0x43,0xbb,0x7b,0x41,0x43,0x09,0x06,0x3a,0xad,0x82,0x43,0xc4,0x59,0x43,0x43,0x08,0xd2,0xb7,0x83, 14698 0x43,0x2b,0x5b,0x44,0x43,0x35,0xd6,0x85,0x43,0x48,0xf5,0x49,0x43,0x42,0x97,0x86,0x43,0xc4,0xa1,0x4f, 14699 0x43,0x09,0x06,0x9c,0xb3,0x80,0x43,0x48,0x55,0x5a,0x43,0x08,0xff,0xc5,0x80,0x43,0x09,0x73,0x55,0x43, 14700 0x93,0xe1,0x80,0x43,0x0f,0x39,0x53,0x43,0xf1,0xbe,0x7e,0x43,0x18,0xe7,0x4c,0x43,0x09,0x06,0xe0,0x02, 14701 0x7b,0x43,0x92,0xec,0x5d,0x43,0x08,0x09,0x3a,0x7b,0x43,0xf0,0xf7,0x58,0x43,0x09,0x3a,0x7b,0x43,0xe6, 14702 0x31,0x5b,0x43,0xe0,0x02,0x7b,0x43,0xa8,0x4f,0x56,0x43,0x09,0x06,0x39,0x4f,0x7d,0x43,0x3e,0x8f,0x5c, 14703 0x43,0x08,0xe9,0xe0,0x7c,0x43,0x03,0x9c,0x58,0x43,0x1e,0x2b,0x81,0x43,0x7f,0x30,0x5a,0x43,0xff,0x73, 14704 0x7d,0x43,0xf6,0xb6,0x51,0x43,0x09,0x06,0x5c,0xb8,0x52,0x43,0x28,0x21,0x87,0x43,0x08,0xae,0x3e,0x57, 14705 0x43,0x12,0x9a,0x88,0x43,0x23,0xf5,0x56,0x43,0x04,0xf1,0x8b,0x43,0x25,0xfc,0x5b,0x43,0x85,0x74,0x8e, 14706 0x43,0x08,0x2f,0xf2,0x61,0x43,0x8e,0x52,0x90,0x43,0xd9,0xdc,0x6c,0x43,0x85,0x74,0x8e,0x43,0xc6,0x20, 14707 0x69,0x43,0x3d,0xd8,0x8d,0x43,0x08,0x6d,0x8c,0x5a,0x43,0xf5,0x3b,0x8d,0x43,0x3d,0x77,0x58,0x43,0xa1, 14708 0xc6,0x87,0x43,0xf8,0xed,0x5e,0x43,0x5e,0x0d,0x86,0x43,0x09,0x06,0xde,0xcc,0x92,0x43,0xf7,0x17,0x87, 14709 0x43,0x08,0xb6,0x89,0x90,0x43,0xae,0x87,0x88,0x43,0x4a,0xa5,0x90,0x43,0xa1,0xde,0x8b,0x43,0xf9,0x2a, 14710 0x8e,0x43,0x23,0x62,0x8e,0x43,0x08,0xf5,0x2f,0x8b,0x43,0x5c,0x49,0x90,0x43,0x35,0xd6,0x85,0x43,0x8e, 14711 0x46,0x8e,0x43,0x3d,0xb4,0x87,0x43,0x47,0xaa,0x8d,0x43,0x08,0x6a,0xfe,0x8e,0x43,0xff,0x0d,0x8d,0x43, 14712 0xbb,0x6c,0x8f,0x43,0xf7,0x17,0x87,0x43,0x5c,0x31,0x8c,0x43,0xb2,0x5e,0x85,0x43,0x09,0x06,0x60,0x38, 14713 0x91,0x43,0x69,0x5d,0x7a,0x43,0x08,0x34,0x1e,0x92,0x43,0x1e,0x5b,0x89,0x43,0x04,0x63,0x7e,0x43,0x5e, 14714 0x01,0x84,0x43,0x59,0x2a,0x87,0x43,0x0d,0xcf,0x8d,0x43,0x09,0x03,0x04,0x06,0x5a,0x18,0x63,0x43,0x82, 14715 0x79,0x8b,0x43,0x08,0x25,0x2c,0x64,0x43,0x82,0x79,0x8b,0x43,0x2a,0x1b,0x65,0x43,0x9d,0xef,0x8a,0x43, 14716 0x2a,0x1b,0x65,0x43,0xc1,0x37,0x8a,0x43,0x08,0x2a,0x1b,0x65,0x43,0x17,0x89,0x89,0x43,0x25,0x2c,0x64, 14717 0x43,0x31,0xff,0x88,0x43,0x5a,0x18,0x63,0x43,0x31,0xff,0x88,0x43,0x08,0xf3,0x16,0x62,0x43,0x31,0xff, 14718 0x88,0x43,0xee,0x27,0x61,0x43,0x17,0x89,0x89,0x43,0xee,0x27,0x61,0x43,0xc1,0x37,0x8a,0x43,0x08,0xee, 14719 0x27,0x61,0x43,0x9d,0xef,0x8a,0x43,0xf3,0x16,0x62,0x43,0x82,0x79,0x8b,0x43,0x5a,0x18,0x63,0x43,0x82, 14720 0x79,0x8b,0x43,0x09,0x06,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x08,0x34,0xee,0x89,0x43,0x82,0x79, 14721 0x8b,0x43,0x85,0x5c,0x8a,0x43,0x9d,0xef,0x8a,0x43,0x85,0x5c,0x8a,0x43,0xc1,0x37,0x8a,0x43,0x08,0x85, 14722 0x5c,0x8a,0x43,0x17,0x89,0x89,0x43,0x34,0xee,0x89,0x43,0x31,0xff,0x88,0x43,0x4f,0x64,0x89,0x43,0x31, 14723 0xff,0x88,0x43,0x08,0x9c,0xe3,0x88,0x43,0x31,0xff,0x88,0x43,0x19,0x6c,0x88,0x43,0x17,0x89,0x89,0x43, 14724 0x19,0x6c,0x88,0x43,0xc1,0x37,0x8a,0x43,0x08,0x19,0x6c,0x88,0x43,0x9d,0xef,0x8a,0x43,0x9c,0xe3,0x88, 14725 0x43,0x82,0x79,0x8b,0x43,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x09,0x02,0x04,0x06,0x19,0x60,0x86, 14726 0x43,0xec,0xed,0xa3,0x43,0x08,0x35,0xd6,0x85,0x43,0x76,0x43,0xa6,0x43,0x93,0xe1,0x80,0x43,0x57,0x02, 14727 0xac,0x43,0x61,0xd8,0x80,0x43,0x87,0x17,0xae,0x43,0x08,0xa5,0x85,0x80,0x43,0xc3,0xfe,0xaf,0x43,0xce, 14728 0xbc,0x80,0x43,0x83,0x40,0xb1,0x43,0xa5,0x91,0x82,0x43,0x79,0x6e,0xb1,0x43,0x08,0x23,0x26,0x84,0x43, 14729 0x40,0x93,0xb1,0x43,0x30,0xe7,0x84,0x43,0xbe,0x1b,0xb1,0x43,0x11,0x82,0x84,0x43,0xab,0x6b,0xaf,0x43, 14730 0x08,0xb7,0x41,0x84,0x43,0x3b,0x98,0xae,0x43,0xb7,0x41,0x84,0x43,0xc3,0xf2,0xad,0x43,0xa1,0xae,0x83, 14731 0x43,0x83,0x28,0xad,0x43,0x08,0xb2,0x52,0x83,0x43,0x80,0x39,0xac,0x43,0x81,0x49,0x83,0x43,0xf0,0x00, 14732 0xab,0x43,0xe4,0x67,0x85,0x43,0x76,0x4f,0xa8,0x43,0x08,0x9c,0xd7,0x86,0x43,0xd1,0x83,0xa6,0x43,0xec, 14733 0x45,0x87,0x43,0x01,0x75,0xa2,0x43,0x19,0x60,0x86,0x43,0xec,0xed,0xa3,0x43,0x09,0x06,0xd9,0xdc,0x6c, 14734 0x43,0x14,0x25,0xa4,0x43,0x08,0xa2,0xf0,0x6d,0x43,0x9f,0x7a,0xa6,0x43,0x47,0xec,0x77,0x43,0x80,0x39, 14735 0xac,0x43,0xa9,0xfe,0x77,0x43,0xb0,0x4e,0xae,0x43,0x08,0x23,0xa4,0x78,0x43,0xea,0x35,0xb0,0x43,0xd2, 14736 0x35,0x78,0x43,0xab,0x77,0xb1,0x43,0xc1,0x79,0x74,0x43,0xa2,0xa5,0xb1,0x43,0x08,0xc6,0x50,0x71,0x43, 14737 0x68,0xca,0xb1,0x43,0xab,0xce,0x6f,0x43,0xe7,0x52,0xb1,0x43,0xea,0x98,0x70,0x43,0xd4,0xa2,0xaf,0x43, 14738 0x08,0x9d,0x19,0x71,0x43,0x96,0xd8,0xae,0x43,0x9d,0x19,0x71,0x43,0xec,0x29,0xae,0x43,0xca,0x3f,0x72, 14739 0x43,0xab,0x5f,0xad,0x43,0x08,0xa6,0xf7,0x72,0x43,0xa7,0x70,0xac,0x43,0x09,0x0a,0x73,0x43,0x17,0x38, 14740 0xab,0x43,0x44,0xcd,0x6e,0x43,0x9f,0x86,0xa8,0x43,0x08,0xd4,0xed,0x6b,0x43,0xf8,0xba,0xa6,0x43,0x31, 14741 0x11,0x6b,0x43,0x2a,0xac,0xa2,0x43,0xd9,0xdc,0x6c,0x43,0x14,0x25,0xa4,0x43,0x09,0x01,0x05,0x06,0x66, 14742 0x5d,0x7a,0x43,0x74,0xeb,0xc2,0x43,0x08,0x09,0x22,0x77,0x43,0x50,0xbb,0xc7,0x43,0xe9,0xe0,0x7c,0x43, 14743 0xf5,0x86,0xc9,0x43,0x8f,0x94,0x7a,0x43,0xc5,0x95,0xcd,0x43,0x09,0x06,0x08,0x98,0x80,0x43,0x6b,0x19, 14744 0xc3,0x43,0x08,0xb7,0x35,0x82,0x43,0x79,0xf2,0xc7,0x43,0xf1,0xbe,0x7e,0x43,0x1e,0xbe,0xc9,0x43,0x73, 14745 0x7c,0x80,0x43,0xec,0xcc,0xcd,0x43,0x09,0x06,0x28,0xab,0x7d,0x43,0xae,0xde,0xc6,0x43,0x08,0x1e,0xcd, 14746 0x7b,0x43,0x8a,0xa2,0xc9,0x43,0x30,0x89,0x7f,0x43,0x5c,0x94,0xcc,0x43,0x28,0xab,0x7d,0x43,0x42,0x2a, 14747 0xcf,0x43,0x09,0x01,0x05,0x06,0x24,0x14,0xe0,0x42,0xf5,0x77,0x97,0x43,0x08,0xf7,0x1d,0xe7,0x42,0x74, 14748 0x00,0x97,0x43,0x4d,0x93,0xec,0x42,0xdb,0xf5,0x95,0x43,0x29,0x4b,0xed,0x42,0xcd,0x34,0x95,0x43,0x09, 14749 0x06,0x29,0x7b,0xf5,0x42,0x6f,0x1d,0x98,0x43,0x08,0xe4,0xf1,0xfb,0x42,0x61,0x5c,0x97,0x43,0xdb,0x7d, 14750 0x01,0x43,0xb2,0xbe,0x95,0x43,0x55,0x23,0x02,0x43,0xe7,0xaa,0x94,0x43,0x09,0x06,0x98,0xdc,0x03,0x43, 14751 0xbe,0x8b,0x98,0x43,0x08,0x66,0xdf,0x05,0x43,0x47,0xe6,0x97,0x43,0xae,0x87,0x08,0x43,0x98,0x48,0x96, 14752 0x43,0x61,0x08,0x09,0x43,0xd6,0x06,0x95,0x43,0x09,0x06,0x31,0x0b,0x0b,0x43,0x8e,0x82,0x98,0x43,0x08, 14753 0xdb,0xc5,0x0d,0x43,0x80,0xc1,0x97,0x43,0xd6,0xee,0x10,0x43,0xa9,0xec,0x95,0x43,0x79,0xcb,0x11,0x43, 14754 0x55,0x8f,0x94,0x43,0x09,0x06,0xd1,0x2f,0x18,0x43,0xdb,0x01,0x98,0x43,0x08,0xad,0xe7,0x18,0x43,0x38, 14755 0x25,0x97,0x43,0x8a,0x9f,0x19,0x43,0x80,0xb5,0x95,0x43,0xd6,0x1e,0x19,0x43,0xe0,0xd8,0x94,0x43,0x09, 14756 0x06,0x9a,0x5b,0x1d,0x43,0x58,0x8a,0x97,0x43,0x08,0x01,0x5d,0x1e,0x43,0xf1,0x88,0x96,0x43,0x2f,0x83, 14757 0x1f,0x43,0x19,0xb4,0x94,0x43,0x19,0xf0,0x1e,0x43,0x6f,0x05,0x94,0x43,0x09,0x06,0x0b,0x53,0x24,0x43, 14758 0xae,0xdb,0x96,0x43,0x08,0x25,0xd5,0x25,0x43,0x50,0xac,0x95,0x43,0x53,0xfb,0x26,0x43,0x8a,0x7b,0x93, 14759 0x43,0x76,0x43,0x26,0x43,0xb7,0x95,0x92,0x43,0x09,0x06,0x76,0x5b,0x2a,0x43,0x47,0xda,0x95,0x43,0x08, 14760 0xf3,0xef,0x2b,0x43,0x10,0xe2,0x94,0x43,0x6d,0x95,0x2c,0x43,0xae,0xc3,0x92,0x43,0x68,0xa6,0x2b,0x43, 14761 0x47,0xc2,0x91,0x43,0x09,0x06,0x36,0xc1,0x31,0x43,0x2c,0x58,0x94,0x43,0x08,0x8c,0x1e,0x33,0x43,0x31, 14762 0x3b,0x93,0x43,0x79,0x7a,0x33,0x43,0xff,0x25,0x91,0x43,0xd9,0x9d,0x32,0x43,0xc1,0x5b,0x90,0x43,0x09, 14763 0x06,0x25,0x35,0x36,0x43,0x31,0x3b,0x93,0x43,0x08,0x3f,0xb7,0x37,0x43,0xc1,0x67,0x92,0x43,0xe0,0x93, 14764 0x38,0x43,0xae,0xb7,0x90,0x43,0x7e,0x81,0x38,0x43,0x0d,0xdb,0x8f,0x43,0x09,0x06,0xb5,0x85,0x3b,0x43, 14765 0xe4,0xaf,0x91,0x43,0x08,0xcf,0x07,0x3d,0x43,0x9d,0x13,0x91,0x43,0xbc,0x63,0x3d,0x43,0x47,0xb6,0x8f, 14766 0x43,0xe5,0x9a,0x3d,0x43,0x74,0xd0,0x8e,0x43,0x09,0x06,0xae,0xc6,0x42,0x43,0xa4,0xd9,0x8e,0x43,0x08, 14767 0xca,0x48,0x44,0x43,0xfa,0x2a,0x8e,0x43,0xa2,0x11,0x44,0x43,0x9d,0xfb,0x8c,0x43,0x55,0x92,0x44,0x43, 14768 0x0d,0xc3,0x8b,0x43,0x09,0x06,0x39,0x10,0xc3,0x43,0x34,0x36,0x96,0x43,0x08,0x92,0x44,0xc1,0x43,0xe4, 14769 0xc7,0x95,0x43,0x6f,0xf0,0xbf,0x43,0x4b,0xbd,0x94,0x43,0x47,0xb9,0xbf,0x43,0x0b,0xf3,0x93,0x43,0x09, 14770 0x06,0x8f,0x49,0xbe,0x43,0xb7,0xad,0x96,0x43,0x08,0x11,0xb5,0xbc,0x43,0x77,0xe3,0x95,0x43,0x9c,0xf2, 14771 0xba,0x43,0xfa,0x4e,0x94,0x43,0xae,0x96,0xba,0x43,0x31,0x3b,0x93,0x43,0x09,0x06,0xdb,0xb0,0xb9,0x43, 14772 0x10,0xee,0x96,0x43,0x08,0x42,0xa6,0xb8,0x43,0xc8,0x51,0x96,0x43,0x50,0x5b,0xb7,0x43,0x19,0xb4,0x94, 14773 0x43,0xf7,0x1a,0xb7,0x43,0x58,0x72,0x93,0x43,0x09,0x06,0xf2,0x2b,0xb6,0x43,0x10,0xee,0x96,0x43,0x08, 14774 0x9d,0xce,0xb4,0x43,0x04,0x2d,0x96,0x43,0xed,0x30,0xb3,0x43,0x2c,0x58,0x94,0x43,0xce,0xcb,0xb2,0x43, 14775 0xd6,0xfa,0x92,0x43,0x09,0x06,0x5a,0x09,0xb1,0x43,0x19,0xc0,0x96,0x43,0x08,0x6c,0xad,0xb0,0x43,0x77, 14776 0xe3,0x95,0x43,0x7e,0x51,0xb0,0x43,0xc0,0x73,0x94,0x43,0xd8,0x91,0xb0,0x43,0x1e,0x97,0x93,0x43,0x09, 14777 0x06,0x48,0x4d,0xad,0x43,0xbe,0x7f,0x96,0x43,0x08,0x95,0xcc,0xac,0x43,0x58,0x7e,0x95,0x43,0x4d,0x30, 14778 0xac,0x43,0x80,0xa9,0x93,0x43,0xd8,0x79,0xac,0x43,0xd6,0xfa,0x92,0x43,0x09,0x06,0x90,0xd1,0xa9,0x43, 14779 0x14,0xd1,0x95,0x43,0x08,0x83,0x10,0xa9,0x43,0xb7,0xa1,0x94,0x43,0x3b,0x74,0xa8,0x43,0xf1,0x70,0x92, 14780 0x43,0x29,0xd0,0xa8,0x43,0x1e,0x8b,0x91,0x43,0x09,0x06,0x5a,0xcd,0xa6,0x43,0x8a,0x87,0x95,0x43,0x08, 14781 0x1c,0x03,0xa6,0x43,0x23,0x86,0x94,0x43,0x5f,0xb0,0xa5,0x43,0xc1,0x67,0x92,0x43,0xe1,0x27,0xa6,0x43, 14782 0x8a,0x6f,0x91,0x43,0x09,0x06,0xd4,0x5a,0xa3,0x43,0x2c,0x58,0x94,0x43,0x08,0x29,0xac,0xa2,0x43,0x31, 14783 0x3b,0x93,0x43,0x32,0x7e,0xa2,0x43,0xff,0x25,0x91,0x43,0x83,0xec,0xa2,0x43,0x8e,0x52,0x90,0x43,0x09, 14784 0x06,0xf8,0x96,0xa0,0x43,0x1e,0x97,0x93,0x43,0x08,0xeb,0xd5,0x9f,0x43,0x7b,0xba,0x92,0x43,0x99,0x67, 14785 0x9f,0x43,0x9d,0x13,0x91,0x43,0x99,0x67,0x9f,0x43,0xfa,0x36,0x90,0x43,0x09,0x06,0xeb,0xc9,0x9d,0x43, 14786 0xc8,0x39,0x92,0x43,0x08,0xde,0x08,0x9d,0x43,0xb2,0xa6,0x91,0x43,0xe6,0xda,0x9c,0x43,0x2c,0x40,0x90, 14787 0x43,0x52,0xbf,0x9c,0x43,0x5a,0x5a,0x8f,0x43,0x09,0x06,0x37,0x3d,0x9b,0x43,0x85,0x80,0x90,0x43,0x08, 14788 0x2a,0x7c,0x9a,0x43,0xdb,0xd1,0x8f,0x43,0xf0,0xa0,0x9a,0x43,0x7d,0xa2,0x8e,0x43,0x65,0x57,0x9a,0x43, 14789 0xee,0x69,0x8d,0x43,0x09,0x02,0x04,0x06,0x2a,0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x08,0x0d,0x8a,0x31, 14790 0x42,0x9f,0x0e,0x94,0x43,0xf3,0x1f,0x34,0x42,0x3d,0xfc,0x93,0x43,0x63,0xff,0x36,0x42,0xa9,0xe0,0x93, 14791 0x43,0x08,0xb5,0x34,0x5d,0x42,0x0b,0xf3,0x93,0x43,0x6d,0xa4,0x5e,0x42,0x03,0x39,0x98,0x43,0xe7,0x31, 14792 0x5b,0x42,0x93,0x89,0x9d,0x43,0x08,0x02,0x9c,0x58,0x42,0xd4,0x5a,0xa3,0x43,0x38,0x70,0x53,0x42,0x14, 14793 0x49,0xaa,0x43,0xf8,0xed,0x5e,0x42,0x83,0x28,0xad,0x43,0x08,0xea,0x68,0x68,0x42,0x20,0x22,0xaf,0x43, 14794 0x12,0xb8,0x6c,0x42,0xb5,0x49,0xb1,0x43,0x2a,0x4b,0x6d,0x42,0x0d,0x96,0xb3,0x43,0x07,0x2a,0x4b,0x6d, 14795 0x42,0xc6,0x05,0xb5,0x43,0x08,0x87,0x6e,0x6c,0x42,0x68,0xee,0xb7,0x43,0x1c,0x66,0x66,0x42,0x31,0x0e, 14796 0xbb,0x43,0x57,0x11,0x5e,0x42,0x8f,0x49,0xbe,0x43,0x08,0x66,0x96,0x54,0x42,0xb9,0x5c,0xb8,0x43,0x2c, 14797 0x2b,0x3c,0x42,0x68,0xd6,0xb3,0x43,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x07,0x2a,0xf4,0x2e,0x42, 14798 0x61,0xa4,0xa3,0x43,0x08,0x55,0x1a,0x30,0x42,0xf0,0xd0,0xa2,0x43,0xf8,0xf6,0x30,0x42,0xb2,0x06,0xa2, 14799 0x43,0x98,0xd3,0x31,0x42,0xd6,0x4e,0xa1,0x43,0x08,0x1c,0x6f,0x38,0x42,0x2a,0x94,0x9e,0x43,0xc1,0x22, 14800 0x36,0x42,0xf5,0x9b,0x9d,0x43,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x57, 14801 0xa2,0x9b,0x43,0x08,0xab,0x8f,0x35,0x42,0x8a,0xab,0x9b,0x43,0xe9,0x71,0x3a,0x42,0xb2,0xe2,0x9b,0x43, 14802 0xb7,0x74,0x3c,0x42,0x34,0x5a,0x9c,0x43,0x08,0x23,0x7d,0x42,0x42,0x0b,0x2f,0x9e,0x43,0xe5,0x9a,0x3d, 14803 0x42,0x38,0x6d,0xa3,0x43,0x36,0xd9,0x35,0x42,0xf3,0xd7,0xa7,0x43,0x08,0x12,0x61,0x2e,0x42,0xb0,0x42, 14804 0xac,0x43,0x63,0xff,0x36,0x42,0xdd,0x74,0xaf,0x43,0x1e,0xa6,0x45,0x42,0x44,0x82,0xb2,0x43,0x08,0x74, 14805 0x1b,0x4b,0x42,0x79,0x7a,0xb3,0x43,0x10,0x21,0x4f,0x42,0x2a,0x18,0xb5,0x43,0xdb,0x4c,0x54,0x42,0x91, 14806 0x19,0xb6,0x43,0x08,0xee,0x3f,0x65,0x42,0x5f,0x28,0xba,0x43,0xa7,0xaf,0x66,0x42,0xb9,0x50,0xb6,0x43, 14807 0x14,0x58,0x5c,0x42,0xca,0xdc,0xb1,0x43,0x08,0x2c,0x8b,0x4c,0x42,0x4e,0x30,0xac,0x43,0x19,0xcf,0x48, 14808 0x42,0x2a,0xd0,0xa8,0x43,0xbc,0xab,0x49,0x42,0xa9,0x4c,0xa6,0x43,0x08,0x61,0x5f,0x47,0x42,0xfa,0xa2, 14809 0xa2,0x43,0xa7,0xaf,0x66,0x42,0x85,0x98,0x94,0x43,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x07,0x2a, 14810 0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x09,0x06,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x08,0xdc,0xe3, 14811 0xf1,0x41,0xe9,0x9e,0x92,0x43,0xd2,0xe7,0x0b,0x42,0xd6,0x06,0x95,0x43,0x2a,0xf4,0x2e,0x42,0x04,0x21, 14812 0x94,0x43,0x07,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x08,0x87,0x17,0x2e,0x42,0xc3,0x62,0x95,0x43, 14813 0xe7,0x3a,0x2d,0x42,0xf5,0x6b,0x95,0x43,0x44,0x5e,0x2c,0x42,0xf5,0x6b,0x95,0x43,0x08,0xd1,0x47,0x1c, 14814 0x42,0x19,0xc0,0x96,0x43,0x66,0xdf,0x05,0x42,0x38,0x19,0x95,0x43,0x12,0x6a,0x00,0x42,0xb2,0xbe,0x95, 14815 0x43,0x08,0xbb,0x6b,0xea,0x41,0xd6,0x12,0x97,0x43,0x2d,0x82,0xfa,0x41,0x61,0x74,0x9b,0x43,0x7e,0x72, 14816 0x06,0x42,0x8a,0xab,0x9b,0x43,0x08,0xc8,0x39,0x12,0x42,0x4e,0xd0,0x9b,0x43,0x53,0xe3,0x22,0x42,0xc3, 14817 0x86,0x9b,0x43,0x2a,0xf4,0x2e,0x42,0x57,0xa2,0x9b,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43, 14818 0x08,0x01,0xa5,0x2a,0x42,0xa4,0x2d,0x9d,0x43,0x96,0x9c,0x24,0x42,0x06,0x40,0x9d,0x43,0x8a,0xb7,0x1d, 14819 0x42,0x9a,0x5b,0x9d,0x43,0x08,0x6b,0x16,0x13,0x42,0xcd,0x64,0x9d,0x43,0x42,0xc7,0x0e,0x42,0x9a,0x5b, 14820 0x9d,0x43,0x23,0x26,0x04,0x42,0xcd,0x64,0x9d,0x43,0x08,0xe6,0x91,0xeb,0x41,0x38,0x49,0x9d,0x43,0x73, 14821 0x7b,0xdb,0x41,0xf5,0x83,0x99,0x43,0x7f,0x60,0xe2,0x41,0x0b,0x0b,0x98,0x43,0x08,0x7f,0x60,0xe2,0x41, 14822 0xec,0x99,0x95,0x43,0xe3,0x5a,0xde,0x41,0xbe,0x7f,0x96,0x43,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43, 14823 0x07,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x09,0x06,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x08, 14824 0xd4,0x7e,0x29,0x42,0xab,0x6b,0xaf,0x43,0x4e,0x0c,0x26,0x42,0x44,0x6a,0xae,0x43,0x38,0x79,0x25,0x42, 14825 0xd4,0x96,0xad,0x43,0x08,0x25,0xbd,0x21,0x42,0xe2,0x4b,0xac,0x43,0x49,0x35,0x29,0x42,0x9a,0x97,0xa7, 14826 0x43,0x2a,0xf4,0x2e,0x42,0x61,0xa4,0xa3,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x09,0x06, 14827 0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x08,0x86,0x20,0x80,0x43,0x57,0x41,0xe6,0x43,0x7d,0x4e,0x80, 14828 0x43,0x25,0x38,0xe6,0x43,0xa5,0x85,0x80,0x43,0xf3,0x2e,0xe6,0x43,0x08,0x35,0xca,0x83,0x43,0xd4,0xc9, 14829 0xe5,0x43,0x9c,0xd7,0x86,0x43,0x44,0x91,0xe4,0x43,0xd5,0xca,0x8a,0x43,0x91,0x1c,0xe6,0x43,0x08,0x53, 14830 0x5f,0x8c,0x43,0xf8,0x1d,0xe7,0x43,0x2f,0x17,0x8d,0x43,0x4e,0x7b,0xe8,0x43,0x92,0x29,0x8d,0x43,0x2f, 14831 0x22,0xea,0x43,0x07,0x92,0x29,0x8d,0x43,0x44,0xb5,0xea,0x43,0x08,0xfe,0x0d,0x8d,0x43,0x2a,0x4b,0xed, 14832 0x43,0xe3,0x8b,0x8b,0x43,0x55,0x7d,0xf0,0x43,0xec,0x51,0x89,0x43,0x72,0x0b,0xf4,0x43,0x08,0xcd,0xd4, 14833 0x84,0x43,0x9d,0x55,0xfb,0x43,0xc9,0xe5,0x83,0x43,0x74,0x1e,0xfb,0x43,0x73,0x94,0x84,0x43,0x5a,0x90, 14834 0xf7,0x43,0x08,0xe8,0x62,0x88,0x43,0xfd,0x30,0xee,0x43,0x39,0xc5,0x86,0x43,0xdd,0xbf,0xeb,0x43,0x35, 14835 0xbe,0x81,0x43,0x40,0xde,0xed,0x43,0x08,0x4f,0x34,0x81,0x43,0x36,0x0c,0xee,0x43,0x08,0x98,0x80,0x43, 14836 0xfd,0x30,0xee,0x43,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x40,0xec, 14837 0x43,0x08,0x35,0xbe,0x81,0x43,0x06,0xf7,0xeb,0x43,0x15,0x65,0x83,0x43,0x49,0xa4,0xeb,0x43,0x1e,0x43, 14838 0x85,0x43,0xbe,0x5a,0xeb,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0x18,0xea,0x43,0x42,0x97,0x86,0x43,0x5f, 14839 0x67,0xf4,0x43,0xa9,0x98,0x87,0x43,0xd4,0x1d,0xf4,0x43,0x08,0x5c,0x25,0x8a,0x43,0xcf,0x16,0xef,0x43, 14840 0x46,0xaa,0x8d,0x43,0x5a,0x3c,0xe9,0x43,0x19,0x6c,0x88,0x43,0x53,0x5e,0xe7,0x43,0x08,0xc4,0x02,0x85, 14841 0x43,0x96,0x0b,0xe7,0x43,0x85,0x2c,0x82,0x43,0x83,0x67,0xe7,0x43,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7, 14842 0x43,0x07,0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x09,0x06,0xfd,0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43, 14843 0x08,0xfa,0x6c,0x78,0x43,0xd1,0xc2,0xe0,0x43,0x25,0x5c,0x6c,0x43,0x25,0x44,0xe8,0x43,0x1d,0xe5,0x7f, 14844 0x43,0x87,0x4a,0xe6,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,0x43,0x08,0xa6,0x27,0x7b,0x43,0x91, 14845 0x28,0xe8,0x43,0xbc,0xa2,0x77,0x43,0xb0,0x8d,0xe8,0x43,0xc6,0x68,0x75,0x43,0x57,0x4d,0xe8,0x43,0x08, 14846 0xe0,0xd2,0x72,0x43,0xab,0x9e,0xe7,0x43,0x50,0x9a,0x71,0x43,0x2a,0x27,0xe7,0x43,0xea,0x98,0x70,0x43, 14847 0x57,0x35,0xe4,0x43,0x08,0x94,0x3b,0x6f,0x43,0x14,0x7c,0xe2,0x43,0xff,0x13,0x6d,0x43,0x06,0xbb,0xe1, 14848 0x43,0xcf,0xfe,0x6a,0x43,0x06,0xbb,0xe1,0x43,0x08,0x44,0x9d,0x66,0x43,0x77,0x8e,0xe2,0x43,0x3b,0xef, 14849 0x6c,0x43,0x91,0x10,0xe4,0x43,0xfd,0x24,0x6c,0x43,0xb0,0x81,0xe6,0x43,0x08,0x96,0x23,0x6b,0x43,0xee, 14850 0x57,0xe9,0x43,0xca,0x0f,0x6a,0x43,0x5f,0x37,0xec,0x43,0x55,0x71,0x6e,0x43,0x9f,0x01,0xed,0x43,0x08, 14851 0xdb,0xfb,0x75,0x43,0x3b,0xef,0xec,0x43,0x09,0x3a,0x7b,0x43,0xb0,0xa5,0xec,0x43,0x1d,0xe5,0x7f,0x43, 14852 0x91,0x40,0xec,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x08,0xa9,0x16,0x7c,0x43,0xb0,0xb1, 14853 0xee,0x43,0x47,0xec,0x77,0x43,0xd9,0xe8,0xee,0x43,0x1e,0x9d,0x73,0x43,0xcf,0x16,0xef,0x43,0x08,0x0e, 14854 0xc9,0x6b,0x43,0xee,0x7b,0xef,0x43,0x7e,0x90,0x6a,0x43,0xfd,0x30,0xee,0x43,0x01,0xfc,0x68,0x43,0x4e, 14855 0x93,0xec,0x43,0x08,0x31,0xf9,0x66,0x43,0x4e,0x87,0xea,0x43,0x31,0x11,0x6b,0x43,0xd4,0xd5,0xe7,0x43, 14856 0xd9,0xc4,0x68,0x43,0xd4,0xc9,0xe5,0x43,0x08,0xe5,0x79,0x67,0x43,0x77,0x9a,0xe4,0x43,0x44,0x9d,0x66, 14857 0x43,0xab,0x86,0xe3,0x43,0x7e,0x78,0x66,0x43,0x0b,0xaa,0xe2,0x43,0x07,0x7e,0x78,0x66,0x43,0x57,0x29, 14858 0xe2,0x43,0x08,0xa7,0xaf,0x66,0x43,0xbe,0x1e,0xe1,0x43,0x87,0x56,0x68,0x43,0x77,0x82,0xe0,0x43,0xfd, 14859 0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,0x09,0x06,0xc4,0x41,0xbf,0x43,0x85,0xc0,0x72,0x42,0x08,0x73,0xdf, 14860 0xc0,0x43,0xf4,0x76,0x72,0x42,0x97,0x33,0xc2,0x43,0x85,0xc0,0x72,0x42,0xb2,0xb5,0xc3,0x43,0x64,0x56, 14861 0x75,0x42,0x08,0x03,0x24,0xc4,0x43,0x5e,0x7f,0x78,0x42,0xfa,0x51,0xc4,0x43,0x01,0x85,0x7c,0x42,0x5c, 14862 0x64,0xc4,0x43,0xa0,0xb3,0x80,0x42,0x07,0x5c,0x64,0xc4,0x43,0x10,0x93,0x83,0x42,0x08,0xc8,0x48,0xc4, 14863 0x43,0x1c,0x78,0x8a,0x42,0x27,0x6c,0xc3,0x43,0xaf,0xcf,0x94,0x42,0x23,0x7d,0xc2,0x43,0x99,0x9c,0xa4, 14864 0x42,0x08,0x3d,0xe7,0xbf,0x43,0xfb,0xfd,0xb5,0x42,0xb3,0x9d,0xbf,0x43,0x88,0x17,0xae,0x42,0xc4,0x41, 14865 0xbf,0x43,0x69,0x76,0xa3,0x42,0x07,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x08,0x4f,0x8b,0xbf,0x43, 14866 0xed,0x81,0x91,0x42,0xe4,0xa6,0xbf,0x43,0x5d,0x61,0x94,0x42,0xfa,0x39,0xc0,0x43,0x3b,0x49,0x9d,0x42, 14867 0x08,0x2b,0x43,0xc0,0x43,0x28,0xed,0xa9,0x42,0x61,0x3b,0xc1,0x43,0x00,0x9e,0xa5,0x42,0xe4,0xb2,0xc1, 14868 0x43,0x5d,0x91,0x9c,0x42,0x08,0x78,0xce,0xc1,0x43,0xfd,0x36,0x90,0x42,0x22,0x89,0xc4,0x43,0x81,0x72, 14869 0x86,0x42,0xae,0xc6,0xc2,0x43,0xa0,0xb3,0x80,0x42,0x08,0x54,0x86,0xc2,0x43,0x58,0xd1,0x7e,0x42,0x30, 14870 0x32,0xc1,0x43,0xce,0x5e,0x7b,0x42,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x07,0xc4,0x41,0xbf,0x43, 14871 0x85,0xc0,0x72,0x42,0x09,0x06,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x08,0x35,0xfd,0xbb,0x43,0xa4, 14872 0xa1,0x5c,0x42,0x5e,0x34,0xbc,0x43,0x9d,0x2a,0x70,0x42,0x5e,0x40,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08, 14873 0x4c,0x9c,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,0xef,0xbe,0x43,0x0e,0x0a,0x73,0x42,0xc4,0x41,0xbf,0x43, 14874 0x85,0xc0,0x72,0x42,0x07,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x08,0xcd,0x13,0xbf,0x43,0xe8,0xf1, 14875 0x7b,0x42,0xd6,0xe5,0xbe,0x43,0x71,0x3b,0x7c,0x42,0xdf,0xb7,0xbe,0x43,0x71,0x3b,0x7c,0x42,0x08,0x08, 14876 0xe3,0xbc,0x43,0xa4,0x61,0x7d,0x42,0x28,0x3c,0xbb,0x43,0x91,0x45,0x69,0x42,0x28,0x3c,0xbb,0x43,0x58, 14877 0x71,0x6e,0x42,0x08,0xce,0xfb,0xba,0x43,0xd5,0x35,0x78,0x42,0x59,0x45,0xbb,0x43,0x58,0x23,0x82,0x42, 14878 0xa1,0xe1,0xbb,0x43,0xd7,0xbe,0x88,0x42,0x08,0xc9,0x18,0xbc,0x43,0xaf,0x9f,0x8c,0x42,0x1e,0x76,0xbd, 14879 0x43,0x51,0x7c,0x8d,0x42,0xd6,0xe5,0xbe,0x43,0xf4,0x58,0x8e,0x42,0x08,0x9c,0x0a,0xbf,0x43,0x45,0xc7, 14880 0x8e,0x42,0x30,0x26,0xbf,0x43,0x96,0x35,0x8f,0x42,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x07,0xc4, 14881 0x41,0xbf,0x43,0x69,0x76,0xa3,0x42,0x08,0x08,0xef,0xbe,0x43,0xb1,0xd6,0x99,0x42,0xe8,0x89,0xbe,0x43, 14882 0xde,0xc5,0x8d,0x42,0xc0,0x46,0xbc,0x43,0xc2,0x5b,0x90,0x42,0x08,0x9c,0xf2,0xba,0x43,0x86,0x80,0x90, 14883 0x42,0xf2,0x43,0xba,0x43,0xe8,0x73,0x87,0x42,0x8f,0x31,0xba,0x43,0xb6,0xf4,0x7d,0x42,0x07,0x8f,0x31, 14884 0xba,0x43,0x21,0xc6,0x76,0x42,0x08,0xc0,0x3a,0xba,0x43,0x5f,0x48,0x6b,0x42,0xae,0x96,0xba,0x43,0xe3, 14885 0x83,0x61,0x42,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x09,0x06,0xea,0x74,0xea,0x43,0x61,0x44,0x93, 14886 0x43,0x08,0x24,0x5c,0xec,0x43,0x31,0x3b,0x93,0x43,0xfb,0x30,0xee,0x43,0x93,0x4d,0x93,0x43,0x0d,0xe1, 14887 0xef,0x43,0x80,0xa9,0x93,0x43,0x08,0x8f,0x58,0xf0,0x43,0xd1,0x17,0x94,0x43,0xb7,0x8f,0xf0,0x43,0x10, 14888 0xe2,0x94,0x43,0xea,0x98,0xf0,0x43,0xa9,0xec,0x95,0x43,0x07,0xea,0x98,0xf0,0x43,0x38,0x25,0x97,0x43, 14889 0x08,0x23,0x74,0xf0,0x43,0x9f,0x32,0x9a,0x43,0x5a,0x60,0xef,0x43,0x53,0xcb,0x9e,0x43,0x2d,0x3a,0xee, 14890 0x43,0xfd,0x91,0xa3,0x43,0x08,0xa2,0xf0,0xed,0x43,0xdd,0x38,0xa5,0x43,0x17,0xa7,0xed,0x43,0xbe,0xdf, 14891 0xa6,0x43,0x5a,0x54,0xed,0x43,0x9f,0x86,0xa8,0x43,0x08,0xfc,0x24,0xec,0x43,0xca,0xc4,0xad,0x43,0x48, 14892 0xa4,0xeb,0x43,0x40,0x6f,0xab,0x43,0x28,0x3f,0xeb,0x43,0x1c,0x0f,0xa8,0x43,0x08,0x1f,0x6d,0xeb,0x43, 14893 0x72,0x48,0xa3,0x43,0x67,0x09,0xec,0x43,0xd1,0x53,0x9e,0x43,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,0x43, 14894 0x07,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x08,0x7e,0x90,0xea,0x43,0x8a,0x9f,0x99,0x43,0x12,0xac, 14895 0xea,0x43,0xbc,0xa8,0x99,0x43,0xa7,0xc7,0xea,0x43,0xbc,0xa8,0x99,0x43,0x08,0x51,0x76,0xeb,0x43,0x9f, 14896 0x32,0x9a,0x43,0x5e,0x37,0xec,0x43,0x49,0xed,0x9c,0x43,0xb0,0xa5,0xec,0x43,0x2a,0xa0,0xa0,0x43,0x08, 14897 0x09,0xe6,0xec,0x43,0xd1,0x77,0xa4,0x43,0x28,0x4b,0xed,0x43,0x61,0xa4,0xa3,0x43,0xab,0xc2,0xed,0x43, 14898 0x8e,0xb2,0xa0,0x43,0x08,0x70,0xe7,0xed,0x43,0xde,0x08,0x9d,0x43,0x87,0x86,0xf0,0x43,0x2f,0x53,0x97, 14899 0x43,0x87,0x7a,0xee,0x43,0xec,0x99,0x95,0x43,0x08,0xca,0x27,0xee,0x43,0xff,0x3d,0x95,0x43,0x74,0xca, 14900 0xec,0x43,0x55,0x8f,0x94,0x43,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x07,0xea,0x74,0xea,0x43,0x61, 14901 0x44,0x93,0x43,0x09,0x06,0x05,0xd3,0xe5,0x43,0x19,0x9c,0x90,0x43,0x08,0x09,0xc2,0xe6,0x43,0xd1,0xff, 14902 0x8f,0x43,0x4d,0x6f,0xe6,0x43,0x74,0xe8,0x92,0x43,0x3b,0xd7,0xe8,0x43,0xc3,0x56,0x93,0x43,0x08,0x1f, 14903 0x61,0xe9,0x43,0x93,0x4d,0x93,0x43,0x05,0xeb,0xe9,0x43,0x93,0x4d,0x93,0x43,0xea,0x74,0xea,0x43,0x61, 14904 0x44,0x93,0x43,0x07,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x08,0x24,0x50,0xea,0x43,0xe7,0xaa,0x94, 14905 0x43,0x2d,0x22,0xea,0x43,0xe7,0xaa,0x94,0x43,0x36,0xf4,0xe9,0x43,0xe7,0xaa,0x94,0x43,0x08,0xa2,0xcc, 14906 0xe7,0x43,0xe0,0xd8,0x94,0x43,0xd4,0xc9,0xe5,0x43,0x19,0xa8,0x92,0x43,0xd4,0xc9,0xe5,0x43,0x27,0x69, 14907 0x93,0x43,0x08,0x17,0x77,0xe5,0x43,0xe0,0xd8,0x94,0x43,0x67,0xe5,0xe5,0x43,0x47,0xda,0x95,0x43,0x43, 14908 0x9d,0xe6,0x43,0xe2,0xd3,0x97,0x43,0x08,0x9d,0xdd,0xe6,0x43,0xad,0xe7,0x98,0x43,0x09,0xce,0xe8,0x43, 14909 0xff,0x55,0x99,0x43,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x07,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b, 14910 0x43,0x08,0x71,0xcf,0xe9,0x43,0x53,0xb3,0x9a,0x43,0xa7,0xbb,0xe8,0x43,0xdb,0x0d,0x9a,0x43,0xc6,0x14, 14911 0xe7,0x43,0xdb,0x0d,0x9a,0x43,0x08,0x48,0x80,0xe5,0x43,0xdb,0x0d,0x9a,0x43,0x0a,0xb6,0xe4,0x43,0xc3, 14912 0x6e,0x97,0x43,0x76,0x9a,0xe4,0x43,0x74,0xf4,0x94,0x43,0x07,0x76,0x9a,0xe4,0x43,0x79,0xd7,0x93,0x43, 14913 0x08,0xd8,0xac,0xe4,0x43,0x66,0x27,0x92,0x43,0x29,0x1b,0xe5,0x43,0xe0,0xc0,0x90,0x43,0x05,0xd3,0xe5, 14914 0x43,0x19,0x9c,0x90,0x43,0x09,0x06,0x1b,0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x08,0x71,0x0b,0xf4,0x42, 14915 0x00,0x0e,0x8d,0x42,0x8c,0x0f,0x01,0x43,0x3e,0xc0,0x89,0x42,0xf3,0x28,0x06,0x43,0x48,0x9e,0x8b,0x42, 14916 0x08,0x15,0x89,0x09,0x43,0x00,0x0e,0x8d,0x42,0xe0,0x9c,0x0a,0x43,0xc1,0x8b,0x98,0x42,0xa6,0xc1,0x0a, 14917 0x43,0x02,0xa5,0xaa,0x42,0x07,0xa6,0xc1,0x0a,0x43,0xf9,0xf6,0xb0,0x42,0x08,0xa6,0xc1,0x0a,0x43,0x47, 14918 0x8e,0xb4,0x42,0x42,0xaf,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0xe0,0x9c,0x0a,0x43,0xba,0x74,0xbc,0x42,0x08, 14919 0xa1,0xd2,0x09,0x43,0x40,0x47,0xd0,0x42,0x0d,0xab,0x07,0x43,0x91,0xb5,0xd0,0x42,0x3b,0xb9,0x04,0x43, 14920 0xec,0x71,0xba,0x42,0x08,0xe5,0x5b,0x03,0x43,0xe3,0x33,0xa8,0x42,0x63,0xd8,0x00,0x43,0xce,0x70,0x9f, 14921 0x42,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x07,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,0x42,0x08,0xed, 14922 0x6f,0xed,0x42,0x73,0x24,0x9d,0x42,0xd8,0x0c,0xf5,0x42,0x99,0x6c,0x9c,0x42,0x27,0xab,0xfd,0x42,0xea, 14923 0xda,0x9c,0x42,0x08,0x36,0xca,0x03,0x43,0x2b,0x94,0x9e,0x42,0x68,0xc7,0x01,0x43,0x8f,0xbe,0xa2,0x42, 14924 0xfa,0x06,0x08,0x43,0x73,0xb4,0xb5,0x42,0x08,0x8e,0x2e,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0x9d,0xe3,0x08, 14925 0x43,0xd7,0x1e,0x99,0x42,0x28,0x15,0x05,0x43,0x32,0x3b,0x93,0x42,0x08,0x63,0xf0,0x04,0x43,0x70,0xed, 14926 0x8f,0x42,0x71,0x0b,0xf4,0x42,0x32,0x3b,0x93,0x42,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x07,0x1b, 14927 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x09,0x06,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,0x08,0x8e,0x55, 14928 0xc0,0x42,0xb8,0x4d,0x86,0x42,0x60,0xbf,0xd7,0x42,0x3e,0xf0,0x91,0x42,0x63,0xf6,0xe4,0x42,0x70,0xed, 14929 0x8f,0x42,0x08,0x7a,0x89,0xe5,0x42,0xac,0xc8,0x8f,0x42,0xcc,0xf7,0xe5,0x42,0xac,0xc8,0x8f,0x42,0x1b, 14930 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x07,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x08,0x63,0xf6,0xe4, 14931 0x42,0x3b,0x19,0x95,0x42,0xe6,0x61,0xe3,0x42,0x00,0x3e,0x95,0x42,0xf4,0x16,0xe2,0x42,0xc4,0x62,0x95, 14932 0x42,0x08,0x6e,0x74,0xd6,0x42,0x15,0xd1,0x95,0x42,0x97,0x63,0xca,0x42,0xaf,0xcf,0x94,0x42,0xfb,0x2d, 14933 0xbe,0x42,0x86,0x80,0x90,0x42,0x08,0x97,0x03,0xba,0x42,0xce,0x10,0x8f,0x42,0x5e,0x28,0xba,0x42,0x3e, 14934 0xf0,0x91,0x42,0xf2,0x4f,0xbc,0x42,0x45,0xf7,0x96,0x42,0x08,0x27,0x54,0xbf,0x42,0x73,0x24,0x9d,0x42, 14935 0xa5,0xe8,0xc0,0x42,0x86,0xe0,0xa0,0x42,0xe4,0xca,0xc5,0x42,0xed,0x11,0xaa,0x42,0x08,0x54,0xaa,0xc8, 14936 0x42,0x86,0x40,0xb1,0x42,0x59,0x81,0xc5,0x42,0xa1,0x11,0xc4,0x42,0x3e,0xe7,0xbf,0x42,0xfb,0x8d,0xce, 14937 0x42,0x08,0xb4,0x6d,0xb7,0x42,0x30,0xc2,0xd9,0x42,0x46,0xf5,0xc9,0x42,0xdf,0x53,0xd9,0x42,0x38,0x40, 14938 0xcb,0x42,0x62,0x8f,0xcf,0x42,0x08,0x7d,0xf9,0xcc,0x42,0xec,0xa1,0xc2,0x42,0x07,0x43,0xcd,0x42,0x6c, 14939 0xdd,0xb8,0x42,0x2b,0x8b,0xcc,0x42,0x92,0xf5,0xaf,0x42,0x08,0xf9,0x8d,0xce,0x42,0x41,0x57,0xa7,0x42, 14940 0x5b,0xb8,0xd2,0x42,0xae,0x2f,0xa5,0x42,0x18,0x2f,0xd9,0x42,0x13,0x2a,0xa1,0x42,0x08,0x41,0x7e,0xdd, 14941 0x42,0xe3,0x03,0xa0,0x42,0x2e,0xf2,0xe1,0x42,0x7c,0x02,0x9f,0x42,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e, 14942 0x42,0x07,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x08,0x4d,0x63,0xe4,0x42,0x00,0x9e,0xa5,0x42,0xf4, 14943 0x16,0xe2,0x42,0x15,0x31,0xa6,0x42,0x99,0xca,0xdf,0x42,0x2b,0xc4,0xa6,0x42,0x08,0xc0,0x82,0xc6,0x42, 14944 0xc4,0xc2,0xa5,0x42,0x57,0xe1,0xd5,0x42,0x91,0xb5,0xd0,0x42,0x54,0xda,0xd0,0x42,0x97,0x93,0xd2,0x42, 14945 0x08,0x9c,0x3a,0xc7,0x42,0x17,0x58,0xdc,0x42,0x9c,0x0a,0xbf,0x42,0x6e,0xa4,0xde,0x42,0x90,0x25,0xb8, 14946 0x42,0xdf,0x53,0xd9,0x42,0x08,0x59,0x21,0xb5,0x42,0xf2,0xdf,0xd4,0x42,0x51,0x43,0xb3,0x42,0x91,0xb5, 14947 0xd0,0x42,0xc5,0x29,0xbb,0x42,0x0e,0x1a,0xca,0x42,0x08,0x65,0x36,0xc4,0x42,0xd0,0x07,0xbd,0x42,0x3e, 14948 0xe7,0xbf,0x42,0x37,0x09,0xbe,0x42,0x0c,0xea,0xc1,0x42,0xcd,0xd0,0xaf,0x42,0x08,0x2b,0x5b,0xc4,0x42, 14949 0x18,0x08,0xa3,0x42,0x67,0xa6,0xab,0x42,0x99,0x3c,0x94,0x42,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42, 14950 0x09,]; 14951 14952 private struct ThePath { 14953 public: 14954 enum Command { 14955 Bounds, // always first, has 4 args (x0, y0, x1, y1) 14956 StrokeMode, 14957 FillMode, 14958 StrokeFillMode, 14959 NormalStroke, 14960 ThinStroke, 14961 MoveTo, 14962 LineTo, 14963 CubicTo, // cubic bezier 14964 EndPath, 14965 } 14966 14967 public: 14968 const(ubyte)[] path; 14969 uint ppos; 14970 14971 public: 14972 this (const(void)[] apath) pure nothrow @trusted @nogc { 14973 path = cast(const(ubyte)[])apath; 14974 } 14975 14976 @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (ppos >= path.length); } 14977 14978 Command getCommand () nothrow @trusted @nogc { 14979 pragma(inline, true); 14980 if (ppos >= cast(uint)path.length) assert(0, "invalid path"); 14981 return cast(Command)(path.ptr[ppos++]); 14982 } 14983 14984 // number of (x,y) pairs for this command 14985 static int argCount (in Command cmd) nothrow @safe @nogc { 14986 version(aliced) pragma(inline, true); 14987 if (cmd == Command.Bounds) return 2; 14988 else if (cmd == Command.MoveTo || cmd == Command.LineTo) return 1; 14989 else if (cmd == Command.CubicTo) return 3; 14990 else return 0; 14991 } 14992 14993 void skipArgs (int argc) nothrow @trusted @nogc { 14994 pragma(inline, true); 14995 ppos += cast(uint)(float.sizeof*2*argc); 14996 } 14997 14998 float getFloat () nothrow @trusted @nogc { 14999 pragma(inline, true); 15000 if (ppos >= cast(uint)path.length || cast(uint)path.length-ppos < float.sizeof) assert(0, "invalid path"); 15001 version(LittleEndian) { 15002 float res = *cast(const(float)*)(&path.ptr[ppos]); 15003 ppos += cast(uint)float.sizeof; 15004 return res; 15005 } else { 15006 static assert(float.sizeof == 4); 15007 uint xp = path.ptr[ppos]|(path.ptr[ppos+1]<<8)|(path.ptr[ppos+2]<<16)|(path.ptr[ppos+3]<<24); 15008 ppos += cast(uint)float.sizeof; 15009 return *cast(const(float)*)(&xp); 15010 } 15011 } 15012 } 15013 15014 // this will add baphomet's background path to the current NanoVega path, so you can fill it. 15015 public void addBaphometBack (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc { 15016 if (nvg is null) return; 15017 15018 auto path = ThePath(baphometPath); 15019 15020 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15021 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15022 15023 bool inPath = false; 15024 while (!path.empty) { 15025 auto cmd = path.getCommand(); 15026 switch (cmd) { 15027 case ThePath.Command.MoveTo: 15028 inPath = true; 15029 immutable float ex = getScaledX(); 15030 immutable float ey = getScaledY(); 15031 nvg.moveTo(ex, ey); 15032 break; 15033 case ThePath.Command.LineTo: 15034 inPath = true; 15035 immutable float ex = getScaledX(); 15036 immutable float ey = getScaledY(); 15037 nvg.lineTo(ex, ey); 15038 break; 15039 case ThePath.Command.CubicTo: // cubic bezier 15040 inPath = true; 15041 immutable float x1 = getScaledX(); 15042 immutable float y1 = getScaledY(); 15043 immutable float x2 = getScaledX(); 15044 immutable float y2 = getScaledY(); 15045 immutable float ex = getScaledX(); 15046 immutable float ey = getScaledY(); 15047 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15048 break; 15049 case ThePath.Command.EndPath: 15050 if (inPath) return; 15051 break; 15052 default: 15053 path.skipArgs(path.argCount(cmd)); 15054 break; 15055 } 15056 } 15057 } 15058 15059 // this will add baphomet's pupil paths to the current NanoVega path, so you can fill it. 15060 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 { 15061 // pupils starts with "fill-and-stroke" mode 15062 if (nvg is null) return; 15063 15064 auto path = ThePath(baphometPath); 15065 15066 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15067 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15068 15069 bool inPath = false; 15070 bool pupLeft = true; 15071 while (!path.empty) { 15072 auto cmd = path.getCommand(); 15073 switch (cmd) { 15074 case ThePath.Command.StrokeFillMode: inPath = true; break; 15075 case ThePath.Command.MoveTo: 15076 if (!inPath) goto default; 15077 static if (!left) { if (pupLeft) goto default; } 15078 static if (!right) { if (!pupLeft) goto default; } 15079 immutable float ex = getScaledX(); 15080 immutable float ey = getScaledY(); 15081 nvg.moveTo(ex, ey); 15082 break; 15083 case ThePath.Command.LineTo: 15084 if (!inPath) goto default; 15085 static if (!left) { if (pupLeft) goto default; } 15086 static if (!right) { if (!pupLeft) goto default; } 15087 immutable float ex = getScaledX(); 15088 immutable float ey = getScaledY(); 15089 nvg.lineTo(ex, ey); 15090 break; 15091 case ThePath.Command.CubicTo: // cubic bezier 15092 if (!inPath) goto default; 15093 static if (!left) { if (pupLeft) goto default; } 15094 static if (!right) { if (!pupLeft) goto default; } 15095 immutable float x1 = getScaledX(); 15096 immutable float y1 = getScaledY(); 15097 immutable float x2 = getScaledX(); 15098 immutable float y2 = getScaledY(); 15099 immutable float ex = getScaledX(); 15100 immutable float ey = getScaledY(); 15101 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15102 break; 15103 case ThePath.Command.EndPath: 15104 if (inPath) { 15105 if (pupLeft) pupLeft = false; else return; 15106 } 15107 break; 15108 default: 15109 path.skipArgs(path.argCount(cmd)); 15110 break; 15111 } 15112 } 15113 } 15114 15115 // mode: 'f' to allow fills; 's' to allow strokes; 'w' to allow stroke widths; 'c' to replace fills with strokes 15116 public void renderBaphomet(string mode="fs") (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc { 15117 template hasChar(char ch, string s) { 15118 static if (s.length == 0) enum hasChar = false; 15119 else static if (s[0] == ch) enum hasChar = true; 15120 else enum hasChar = hasChar!(ch, s[1..$]); 15121 } 15122 enum AllowStroke = hasChar!('s', mode); 15123 enum AllowFill = hasChar!('f', mode); 15124 enum AllowWidth = hasChar!('w', mode); 15125 enum Contour = hasChar!('c', mode); 15126 //static assert(AllowWidth || AllowFill); 15127 15128 if (nvg is null) return; 15129 15130 auto path = ThePath(baphometPath); 15131 15132 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15133 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15134 15135 int mode = 0; 15136 int sw = ThePath.Command.NormalStroke; 15137 nvg.beginPath(); 15138 while (!path.empty) { 15139 auto cmd = path.getCommand(); 15140 switch (cmd) { 15141 case ThePath.Command.StrokeMode: mode = ThePath.Command.StrokeMode; break; 15142 case ThePath.Command.FillMode: mode = ThePath.Command.FillMode; break; 15143 case ThePath.Command.StrokeFillMode: mode = ThePath.Command.StrokeFillMode; break; 15144 case ThePath.Command.NormalStroke: sw = ThePath.Command.NormalStroke; break; 15145 case ThePath.Command.ThinStroke: sw = ThePath.Command.ThinStroke; break; 15146 case ThePath.Command.MoveTo: 15147 immutable float ex = getScaledX(); 15148 immutable float ey = getScaledY(); 15149 nvg.moveTo(ex, ey); 15150 break; 15151 case ThePath.Command.LineTo: 15152 immutable float ex = getScaledX(); 15153 immutable float ey = getScaledY(); 15154 nvg.lineTo(ex, ey); 15155 break; 15156 case ThePath.Command.CubicTo: // cubic bezier 15157 immutable float x1 = getScaledX(); 15158 immutable float y1 = getScaledY(); 15159 immutable float x2 = getScaledX(); 15160 immutable float y2 = getScaledY(); 15161 immutable float ex = getScaledX(); 15162 immutable float ey = getScaledY(); 15163 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15164 break; 15165 case ThePath.Command.EndPath: 15166 if (mode == ThePath.Command.FillMode || mode == ThePath.Command.StrokeFillMode) { 15167 static if (AllowFill || Contour) { 15168 static if (Contour) { 15169 if (mode == ThePath.Command.FillMode) { nvg.strokeWidth = 1; nvg.stroke(); } 15170 } else { 15171 nvg.fill(); 15172 } 15173 } 15174 } 15175 if (mode == ThePath.Command.StrokeMode || mode == ThePath.Command.StrokeFillMode) { 15176 static if (AllowStroke || Contour) { 15177 static if (AllowWidth) { 15178 if (sw == ThePath.Command.NormalStroke) nvg.strokeWidth = 1; 15179 else if (sw == ThePath.Command.ThinStroke) nvg.strokeWidth = 0.5; 15180 else assert(0, "wtf?!"); 15181 } 15182 nvg.stroke(); 15183 } 15184 } 15185 nvg.newPath(); 15186 break; 15187 default: 15188 path.skipArgs(path.argCount(cmd)); 15189 break; 15190 } 15191 } 15192 nvg.newPath(); 15193 }