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 sdmain.setAsCurrentOpenGlContext(); 467 nvg = nvgCreateContext(); 468 if (nvg is null) assert(0, "cannot initialize NanoVega"); 469 }; 470 471 // this callback will be called when we will need to repaint our window 472 sdmain.redrawOpenGlScene = delegate () { 473 // fix viewport (we can do this in resize event, or here, it doesn't matter) 474 glViewport(0, 0, sdmain.width, sdmain.height); 475 476 // clear window 477 glClearColor(0, 0, 0, 0); 478 glClear(glNVGClearFlags); // use NanoVega API to get flags for OpenGL call 479 480 { 481 nvg.beginFrame(sdmain.width, sdmain.height); // begin rendering 482 scope(exit) nvg.endFrame(); // and flush render queue on exit 483 484 nvg.beginPath(); // start new path 485 nvg.roundedRect(20.5, 30.5, sdmain.width-40, sdmain.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation) 486 // now set filling mode for our rectangle 487 // you can create colors using HTML syntax, or with convenient constants 488 nvg.fillPaint = nvg.linearGradient(20.5, 30.5, sdmain.width-40, sdmain.height-60, NVGColor("#f70"), NVGColor.green); 489 // now fill our rect 490 nvg.fill(); 491 // and draw a nice outline 492 nvg.strokeColor = NVGColor.white; 493 nvg.strokeWidth = 2; 494 nvg.stroke(); 495 // that's all, folks! 496 } 497 }; 498 499 sdmain.eventLoop(0, // no pulse timer required 500 delegate (KeyEvent event) { 501 if (event == "*-Q" || event == "Escape") { sdmain.close(); return; } // quit on Q, Ctrl+Q, and so on 502 }, 503 ); 504 505 flushGui(); // let OS do it's cleanup 506 } 507 } 508 509 private: 510 511 version(aliced) { 512 import iv.meta; 513 import iv.vfs; 514 } else { 515 private alias usize = size_t; 516 // i fear phobos! 517 private template Unqual(T) { 518 static if (is(T U == immutable U)) alias Unqual = U; 519 else static if (is(T U == shared inout const U)) alias Unqual = U; 520 else static if (is(T U == shared inout U)) alias Unqual = U; 521 else static if (is(T U == shared const U)) alias Unqual = U; 522 else static if (is(T U == shared U)) alias Unqual = U; 523 else static if (is(T U == inout const U)) alias Unqual = U; 524 else static if (is(T U == inout U)) alias Unqual = U; 525 else static if (is(T U == const U)) alias Unqual = U; 526 else alias Unqual = T; 527 } 528 private template isAnyCharType(T, bool unqual=false) { 529 static if (unqual) private alias UT = Unqual!T; else private alias UT = T; 530 enum isAnyCharType = is(UT == char) || is(UT == wchar) || is(UT == dchar); 531 } 532 private template isWideCharType(T, bool unqual=false) { 533 static if (unqual) private alias UT = Unqual!T; else private alias UT = T; 534 enum isWideCharType = is(UT == wchar) || is(UT == dchar); 535 } 536 } 537 version(nanovg_disable_vfs) { 538 enum NanoVegaHasIVVFS = false; 539 } else { 540 static if (is(typeof((){import iv.vfs;}))) { 541 enum NanoVegaHasIVVFS = true; 542 import iv.vfs; 543 } else { 544 enum NanoVegaHasIVVFS = false; 545 } 546 } 547 548 // ////////////////////////////////////////////////////////////////////////// // 549 // engine 550 // ////////////////////////////////////////////////////////////////////////// // 551 import core.stdc.stdlib : malloc, realloc, free; 552 import core.stdc.string : memset, memcpy, strlen; 553 import std.math : PI; 554 555 //version = nanovg_force_stb_ttf; 556 557 version(Posix) { 558 version = nanovg_use_freetype; 559 } else { 560 version = nanovg_disable_fontconfig; 561 } 562 563 version (bindbc) { 564 version = nanovg_builtin_fontconfig_bindings; 565 version = nanovg_bindbc_opengl_bindings; 566 version = nanovg_bindbc_freetype_bindings; 567 version(BindFT_Dynamic) 568 static assert(0, "AsumFace was too lazy to write the code for the dynamic bindbc freetype bindings"); 569 else { 570 version(BindFT_Static) {} 571 else 572 static assert(0, "well, duh. you got to pass the BindFT_Static version identifier to the compiler"); 573 } 574 } else version(aliced) { 575 version = nanovg_default_no_font_aa; 576 version = nanovg_builtin_fontconfig_bindings; 577 version = nanovg_builtin_freetype_bindings; 578 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings 579 } else { 580 version (neverHave_bindbc_opengl) 581 version = nanovg_bindbc_opengl_bindings; 582 else 583 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings 584 version (neverHave_bindbc_freetype) 585 version = nanovg_bindbc_freetype_bindings; 586 else 587 version = nanovg_builtin_freetype_bindings; 588 version = nanovg_builtin_fontconfig_bindings; 589 } 590 591 version(nanovg_disable_fontconfig) { 592 public enum NanoVegaHasFontConfig = false; 593 } else { 594 public enum NanoVegaHasFontConfig = true; 595 version(nanovg_builtin_fontconfig_bindings) {} else import iv.fontconfig; 596 } 597 598 //version = nanovg_bench_flatten; 599 600 /++ 601 Annotation to indicate the marked function is compatible with [arsd.script]. 602 603 604 Any function that takes a [Color] argument will be passed a string instead. 605 606 Scriptable Functions 607 ==================== 608 609 $(UDA_USES) 610 611 $(ALWAYS_DOCUMENT) 612 +/ 613 private enum scriptable = "arsd_jsvar_compatible"; 614 615 public: 616 alias NVG_PI = PI; 617 618 enum NanoVegaHasArsdColor = (is(typeof((){ import arsd.color; }))); 619 enum NanoVegaHasArsdImage = (is(typeof((){ import arsd.color; import arsd.image; }))); 620 621 static if (NanoVegaHasArsdColor) private import arsd.color; 622 static if (NanoVegaHasArsdImage) { 623 private import arsd.image; 624 } else { 625 void stbi_set_unpremultiply_on_load (int flag_true_if_should_unpremultiply) {} 626 void stbi_convert_iphone_png_to_rgb (int flag_true_if_should_convert) {} 627 ubyte* stbi_load (const(char)* filename, int* x, int* y, int* comp, int req_comp) { return null; } 628 ubyte* stbi_load_from_memory (const(void)* buffer, int len, int* x, int* y, int* comp, int req_comp) { return null; } 629 void stbi_image_free (void* retval_from_stbi_load) {} 630 } 631 632 version(nanovg_default_no_font_aa) { 633 __gshared bool NVG_INVERT_FONT_AA = false; 634 } else { 635 __gshared bool NVG_INVERT_FONT_AA = true; 636 } 637 638 639 /// this is branchless for ints on x86, and even for longs on x86_64 640 public ubyte nvgClampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) { 641 static if (__VERSION__ > 2067) pragma(inline, true); 642 static if (T.sizeof == 2 || T.sizeof == 4) { 643 static if (__traits(isUnsigned, T)) { 644 return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24))); 645 } else { 646 n &= -cast(int)(n >= 0); 647 return cast(ubyte)(n|((255-cast(int)n)>>31)); 648 } 649 } else static if (T.sizeof == 1) { 650 static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?"); 651 return cast(ubyte)n; 652 } else static if (T.sizeof == 8) { 653 static if (__traits(isUnsigned, T)) { 654 return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56))); 655 } else { 656 n &= -cast(long)(n >= 0); 657 return cast(ubyte)(n|((255-cast(long)n)>>63)); 658 } 659 } else { 660 static assert(false, "clampToByte: integer too big"); 661 } 662 } 663 664 665 /// NanoVega RGBA color 666 /// Group: color_utils 667 public align(1) struct NVGColor { 668 align(1): 669 public: 670 float[4] rgba = 0; /// default color is transparent (a=1 is opaque) 671 672 public: 673 @property string toString () const @safe { import std.string : format; return "NVGColor(%s,%s,%s,%s)".format(r, g, b, a); } 674 675 public: 676 enum transparent = NVGColor(0.0f, 0.0f, 0.0f, 0.0f); 677 enum k8orange = NVGColor(1.0f, 0.5f, 0.0f, 1.0f); 678 679 enum aliceblue = NVGColor(240, 248, 255); 680 enum antiquewhite = NVGColor(250, 235, 215); 681 enum aqua = NVGColor(0, 255, 255); 682 enum aquamarine = NVGColor(127, 255, 212); 683 enum azure = NVGColor(240, 255, 255); 684 enum beige = NVGColor(245, 245, 220); 685 enum bisque = NVGColor(255, 228, 196); 686 enum black = NVGColor(0, 0, 0); // basic color 687 enum blanchedalmond = NVGColor(255, 235, 205); 688 enum blue = NVGColor(0, 0, 255); // basic color 689 enum blueviolet = NVGColor(138, 43, 226); 690 enum brown = NVGColor(165, 42, 42); 691 enum burlywood = NVGColor(222, 184, 135); 692 enum cadetblue = NVGColor(95, 158, 160); 693 enum chartreuse = NVGColor(127, 255, 0); 694 enum chocolate = NVGColor(210, 105, 30); 695 enum coral = NVGColor(255, 127, 80); 696 enum cornflowerblue = NVGColor(100, 149, 237); 697 enum cornsilk = NVGColor(255, 248, 220); 698 enum crimson = NVGColor(220, 20, 60); 699 enum cyan = NVGColor(0, 255, 255); // basic color 700 enum darkblue = NVGColor(0, 0, 139); 701 enum darkcyan = NVGColor(0, 139, 139); 702 enum darkgoldenrod = NVGColor(184, 134, 11); 703 enum darkgray = NVGColor(169, 169, 169); 704 enum darkgreen = NVGColor(0, 100, 0); 705 enum darkgrey = NVGColor(169, 169, 169); 706 enum darkkhaki = NVGColor(189, 183, 107); 707 enum darkmagenta = NVGColor(139, 0, 139); 708 enum darkolivegreen = NVGColor(85, 107, 47); 709 enum darkorange = NVGColor(255, 140, 0); 710 enum darkorchid = NVGColor(153, 50, 204); 711 enum darkred = NVGColor(139, 0, 0); 712 enum darksalmon = NVGColor(233, 150, 122); 713 enum darkseagreen = NVGColor(143, 188, 143); 714 enum darkslateblue = NVGColor(72, 61, 139); 715 enum darkslategray = NVGColor(47, 79, 79); 716 enum darkslategrey = NVGColor(47, 79, 79); 717 enum darkturquoise = NVGColor(0, 206, 209); 718 enum darkviolet = NVGColor(148, 0, 211); 719 enum deeppink = NVGColor(255, 20, 147); 720 enum deepskyblue = NVGColor(0, 191, 255); 721 enum dimgray = NVGColor(105, 105, 105); 722 enum dimgrey = NVGColor(105, 105, 105); 723 enum dodgerblue = NVGColor(30, 144, 255); 724 enum firebrick = NVGColor(178, 34, 34); 725 enum floralwhite = NVGColor(255, 250, 240); 726 enum forestgreen = NVGColor(34, 139, 34); 727 enum fuchsia = NVGColor(255, 0, 255); 728 enum gainsboro = NVGColor(220, 220, 220); 729 enum ghostwhite = NVGColor(248, 248, 255); 730 enum gold = NVGColor(255, 215, 0); 731 enum goldenrod = NVGColor(218, 165, 32); 732 enum gray = NVGColor(128, 128, 128); // basic color 733 enum green = NVGColor(0, 128, 0); // basic color 734 enum greenyellow = NVGColor(173, 255, 47); 735 enum grey = NVGColor(128, 128, 128); // basic color 736 enum honeydew = NVGColor(240, 255, 240); 737 enum hotpink = NVGColor(255, 105, 180); 738 enum indianred = NVGColor(205, 92, 92); 739 enum indigo = NVGColor(75, 0, 130); 740 enum ivory = NVGColor(255, 255, 240); 741 enum khaki = NVGColor(240, 230, 140); 742 enum lavender = NVGColor(230, 230, 250); 743 enum lavenderblush = NVGColor(255, 240, 245); 744 enum lawngreen = NVGColor(124, 252, 0); 745 enum lemonchiffon = NVGColor(255, 250, 205); 746 enum lightblue = NVGColor(173, 216, 230); 747 enum lightcoral = NVGColor(240, 128, 128); 748 enum lightcyan = NVGColor(224, 255, 255); 749 enum lightgoldenrodyellow = NVGColor(250, 250, 210); 750 enum lightgray = NVGColor(211, 211, 211); 751 enum lightgreen = NVGColor(144, 238, 144); 752 enum lightgrey = NVGColor(211, 211, 211); 753 enum lightpink = NVGColor(255, 182, 193); 754 enum lightsalmon = NVGColor(255, 160, 122); 755 enum lightseagreen = NVGColor(32, 178, 170); 756 enum lightskyblue = NVGColor(135, 206, 250); 757 enum lightslategray = NVGColor(119, 136, 153); 758 enum lightslategrey = NVGColor(119, 136, 153); 759 enum lightsteelblue = NVGColor(176, 196, 222); 760 enum lightyellow = NVGColor(255, 255, 224); 761 enum lime = NVGColor(0, 255, 0); 762 enum limegreen = NVGColor(50, 205, 50); 763 enum linen = NVGColor(250, 240, 230); 764 enum magenta = NVGColor(255, 0, 255); // basic color 765 enum maroon = NVGColor(128, 0, 0); 766 enum mediumaquamarine = NVGColor(102, 205, 170); 767 enum mediumblue = NVGColor(0, 0, 205); 768 enum mediumorchid = NVGColor(186, 85, 211); 769 enum mediumpurple = NVGColor(147, 112, 219); 770 enum mediumseagreen = NVGColor(60, 179, 113); 771 enum mediumslateblue = NVGColor(123, 104, 238); 772 enum mediumspringgreen = NVGColor(0, 250, 154); 773 enum mediumturquoise = NVGColor(72, 209, 204); 774 enum mediumvioletred = NVGColor(199, 21, 133); 775 enum midnightblue = NVGColor(25, 25, 112); 776 enum mintcream = NVGColor(245, 255, 250); 777 enum mistyrose = NVGColor(255, 228, 225); 778 enum moccasin = NVGColor(255, 228, 181); 779 enum navajowhite = NVGColor(255, 222, 173); 780 enum navy = NVGColor(0, 0, 128); 781 enum oldlace = NVGColor(253, 245, 230); 782 enum olive = NVGColor(128, 128, 0); 783 enum olivedrab = NVGColor(107, 142, 35); 784 enum orange = NVGColor(255, 165, 0); 785 enum orangered = NVGColor(255, 69, 0); 786 enum orchid = NVGColor(218, 112, 214); 787 enum palegoldenrod = NVGColor(238, 232, 170); 788 enum palegreen = NVGColor(152, 251, 152); 789 enum paleturquoise = NVGColor(175, 238, 238); 790 enum palevioletred = NVGColor(219, 112, 147); 791 enum papayawhip = NVGColor(255, 239, 213); 792 enum peachpuff = NVGColor(255, 218, 185); 793 enum peru = NVGColor(205, 133, 63); 794 enum pink = NVGColor(255, 192, 203); 795 enum plum = NVGColor(221, 160, 221); 796 enum powderblue = NVGColor(176, 224, 230); 797 enum purple = NVGColor(128, 0, 128); 798 enum red = NVGColor(255, 0, 0); // basic color 799 enum rosybrown = NVGColor(188, 143, 143); 800 enum royalblue = NVGColor(65, 105, 225); 801 enum saddlebrown = NVGColor(139, 69, 19); 802 enum salmon = NVGColor(250, 128, 114); 803 enum sandybrown = NVGColor(244, 164, 96); 804 enum seagreen = NVGColor(46, 139, 87); 805 enum seashell = NVGColor(255, 245, 238); 806 enum sienna = NVGColor(160, 82, 45); 807 enum silver = NVGColor(192, 192, 192); 808 enum skyblue = NVGColor(135, 206, 235); 809 enum slateblue = NVGColor(106, 90, 205); 810 enum slategray = NVGColor(112, 128, 144); 811 enum slategrey = NVGColor(112, 128, 144); 812 enum snow = NVGColor(255, 250, 250); 813 enum springgreen = NVGColor(0, 255, 127); 814 enum steelblue = NVGColor(70, 130, 180); 815 enum tan = NVGColor(210, 180, 140); 816 enum teal = NVGColor(0, 128, 128); 817 enum thistle = NVGColor(216, 191, 216); 818 enum tomato = NVGColor(255, 99, 71); 819 enum turquoise = NVGColor(64, 224, 208); 820 enum violet = NVGColor(238, 130, 238); 821 enum wheat = NVGColor(245, 222, 179); 822 enum white = NVGColor(255, 255, 255); // basic color 823 enum whitesmoke = NVGColor(245, 245, 245); 824 enum yellow = NVGColor(255, 255, 0); // basic color 825 enum yellowgreen = NVGColor(154, 205, 50); 826 827 nothrow @safe @nogc: 828 public: 829 /// 830 this (ubyte ar, ubyte ag, ubyte ab, ubyte aa=255) pure { 831 pragma(inline, true); 832 r = ar/255.0f; 833 g = ag/255.0f; 834 b = ab/255.0f; 835 a = aa/255.0f; 836 } 837 838 /// 839 this (float ar, float ag, float ab, float aa=1.0f) pure { 840 pragma(inline, true); 841 r = ar; 842 g = ag; 843 b = ab; 844 a = aa; 845 } 846 847 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 848 this (uint c) pure { 849 pragma(inline, true); 850 r = (c&0xff)/255.0f; 851 g = ((c>>8)&0xff)/255.0f; 852 b = ((c>>16)&0xff)/255.0f; 853 a = ((c>>24)&0xff)/255.0f; 854 } 855 856 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb" 857 this (const(char)[] srgb) { 858 static int c2d (char ch) pure nothrow @safe @nogc { 859 pragma(inline, true); 860 return 861 ch >= '0' && ch <= '9' ? ch-'0' : 862 ch >= 'A' && ch <= 'F' ? ch-'A'+10 : 863 ch >= 'a' && ch <= 'f' ? ch-'a'+10 : 864 -1; 865 } 866 int[8] digs; 867 int dc = -1; 868 foreach (immutable char ch; srgb) { 869 if (ch <= ' ') continue; 870 if (ch == '#') { 871 if (dc != -1) { dc = -1; break; } 872 dc = 0; 873 } else { 874 if (dc >= digs.length) { dc = -1; break; } 875 if ((digs[dc++] = c2d(ch)) < 0) { dc = -1; break; } 876 } 877 } 878 switch (dc) { 879 case 3: // rgb 880 a = 1.0f; 881 r = digs[0]/15.0f; 882 g = digs[1]/15.0f; 883 b = digs[2]/15.0f; 884 break; 885 case 4: // argb 886 a = digs[0]/15.0f; 887 r = digs[1]/15.0f; 888 g = digs[2]/15.0f; 889 b = digs[3]/15.0f; 890 break; 891 case 6: // rrggbb 892 a = 1.0f; 893 r = (digs[0]*16+digs[1])/255.0f; 894 g = (digs[2]*16+digs[3])/255.0f; 895 b = (digs[4]*16+digs[5])/255.0f; 896 break; 897 case 8: // aarrggbb 898 a = (digs[0]*16+digs[1])/255.0f; 899 r = (digs[2]*16+digs[3])/255.0f; 900 g = (digs[4]*16+digs[5])/255.0f; 901 b = (digs[6]*16+digs[7])/255.0f; 902 break; 903 default: 904 break; 905 } 906 } 907 908 /// Is this color completely opaque? 909 @property bool isOpaque () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] >= 1.0f); } 910 /// Is this color completely transparent? 911 @property bool isTransparent () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] <= 0.0f); } 912 913 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 914 @property uint asUint () const pure { 915 pragma(inline, true); 916 return 917 cast(uint)(r*255)| 918 (cast(uint)(g*255)<<8)| 919 (cast(uint)(b*255)<<16)| 920 (cast(uint)(a*255)<<24); 921 } 922 923 alias asUintABGR = asUint; /// Ditto. 924 925 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 926 static NVGColor fromUint (uint c) pure { pragma(inline, true); return NVGColor(c); } 927 928 alias fromUintABGR = fromUint; /// Ditto. 929 930 /// AARRGGBB 931 @property uint asUintARGB () const pure { 932 pragma(inline, true); 933 return 934 cast(uint)(b*255)| 935 (cast(uint)(g*255)<<8)| 936 (cast(uint)(r*255)<<16)| 937 (cast(uint)(a*255)<<24); 938 } 939 940 /// AARRGGBB 941 static NVGColor fromUintARGB (uint c) pure { pragma(inline, true); return NVGColor((c>>16)&0xff, (c>>8)&0xff, c&0xff, (c>>24)&0xff); } 942 943 @property ref inout(float) r () inout pure @trusted { pragma(inline, true); return rgba.ptr[0]; } /// 944 @property ref inout(float) g () inout pure @trusted { pragma(inline, true); return rgba.ptr[1]; } /// 945 @property ref inout(float) b () inout pure @trusted { pragma(inline, true); return rgba.ptr[2]; } /// 946 @property ref inout(float) a () inout pure @trusted { pragma(inline, true); return rgba.ptr[3]; } /// 947 948 ref NVGColor applyTint() (const scope auto ref NVGColor tint) nothrow @trusted @nogc { 949 if (tint.a == 0) return this; 950 foreach (immutable idx, ref float v; rgba[0..4]) { 951 v = nvg__clamp(v*tint.rgba.ptr[idx], 0.0f, 1.0f); 952 } 953 return this; 954 } 955 956 NVGHSL asHSL() (bool useWeightedLightness=false) const { pragma(inline, true); return NVGHSL.fromColor(this, useWeightedLightness); } /// 957 static fromHSL() (const scope auto ref NVGHSL hsl) { pragma(inline, true); return hsl.asColor; } /// 958 959 static if (NanoVegaHasArsdColor) { 960 Color toArsd () const { pragma(inline, true); return Color(cast(int)(r*255), cast(int)(g*255), cast(int)(b*255), cast(int)(a*255)); } /// 961 static NVGColor fromArsd (in Color c) { pragma(inline, true); return NVGColor(c.r, c.g, c.b, c.a); } /// 962 /// 963 this (in Color c) { 964 version(aliced) pragma(inline, true); 965 r = c.r/255.0f; 966 g = c.g/255.0f; 967 b = c.b/255.0f; 968 a = c.a/255.0f; 969 } 970 } 971 } 972 973 974 /// NanoVega A-HSL color 975 /// Group: color_utils 976 public align(1) struct NVGHSL { 977 align(1): 978 float h=0, s=0, l=1, a=1; /// 979 980 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)); } 981 982 nothrow @safe @nogc: 983 public: 984 /// 985 this (float ah, float as, float al, float aa=1) pure { pragma(inline, true); h = ah; s = as; l = al; a = aa; } 986 987 NVGColor asColor () const { pragma(inline, true); return nvgHSLA(h, s, l, a); } /// 988 989 // taken from Adam's arsd.color 990 /** Converts an RGB color into an HSL triplet. 991 * [useWeightedLightness] will try to get a better value for luminosity for the human eye, 992 * which is more sensitive to green than red and more to red than blue. 993 * If it is false, it just does average of the rgb. */ 994 static NVGHSL fromColor() (const scope auto ref NVGColor c, bool useWeightedLightness=false) pure { 995 NVGHSL res; 996 res.a = c.a; 997 float r1 = c.r; 998 float g1 = c.g; 999 float b1 = c.b; 1000 1001 float maxColor = r1; 1002 if (g1 > maxColor) maxColor = g1; 1003 if (b1 > maxColor) maxColor = b1; 1004 float minColor = r1; 1005 if (g1 < minColor) minColor = g1; 1006 if (b1 < minColor) minColor = b1; 1007 1008 res.l = (maxColor+minColor)/2; 1009 if (useWeightedLightness) { 1010 // the colors don't affect the eye equally 1011 // this is a little more accurate than plain HSL numbers 1012 res.l = 0.2126*r1+0.7152*g1+0.0722*b1; 1013 } 1014 if (maxColor != minColor) { 1015 if (res.l < 0.5) { 1016 res.s = (maxColor-minColor)/(maxColor+minColor); 1017 } else { 1018 res.s = (maxColor-minColor)/(2.0-maxColor-minColor); 1019 } 1020 if (r1 == maxColor) { 1021 res.h = (g1-b1)/(maxColor-minColor); 1022 } else if(g1 == maxColor) { 1023 res.h = 2.0+(b1-r1)/(maxColor-minColor); 1024 } else { 1025 res.h = 4.0+(r1-g1)/(maxColor-minColor); 1026 } 1027 } 1028 1029 res.h = res.h*60; 1030 if (res.h < 0) res.h += 360; 1031 res.h /= 360; 1032 1033 return res; 1034 } 1035 } 1036 1037 1038 //version = nanovega_debug_image_manager; 1039 //version = nanovega_debug_image_manager_rc; 1040 1041 /** NanoVega image handle. 1042 * 1043 * This is refcounted struct, so you don't need to do anything special to free it once it is allocated. 1044 * 1045 * Group: images 1046 */ 1047 struct NVGImage { 1048 enum isOpaqueStruct = true; 1049 private: 1050 NVGContext ctx; 1051 int id; // backend image id 1052 1053 public: 1054 /// 1055 this() (const scope auto ref NVGImage src) nothrow @trusted @nogc { 1056 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); } 1057 if (src.id > 0 && src.ctx !is null) { 1058 ctx = cast(NVGContext)src.ctx; 1059 id = src.id; 1060 ctx.nvg__imageIncRef(id); 1061 } 1062 } 1063 1064 /// 1065 ~this () nothrow @trusted @nogc { version(aliced) pragma(inline, true); clear(); } 1066 1067 /// 1068 this (this) nothrow @trusted @nogc { 1069 version(aliced) pragma(inline, true); 1070 if (id > 0 && ctx !is null) { 1071 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p postblit (imgid=%d)\n", &this, id); } 1072 ctx.nvg__imageIncRef(id); 1073 } 1074 } 1075 1076 /// 1077 void opAssign() (const scope auto ref NVGImage src) nothrow @trusted @nogc { 1078 if (src.id <= 0 || src.ctx is null) { 1079 clear(); 1080 } else { 1081 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); } 1082 if (src.id > 0 && src.ctx !is null) (cast(NVGContext)src.ctx).nvg__imageIncRef(src.id); 1083 if (id > 0 && ctx !is null) ctx.nvg__imageDecRef(id); 1084 ctx = cast(NVGContext)src.ctx; 1085 id = src.id; 1086 } 1087 } 1088 1089 /// Free this image. 1090 void clear () nothrow @trusted @nogc { 1091 if (id > 0 && ctx !is null) { 1092 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p cleared (imgid=%d)\n", &this, id); } 1093 ctx.nvg__imageDecRef(id); 1094 } 1095 id = 0; 1096 ctx = null; 1097 } 1098 1099 /// Is this image valid? 1100 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (id > 0 && ctx.valid); } 1101 1102 /// Is the given image valid and comes from the same context? 1103 @property bool isSameContext (const(NVGContext) actx) const pure nothrow @safe @nogc { pragma(inline, true); return (actx !is null && ctx is actx); } 1104 1105 /// Returns image width, or zero for invalid image. 1106 int width () const nothrow @trusted @nogc { 1107 int w = 0; 1108 if (valid) { 1109 int h = void; 1110 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h); 1111 } 1112 return w; 1113 } 1114 1115 /// Returns image height, or zero for invalid image. 1116 int height () const nothrow @trusted @nogc { 1117 int h = 0; 1118 if (valid) { 1119 int w = void; 1120 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h); 1121 } 1122 return h; 1123 } 1124 } 1125 1126 1127 /// Paint parameters for various fills. Don't change anything here! 1128 /// Group: render_styles 1129 public struct NVGPaint { 1130 enum isOpaqueStruct = true; 1131 1132 NVGMatrix xform; 1133 float[2] extent = 0.0f; 1134 float radius = 0.0f; 1135 float feather = 0.0f; 1136 NVGColor innerColor; /// this can be used to modulate images (fill/font) 1137 NVGColor middleColor; 1138 NVGColor outerColor; 1139 float midp = -1; // middle stop for 3-color gradient 1140 NVGImage image; 1141 bool simpleColor; /// if `true`, only innerColor is used, and this is solid-color paint 1142 1143 this() (const scope auto ref NVGPaint p) nothrow @trusted @nogc { 1144 xform = p.xform; 1145 extent[] = p.extent[]; 1146 radius = p.radius; 1147 feather = p.feather; 1148 innerColor = p.innerColor; 1149 middleColor = p.middleColor; 1150 midp = p.midp; 1151 outerColor = p.outerColor; 1152 image = p.image; 1153 simpleColor = p.simpleColor; 1154 } 1155 1156 void opAssign() (const scope auto ref NVGPaint p) nothrow @trusted @nogc { 1157 xform = p.xform; 1158 extent[] = p.extent[]; 1159 radius = p.radius; 1160 feather = p.feather; 1161 innerColor = p.innerColor; 1162 middleColor = p.middleColor; 1163 midp = p.midp; 1164 outerColor = p.outerColor; 1165 image = p.image; 1166 simpleColor = p.simpleColor; 1167 } 1168 1169 void clear () nothrow @trusted @nogc { 1170 version(aliced) pragma(inline, true); 1171 import core.stdc.string : memset; 1172 image.clear(); 1173 memset(&this, 0, this.sizeof); 1174 simpleColor = true; 1175 } 1176 } 1177 1178 /// Path winding. 1179 /// Group: paths 1180 public enum NVGWinding { 1181 CCW = 1, /// Winding for solid shapes 1182 CW = 2, /// Winding for holes 1183 } 1184 1185 /// Path solidity. 1186 /// Group: paths 1187 public enum NVGSolidity { 1188 Solid = 1, /// Solid shape (CCW winding). 1189 Hole = 2, /// Hole (CW winding). 1190 } 1191 1192 /// Line cap style. 1193 /// Group: render_styles 1194 public enum NVGLineCap { 1195 Butt, /// 1196 Round, /// 1197 Square, /// 1198 Bevel, /// 1199 Miter, /// 1200 } 1201 1202 /// Text align. 1203 /// Group: text_api 1204 public align(1) struct NVGTextAlign { 1205 align(1): 1206 /// Horizontal align. 1207 enum H : ubyte { 1208 Left = 0, /// Default, align text horizontally to left. 1209 Center = 1, /// Align text horizontally to center. 1210 Right = 2, /// Align text horizontally to right. 1211 } 1212 1213 /// Vertical align. 1214 enum V : ubyte { 1215 Baseline = 0, /// Default, align text vertically to baseline. 1216 Top = 1, /// Align text vertically to top. 1217 Middle = 2, /// Align text vertically to middle. 1218 Bottom = 3, /// Align text vertically to bottom. 1219 } 1220 1221 pure nothrow @safe @nogc: 1222 public: 1223 this (H h) { pragma(inline, true); value = h; } /// 1224 this (V v) { pragma(inline, true); value = cast(ubyte)(v<<4); } /// 1225 this (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1226 this (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1227 void reset () { pragma(inline, true); value = 0; } /// 1228 void reset (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1229 void reset (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1230 @property: 1231 bool left () const { pragma(inline, true); return ((value&0x0f) == H.Left); } /// 1232 void left (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Left : 0)); } /// 1233 bool center () const { pragma(inline, true); return ((value&0x0f) == H.Center); } /// 1234 void center (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Center : 0)); } /// 1235 bool right () const { pragma(inline, true); return ((value&0x0f) == H.Right); } /// 1236 void right (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Right : 0)); } /// 1237 // 1238 bool baseline () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Baseline); } /// 1239 void baseline (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Baseline<<4 : 0)); } /// 1240 bool top () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Top); } /// 1241 void top (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Top<<4 : 0)); } /// 1242 bool middle () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Middle); } /// 1243 void middle (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Middle<<4 : 0)); } /// 1244 bool bottom () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Bottom); } /// 1245 void bottom (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Bottom<<4 : 0)); } /// 1246 // 1247 H horizontal () const { pragma(inline, true); return cast(H)(value&0x0f); } /// 1248 void horizontal (H v) { pragma(inline, true); value = (value&0xf0)|v; } /// 1249 // 1250 V vertical () const { pragma(inline, true); return cast(V)((value>>4)&0x0f); } /// 1251 void vertical (V v) { pragma(inline, true); value = (value&0x0f)|cast(ubyte)(v<<4); } /// 1252 // 1253 private: 1254 ubyte value = 0; // low nibble: horizontal; high nibble: vertical 1255 } 1256 1257 /// Blending type. 1258 /// Group: composite_operation 1259 public enum NVGBlendFactor { 1260 Zero = 1<<0, /// 1261 One = 1<<1, /// 1262 SrcColor = 1<<2, /// 1263 OneMinusSrcColor = 1<<3, /// 1264 DstColor = 1<<4, /// 1265 OneMinusDstColor = 1<<5, /// 1266 SrcAlpha = 1<<6, /// 1267 OneMinusSrcAlpha = 1<<7, /// 1268 DstAlpha = 1<<8, /// 1269 OneMinusDstAlpha = 1<<9, /// 1270 SrcAlphaSaturate = 1<<10, /// 1271 } 1272 1273 /// Composite operation (HTML5-alike). 1274 /// Group: composite_operation 1275 public enum NVGCompositeOperation { 1276 SourceOver, /// 1277 SourceIn, /// 1278 SourceOut, /// 1279 SourceAtop, /// 1280 DestinationOver, /// 1281 DestinationIn, /// 1282 DestinationOut, /// 1283 DestinationAtop, /// 1284 Lighter, /// 1285 Copy, /// 1286 Xor, /// 1287 } 1288 1289 /// Composite operation state. 1290 /// Group: composite_operation 1291 public struct NVGCompositeOperationState { 1292 bool simple; /// `true`: use `glBlendFunc()` instead of `glBlendFuncSeparate()` 1293 NVGBlendFactor srcRGB; /// 1294 NVGBlendFactor dstRGB; /// 1295 NVGBlendFactor srcAlpha; /// 1296 NVGBlendFactor dstAlpha; /// 1297 } 1298 1299 /// Mask combining more 1300 /// Group: clipping 1301 public enum NVGClipMode { 1302 None, /// normal rendering (i.e. render path instead of modifying clip region) 1303 Union, /// old mask will be masked with the current one; this is the default mode for [clip] 1304 Or, /// new mask will be added to the current one (logical `OR` operation); 1305 Xor, /// new mask will be logically `XOR`ed with the current one 1306 Sub, /// "subtract" current path from mask 1307 Replace, /// replace current mask 1308 Add = Or, /// Synonym 1309 } 1310 1311 /// Glyph position info. 1312 /// Group: text_api 1313 public struct NVGGlyphPosition { 1314 usize strpos; /// Position of the glyph in the input string. 1315 float x; /// The x-coordinate of the logical glyph position. 1316 float minx, maxx; /// The bounds of the glyph shape. 1317 } 1318 1319 /// Text row storage. 1320 /// Group: text_api 1321 public struct NVGTextRow(CT) if (isAnyCharType!CT) { 1322 alias CharType = CT; 1323 const(CT)[] s; 1324 int start; /// Index in the input text where the row starts. 1325 int end; /// Index in the input text where the row ends (one past the last character). 1326 float width; /// Logical width of the row. 1327 float minx, maxx; /// Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending. 1328 /// Get rest of the string. 1329 @property const(CT)[] rest () const pure nothrow @trusted @nogc { pragma(inline, true); return (end <= s.length ? s[end..$] : null); } 1330 /// Get current row. 1331 @property const(CT)[] row () const pure nothrow @trusted @nogc { pragma(inline, true); return s[start..end]; } 1332 @property const(CT)[] string () const pure nothrow @trusted @nogc { pragma(inline, true); return s; } 1333 @property void string(CT) (const(CT)[] v) pure nothrow @trusted @nogc { pragma(inline, true); s = v; } 1334 } 1335 1336 /// Image creation flags. 1337 /// Group: images 1338 public enum NVGImageFlag : uint { 1339 None = 0, /// Nothing special. 1340 GenerateMipmaps = 1<<0, /// Generate mipmaps during creation of the image. 1341 RepeatX = 1<<1, /// Repeat image in X direction. 1342 RepeatY = 1<<2, /// Repeat image in Y direction. 1343 FlipY = 1<<3, /// Flips (inverses) image in Y direction when rendered. 1344 Premultiplied = 1<<4, /// Image data has premultiplied alpha. 1345 ClampToBorderX = 1<<5, /// Clamp image to border (instead of clamping to edge by default) 1346 ClampToBorderY = 1<<6, /// Clamp image to border (instead of clamping to edge by default) 1347 NoFiltering = 1<<8, /// use GL_NEAREST instead of GL_LINEAR. Only affects upscaling if GenerateMipmaps is active. 1348 Nearest = NoFiltering, /// compatibility with original NanoVG 1349 NoDelete = 1<<16,/// Do not delete GL texture handle. 1350 } 1351 1352 alias NVGImageFlags = NVGImageFlag; /// Backwards compatibility for [NVGImageFlag]. 1353 1354 1355 // ////////////////////////////////////////////////////////////////////////// // 1356 private: 1357 1358 static T* xdup(T) (const(T)* ptr, int count) nothrow @trusted @nogc { 1359 import core.stdc.stdlib : malloc; 1360 import core.stdc.string : memcpy; 1361 if (count == 0) return null; 1362 T* res = cast(T*)malloc(T.sizeof*count); 1363 if (res is null) assert(0, "NanoVega: out of memory"); 1364 memcpy(res, ptr, T.sizeof*count); 1365 return res; 1366 } 1367 1368 // Internal Render API 1369 enum NVGtexture { 1370 Alpha = 0x01, 1371 RGBA = 0x02, 1372 } 1373 1374 struct NVGscissor { 1375 NVGMatrix xform; 1376 float[2] extent = -1.0f; 1377 } 1378 1379 /// General NanoVega vertex struct. Contains geometry coordinates, and (sometimes unused) texture coordinates. 1380 public struct NVGVertex { 1381 float x, y, u, v; 1382 } 1383 1384 struct NVGpath { 1385 int first; 1386 int count; 1387 bool closed; 1388 int nbevel; 1389 NVGVertex* fill; 1390 int nfill; 1391 NVGVertex* stroke; 1392 int nstroke; 1393 NVGWinding mWinding; 1394 bool convex; 1395 bool cloned; 1396 1397 @disable this (this); // no copies 1398 void opAssign() (const scope auto ref NVGpath a) { static assert(0, "no copies!"); } 1399 1400 void clear () nothrow @trusted @nogc { 1401 import core.stdc.stdlib : free; 1402 import core.stdc.string : memset; 1403 if (cloned) { 1404 if (stroke !is null && stroke !is fill) free(stroke); 1405 if (fill !is null) free(fill); 1406 } 1407 memset(&this, 0, this.sizeof); 1408 } 1409 1410 // won't clear current path 1411 void copyFrom (const NVGpath* src) nothrow @trusted @nogc { 1412 import core.stdc.string : memcpy; 1413 assert(src !is null); 1414 memcpy(&this, src, NVGpath.sizeof); 1415 this.fill = xdup(src.fill, src.nfill); 1416 if (src.stroke is src.fill) { 1417 this.stroke = this.fill; 1418 } else { 1419 this.stroke = xdup(src.stroke, src.nstroke); 1420 } 1421 this.cloned = true; 1422 } 1423 1424 public @property const(NVGVertex)[] fillVertices () const pure nothrow @trusted @nogc { 1425 pragma(inline, true); 1426 return (nfill > 0 ? fill[0..nfill] : null); 1427 } 1428 1429 public @property const(NVGVertex)[] strokeVertices () const pure nothrow @trusted @nogc { 1430 pragma(inline, true); 1431 return (nstroke > 0 ? stroke[0..nstroke] : null); 1432 } 1433 1434 public @property NVGWinding winding () const pure nothrow @trusted @nogc { pragma(inline, true); return mWinding; } 1435 public @property bool complex () const pure nothrow @trusted @nogc { pragma(inline, true); return !convex; } 1436 } 1437 1438 1439 struct NVGparams { 1440 void* userPtr; 1441 bool edgeAntiAlias; 1442 bool fontAA; 1443 bool function (void* uptr) nothrow @trusted @nogc renderCreate; 1444 int function (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc renderCreateTexture; 1445 bool function (void* uptr, int image) nothrow @trusted @nogc renderTextureIncRef; 1446 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 1447 bool function (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc renderUpdateTexture; 1448 bool function (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc renderGetTextureSize; 1449 void function (void* uptr, int width, int height) nothrow @trusted @nogc renderViewport; // called in [beginFrame] 1450 void function (void* uptr) nothrow @trusted @nogc renderCancel; 1451 void function (void* uptr) nothrow @trusted @nogc renderFlush; 1452 void function (void* uptr) nothrow @trusted @nogc renderPushClip; // backend should support stack of at least [NVG_MAX_STATES] elements 1453 void function (void* uptr) nothrow @trusted @nogc renderPopClip; // backend should support stack of at least [NVG_MAX_STATES] elements 1454 void function (void* uptr) nothrow @trusted @nogc renderResetClip; // reset current clip region to `non-clipped` 1455 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; 1456 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; 1457 void function (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc renderTriangles; 1458 void function (void* uptr, const scope ref NVGMatrix mat) nothrow @trusted @nogc renderSetAffine; 1459 void function (void* uptr) nothrow @trusted @nogc renderDelete; 1460 } 1461 1462 // ////////////////////////////////////////////////////////////////////////// // 1463 private: 1464 1465 enum NVG_INIT_FONTIMAGE_SIZE = 512; 1466 enum NVG_MAX_FONTIMAGE_SIZE = 2048; 1467 enum NVG_MAX_FONTIMAGES = 4; 1468 1469 enum NVG_INIT_COMMANDS_SIZE = 256; 1470 enum NVG_INIT_POINTS_SIZE = 128; 1471 enum NVG_INIT_PATHS_SIZE = 16; 1472 enum NVG_INIT_VERTS_SIZE = 256; 1473 enum NVG_MAX_STATES = 32; 1474 1475 public enum NVG_KAPPA90 = 0.5522847493f; /// Length proportional to radius of a cubic bezier handle for 90deg arcs. 1476 enum NVG_MIN_FEATHER = 0.001f; // it should be greater than zero, 'cause it is used in shader for divisions 1477 1478 enum Command { 1479 MoveTo = 0, 1480 LineTo = 1, 1481 BezierTo = 2, 1482 Close = 3, 1483 Winding = 4, 1484 } 1485 1486 enum PointFlag : int { 1487 Corner = 0x01, 1488 Left = 0x02, 1489 Bevel = 0x04, 1490 InnerBevelPR = 0x08, 1491 } 1492 1493 struct NVGstate { 1494 NVGCompositeOperationState compositeOperation; 1495 bool shapeAntiAlias = true; 1496 NVGPaint fill; 1497 NVGPaint stroke; 1498 float strokeWidth = 1.0f; 1499 float miterLimit = 10.0f; 1500 NVGLineCap lineJoin = NVGLineCap.Miter; 1501 NVGLineCap lineCap = NVGLineCap.Butt; 1502 float alpha = 1.0f; 1503 NVGMatrix xform; 1504 NVGscissor scissor; 1505 float fontSize = 16.0f; 1506 float letterSpacing = 0.0f; 1507 float lineHeight = 1.0f; 1508 float fontBlur = 0.0f; 1509 NVGTextAlign textAlign; 1510 int fontId = 0; 1511 bool evenOddMode = false; // use even-odd filling rule (required for some svgs); otherwise use non-zero fill 1512 // dashing 1513 enum MaxDashes = 32; // max 16 dashes 1514 float[MaxDashes] dashes; 1515 uint dashCount = 0; 1516 uint lastFlattenDashCount = 0; 1517 float dashStart = 0; 1518 float totalDashLen; 1519 bool firstDashIsGap = false; 1520 // dasher state for flattener 1521 bool dasherActive = false; 1522 1523 void clearPaint () nothrow @trusted @nogc { 1524 fill.clear(); 1525 stroke.clear(); 1526 } 1527 } 1528 1529 struct NVGpoint { 1530 float x, y; 1531 float dx, dy; 1532 float len; 1533 float dmx, dmy; 1534 ubyte flags; 1535 } 1536 1537 struct NVGpathCache { 1538 NVGpoint* points; 1539 int npoints; 1540 int cpoints; 1541 NVGpath* paths; 1542 int npaths; 1543 int cpaths; 1544 NVGVertex* verts; 1545 int nverts; 1546 int cverts; 1547 float[4] bounds; 1548 // this is required for saved paths 1549 bool strokeReady; 1550 bool fillReady; 1551 float strokeAlphaMul; 1552 float strokeWidth; 1553 float fringeWidth; 1554 bool evenOddMode; 1555 NVGClipMode clipmode; 1556 // non-saved path will not have this 1557 float* commands; 1558 int ncommands; 1559 1560 @disable this (this); // no copies 1561 void opAssign() (const scope auto ref NVGpathCache a) { static assert(0, "no copies!"); } 1562 1563 // won't clear current path 1564 void copyFrom (const NVGpathCache* src) nothrow @trusted @nogc { 1565 import core.stdc.stdlib : malloc; 1566 import core.stdc.string : memcpy, memset; 1567 assert(src !is null); 1568 memcpy(&this, src, NVGpathCache.sizeof); 1569 this.points = xdup(src.points, src.npoints); 1570 this.cpoints = src.npoints; 1571 this.verts = xdup(src.verts, src.nverts); 1572 this.cverts = src.nverts; 1573 this.commands = xdup(src.commands, src.ncommands); 1574 if (src.npaths > 0) { 1575 this.paths = cast(NVGpath*)malloc(src.npaths*NVGpath.sizeof); 1576 memset(this.paths, 0, npaths*NVGpath.sizeof); 1577 foreach (immutable pidx; 0..npaths) this.paths[pidx].copyFrom(&src.paths[pidx]); 1578 this.cpaths = src.npaths; 1579 } else { 1580 this.npaths = this.cpaths = 0; 1581 } 1582 } 1583 1584 void clear () nothrow @trusted @nogc { 1585 import core.stdc.stdlib : free; 1586 import core.stdc.string : memset; 1587 if (paths !is null) { 1588 foreach (ref p; paths[0..npaths]) p.clear(); 1589 free(paths); 1590 } 1591 if (points !is null) free(points); 1592 if (verts !is null) free(verts); 1593 if (commands !is null) free(commands); 1594 memset(&this, 0, this.sizeof); 1595 } 1596 } 1597 1598 /// Pointer to opaque NanoVega context structure. 1599 /// Group: context_management 1600 public alias NVGContext = NVGcontextinternal*; 1601 1602 /// FontStash context 1603 /// Group: font_stash 1604 public alias FONSContext = FONScontextInternal*; 1605 1606 /// Returns FontStash context of the given NanoVega context. 1607 /// Group: font_stash 1608 public FONSContext fonsContext (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.fs : null); } 1609 1610 /// Returns scale that should be applied to FontStash parameters due to matrix transformations on the context (or 1) 1611 /// Group: font_stash 1612 public float fonsScale (NVGContext ctx) /*pure*/ nothrow @trusted @nogc { 1613 pragma(inline, true); 1614 return (ctx !is null && ctx.contextAlive && ctx.nstates > 0 ? nvg__getFontScale(&ctx.states.ptr[ctx.nstates-1])*ctx.devicePxRatio : 1); 1615 } 1616 1617 /// Setup FontStash from the given NanoVega context font parameters. Note that this will apply transformation scale too. 1618 /// Returns `false` if FontStash or NanoVega context is not active. 1619 /// Group: font_stash 1620 public bool setupFonsFrom (FONSContext stash, NVGContext ctx) nothrow @trusted @nogc { 1621 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false; 1622 NVGstate* state = nvg__getState(ctx); 1623 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 1624 stash.size = state.fontSize*scale; 1625 stash.spacing = state.letterSpacing*scale; 1626 stash.blur = state.fontBlur*scale; 1627 stash.textAlign = state.textAlign; 1628 stash.fontId = state.fontId; 1629 return true; 1630 } 1631 1632 /// Setup NanoVega context font parameters from the given FontStash. Note that NanoVega can apply transformation scale later. 1633 /// Returns `false` if FontStash or NanoVega context is not active. 1634 /// Group: font_stash 1635 public bool setupCtxFrom (NVGContext ctx, FONSContext stash) nothrow @trusted @nogc { 1636 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false; 1637 NVGstate* state = nvg__getState(ctx); 1638 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 1639 state.fontSize = stash.size; 1640 state.letterSpacing = stash.spacing; 1641 state.fontBlur = stash.blur; 1642 state.textAlign = stash.textAlign; 1643 state.fontId = stash.fontId; 1644 return true; 1645 } 1646 1647 /** Bezier curve rasterizer. 1648 * 1649 * De Casteljau Bezier rasterizer is faster, but currently rasterizing curves with cusps sligtly wrong. 1650 * It doesn't really matter in practice. 1651 * 1652 * AFD tesselator is somewhat slower, but does cusps better. 1653 * 1654 * McSeem rasterizer should have the best quality, bit it is the slowest method. Basically, you will 1655 * never notice any visial difference (and this code is not really debugged), so you probably should 1656 * not use it. It is there for further experiments. 1657 */ 1658 public enum NVGTesselation { 1659 DeCasteljau, /// default: standard well-known tesselation algorithm 1660 AFD, /// adaptive forward differencing 1661 DeCasteljauMcSeem, /// standard well-known tesselation algorithm, with improvements from Maxim Shemanarev; slowest one, but should give best results 1662 } 1663 1664 /// Default tesselator for Bezier curves. 1665 public __gshared NVGTesselation NVG_DEFAULT_TESSELATOR = NVGTesselation.DeCasteljau; 1666 1667 1668 // some public info 1669 1670 /// valid only inside [beginFrame]/[endFrame] 1671 /// Group: context_management 1672 public int width (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mWidth : 0); } 1673 1674 /// valid only inside [beginFrame]/[endFrame] 1675 /// Group: context_management 1676 public int height (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mHeight : 0); } 1677 1678 /// valid only inside [beginFrame]/[endFrame] 1679 /// Group: context_management 1680 public float devicePixelRatio (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.devicePxRatio : float.nan); } 1681 1682 /// Returns `true` if [beginFrame] was called, and neither [endFrame], nor [cancelFrame] were. 1683 /// Group: context_management 1684 public bool inFrame (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.nstates > 0 : false); } 1685 1686 // path autoregistration 1687 1688 /// [pickid] to stop autoregistration. 1689 /// Group: context_management 1690 public enum NVGNoPick = -1; 1691 1692 /// >=0: this pickid will be assigned to all filled/stroked paths 1693 /// Group: context_management 1694 public int pickid (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickId : NVGNoPick); } 1695 1696 /// >=0: this pickid will be assigned to all filled/stroked paths 1697 /// Group: context_management 1698 public void pickid (NVGContext ctx, int v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.pathPickId = v; } 1699 1700 /// pick autoregistration mode; see [NVGPickKind] 1701 /// Group: context_management 1702 public uint pickmode (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickRegistered&NVGPickKind.All : 0); } 1703 1704 /// pick autoregistration mode; see [NVGPickKind] 1705 /// Group: context_management 1706 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); } 1707 1708 // tesselator options 1709 1710 /// Get current Bezier tesselation mode. See [NVGTesselation]. 1711 /// Group: context_management 1712 public NVGTesselation tesselation (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.tesselatortype : NVGTesselation.DeCasteljau); } 1713 1714 /// Set current Bezier tesselation mode. See [NVGTesselation]. 1715 /// Group: context_management 1716 public void tesselation (NVGContext ctx, NVGTesselation v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.tesselatortype = v; } 1717 1718 1719 private struct NVGcontextinternal { 1720 private: 1721 NVGparams params; 1722 float* commands; 1723 int ccommands; 1724 int ncommands; 1725 float commandx, commandy; 1726 NVGstate[NVG_MAX_STATES] states; 1727 int nstates; 1728 NVGpathCache* cache; 1729 public float tessTol; 1730 public float angleTol; // 0.0f -- angle tolerance for McSeem Bezier rasterizer 1731 public float cuspLimit; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps) 1732 float distTol; 1733 public float fringeWidth; 1734 float devicePxRatio; 1735 FONSContext fs; 1736 NVGImage[NVG_MAX_FONTIMAGES] fontImages; 1737 int fontImageIdx; 1738 int drawCallCount; 1739 int fillTriCount; 1740 int strokeTriCount; 1741 int textTriCount; 1742 NVGTesselation tesselatortype; 1743 // picking API 1744 NVGpickScene* pickScene; 1745 int pathPickId; // >=0: register all paths for picking using this id 1746 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 1747 // path recording 1748 NVGPathSet recset; 1749 int recstart; // used to cancel recording 1750 bool recblockdraw; 1751 // internals 1752 NVGMatrix gpuAffine; 1753 int mWidth, mHeight; 1754 // image manager 1755 shared int imageCount; // number of alive images in this context 1756 bool contextAlive; // context can be dead, but still contain some images 1757 1758 @disable this (this); // no copies 1759 void opAssign() (const scope auto ref NVGcontextinternal a) { static assert(0, "no copies!"); } 1760 1761 // debug feature 1762 public @property int getImageCount () nothrow @trusted @nogc { 1763 import core.atomic; 1764 return atomicLoad(imageCount); 1765 } 1766 } 1767 1768 /** Returns number of tesselated pathes in context. 1769 * 1770 * Tesselated pathes are either triangle strips (for strokes), or 1771 * triangle fans (for fills). Note that NanoVega can generate some 1772 * surprising pathes (like fringe stroke for antialiasing, for example). 1773 * 1774 * One render path can contain vertices both for fill, and for stroke triangles. 1775 */ 1776 public int renderPathCount (NVGContext ctx) pure nothrow @trusted @nogc { 1777 pragma(inline, true); 1778 return (ctx !is null && ctx.contextAlive ? ctx.cache.npaths : 0); 1779 } 1780 1781 /** Get vertices of "fill" triangle fan for the given render path. Can return empty slice. 1782 * 1783 * $(WARNING Returned slice can be invalidated by any other NanoVega API call 1784 * (except the calls to render path accessors), and using it in such 1785 * case is UB. So copy vertices to freshly allocated array if you want 1786 * to keep them for further processing.) 1787 */ 1788 public const(NVGVertex)[] renderPathFillVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1789 pragma(inline, true); 1790 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].fillVertices : null); 1791 } 1792 1793 /** Get vertices of "stroke" triangle strip for the given render path. Can return empty slice. 1794 * 1795 * $(WARNING Returned slice can be invalidated by any other NanoVega API call 1796 * (except the calls to render path accessors), and using it in such 1797 * case is UB. So copy vertices to freshly allocated array if you want 1798 * to keep them for further processing.) 1799 */ 1800 public const(NVGVertex)[] renderPathStrokeVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1801 pragma(inline, true); 1802 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].strokeVertices : null); 1803 1804 } 1805 1806 /// Returns winding for the given render path. 1807 public NVGWinding renderPathWinding (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1808 pragma(inline, true); 1809 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].winding : NVGWinding.CCW); 1810 1811 } 1812 1813 /// Returns "complex path" flag for the given render path. 1814 public bool renderPathComplex (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1815 pragma(inline, true); 1816 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].complex : false); 1817 1818 } 1819 1820 void nvg__imageIncRef (NVGContext ctx, int imgid, bool increfInGL=true) nothrow @trusted @nogc { 1821 if (ctx !is null && imgid > 0) { 1822 import core.atomic : atomicOp; 1823 atomicOp!"+="(ctx.imageCount, 1); 1824 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[++]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); } 1825 if (ctx.contextAlive && increfInGL) ctx.params.renderTextureIncRef(ctx.params.userPtr, imgid); 1826 } 1827 } 1828 1829 void nvg__imageDecRef (NVGContext ctx, int imgid) nothrow @trusted @nogc { 1830 if (ctx !is null && imgid > 0) { 1831 import core.atomic : atomicOp; 1832 int icnt = atomicOp!"-="(ctx.imageCount, 1); 1833 if (icnt < 0) assert(0, "NanoVega: internal image refcounting error"); 1834 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[--]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); } 1835 if (ctx.contextAlive) ctx.params.renderDeleteTexture(ctx.params.userPtr, imgid); 1836 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); } 1837 if (!ctx.contextAlive && icnt == 0) { 1838 // it is finally safe to free context memory 1839 import core.stdc.stdlib : free; 1840 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("killed zombie context %p\n", ctx); } 1841 free(ctx); 1842 } 1843 } 1844 } 1845 1846 1847 public import core.stdc.math : 1848 nvg__sqrtf = sqrtf, 1849 nvg__modf = fmodf, 1850 nvg__sinf = sinf, 1851 nvg__cosf = cosf, 1852 nvg__tanf = tanf, 1853 nvg__atan2f = atan2f, 1854 nvg__acosf = acosf, 1855 nvg__ceilf = ceilf; 1856 1857 version(Windows) { 1858 public int nvg__lrintf (float f) nothrow @trusted @nogc { pragma(inline, true); return cast(int)(f+0.5); } 1859 } else { 1860 public import core.stdc.math : nvg__lrintf = lrintf; 1861 } 1862 1863 public auto nvg__min(T) (T a, T b) { pragma(inline, true); return (a < b ? a : b); } 1864 public auto nvg__max(T) (T a, T b) { pragma(inline, true); return (a > b ? a : b); } 1865 public auto nvg__clamp(T) (T a, T mn, T mx) { pragma(inline, true); return (a < mn ? mn : (a > mx ? mx : a)); } 1866 //float nvg__absf() (float a) { pragma(inline, true); return (a >= 0.0f ? a : -a); } 1867 public auto nvg__sign(T) (T a) { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); } 1868 public float nvg__cross() (float dx0, float dy0, float dx1, float dy1) { pragma(inline, true); return (dx1*dy0-dx0*dy1); } 1869 1870 //public import core.stdc.math : nvg__absf = fabsf; 1871 public import core.math : nvg__absf = fabs; 1872 1873 1874 float nvg__normalize (float* x, float* y) nothrow @safe @nogc { 1875 float d = nvg__sqrtf((*x)*(*x)+(*y)*(*y)); 1876 if (d > 1e-6f) { 1877 immutable float id = 1.0f/d; 1878 *x *= id; 1879 *y *= id; 1880 } 1881 return d; 1882 } 1883 1884 void nvg__deletePathCache (ref NVGpathCache* c) nothrow @trusted @nogc { 1885 if (c !is null) { 1886 c.clear(); 1887 free(c); 1888 } 1889 } 1890 1891 NVGpathCache* nvg__allocPathCache () nothrow @trusted @nogc { 1892 NVGpathCache* c = cast(NVGpathCache*)malloc(NVGpathCache.sizeof); 1893 if (c is null) goto error; 1894 memset(c, 0, NVGpathCache.sizeof); 1895 1896 c.points = cast(NVGpoint*)malloc(NVGpoint.sizeof*NVG_INIT_POINTS_SIZE); 1897 if (c.points is null) goto error; 1898 assert(c.npoints == 0); 1899 c.cpoints = NVG_INIT_POINTS_SIZE; 1900 1901 c.paths = cast(NVGpath*)malloc(NVGpath.sizeof*NVG_INIT_PATHS_SIZE); 1902 if (c.paths is null) goto error; 1903 assert(c.npaths == 0); 1904 c.cpaths = NVG_INIT_PATHS_SIZE; 1905 1906 c.verts = cast(NVGVertex*)malloc(NVGVertex.sizeof*NVG_INIT_VERTS_SIZE); 1907 if (c.verts is null) goto error; 1908 assert(c.nverts == 0); 1909 c.cverts = NVG_INIT_VERTS_SIZE; 1910 1911 return c; 1912 1913 error: 1914 nvg__deletePathCache(c); 1915 return null; 1916 } 1917 1918 void nvg__setDevicePixelRatio (NVGContext ctx, float ratio) pure nothrow @safe @nogc { 1919 ctx.tessTol = 0.25f/ratio; 1920 ctx.distTol = 0.01f/ratio; 1921 ctx.fringeWidth = 1.0f/ratio; 1922 ctx.devicePxRatio = ratio; 1923 } 1924 1925 NVGCompositeOperationState nvg__compositeOperationState (NVGCompositeOperation op) pure nothrow @safe @nogc { 1926 NVGCompositeOperationState state; 1927 NVGBlendFactor sfactor, dfactor; 1928 1929 if (op == NVGCompositeOperation.SourceOver) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha;} 1930 else if (op == NVGCompositeOperation.SourceIn) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.Zero; } 1931 else if (op == NVGCompositeOperation.SourceOut) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.Zero; } 1932 else if (op == NVGCompositeOperation.SourceAtop) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } 1933 else if (op == NVGCompositeOperation.DestinationOver) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.One; } 1934 else if (op == NVGCompositeOperation.DestinationIn) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.SrcAlpha; } 1935 else if (op == NVGCompositeOperation.DestinationOut) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } 1936 else if (op == NVGCompositeOperation.DestinationAtop) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.SrcAlpha; } 1937 else if (op == NVGCompositeOperation.Lighter) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.One; } 1938 else if (op == NVGCompositeOperation.Copy) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.Zero; } 1939 else if (op == NVGCompositeOperation.Xor) { 1940 state.simple = false; 1941 state.srcRGB = NVGBlendFactor.OneMinusDstColor; 1942 state.srcAlpha = NVGBlendFactor.OneMinusDstAlpha; 1943 state.dstRGB = NVGBlendFactor.OneMinusSrcColor; 1944 state.dstAlpha = NVGBlendFactor.OneMinusSrcAlpha; 1945 return state; 1946 } 1947 else { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } // default value for invalid op: SourceOver 1948 1949 state.simple = true; 1950 state.srcAlpha = sfactor; 1951 state.dstAlpha = dfactor; 1952 return state; 1953 } 1954 1955 NVGstate* nvg__getState (NVGContext ctx) pure nothrow @trusted @nogc { 1956 pragma(inline, true); 1957 if (ctx is null || !ctx.contextAlive || ctx.nstates == 0) assert(0, "NanoVega: cannot perform commands on inactive context"); 1958 return &ctx.states.ptr[ctx.nstates-1]; 1959 } 1960 1961 // Constructor called by the render back-end. 1962 NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc { 1963 FONSParams fontParams; 1964 NVGContext ctx = cast(NVGContext)malloc(NVGcontextinternal.sizeof); 1965 if (ctx is null) goto error; 1966 memset(ctx, 0, NVGcontextinternal.sizeof); 1967 1968 ctx.angleTol = 0; // angle tolerance for McSeem Bezier rasterizer 1969 ctx.cuspLimit = 0; // cusp limit for McSeem Bezier rasterizer (0: real cusps) 1970 1971 ctx.contextAlive = true; 1972 1973 ctx.params = *params; 1974 //ctx.fontImages[0..NVG_MAX_FONTIMAGES] = 0; 1975 1976 ctx.commands = cast(float*)malloc(float.sizeof*NVG_INIT_COMMANDS_SIZE); 1977 if (ctx.commands is null) goto error; 1978 ctx.ncommands = 0; 1979 ctx.ccommands = NVG_INIT_COMMANDS_SIZE; 1980 1981 ctx.cache = nvg__allocPathCache(); 1982 if (ctx.cache is null) goto error; 1983 1984 ctx.save(); 1985 ctx.reset(); 1986 1987 nvg__setDevicePixelRatio(ctx, 1.0f); 1988 ctx.mWidth = ctx.mHeight = 0; 1989 1990 if (!ctx.params.renderCreate(ctx.params.userPtr)) goto error; 1991 1992 // init font rendering 1993 memset(&fontParams, 0, fontParams.sizeof); 1994 fontParams.width = NVG_INIT_FONTIMAGE_SIZE; 1995 fontParams.height = NVG_INIT_FONTIMAGE_SIZE; 1996 fontParams.flags = FONSParams.Flag.ZeroTopLeft; 1997 fontParams.renderCreate = null; 1998 fontParams.renderUpdate = null; 1999 fontParams.renderDelete = null; 2000 fontParams.userPtr = null; 2001 ctx.fs = FONSContext.create(fontParams); 2002 if (ctx.fs is null) goto error; 2003 2004 // create font texture 2005 ctx.fontImages[0].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, fontParams.width, fontParams.height, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); 2006 if (ctx.fontImages[0].id == 0) goto error; 2007 ctx.fontImages[0].ctx = ctx; 2008 ctx.nvg__imageIncRef(ctx.fontImages[0].id, false); // don't increment driver refcount 2009 ctx.fontImageIdx = 0; 2010 2011 ctx.pathPickId = -1; 2012 ctx.tesselatortype = NVG_DEFAULT_TESSELATOR; 2013 2014 return ctx; 2015 2016 error: 2017 ctx.deleteInternal(); 2018 return null; 2019 } 2020 2021 // Called by render backend. 2022 NVGparams* internalParams (NVGContext ctx) nothrow @trusted @nogc { 2023 return &ctx.params; 2024 } 2025 2026 // Destructor called by the render back-end. 2027 void deleteInternal (ref NVGContext ctx) nothrow @trusted @nogc { 2028 if (ctx is null) return; 2029 if (ctx.contextAlive) { 2030 if (ctx.commands !is null) free(ctx.commands); 2031 nvg__deletePathCache(ctx.cache); 2032 2033 if (ctx.fs) ctx.fs.kill(); 2034 2035 foreach (uint i; 0..NVG_MAX_FONTIMAGES) ctx.fontImages[i].clear(); 2036 2037 if (ctx.params.renderDelete !is null) ctx.params.renderDelete(ctx.params.userPtr); 2038 2039 if (ctx.pickScene !is null) nvg__deletePickScene(ctx.pickScene); 2040 2041 ctx.contextAlive = false; 2042 2043 import core.atomic : atomicLoad; 2044 if (atomicLoad(ctx.imageCount) == 0) { 2045 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("destroyed context %p\n", ctx); } 2046 free(ctx); 2047 } else { 2048 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("context %p is zombie now (%d image refs)\n", ctx, ctx.imageCount); } 2049 } 2050 } 2051 } 2052 2053 /// Delete NanoVega context. 2054 /// Group: context_management 2055 public void kill (ref NVGContext ctx) nothrow @trusted @nogc { 2056 if (ctx !is null) { 2057 ctx.deleteInternal(); 2058 ctx = null; 2059 } 2060 } 2061 2062 /// Returns `true` if the given context is not `null` and can be used for painting. 2063 /// Group: context_management 2064 public bool valid (in NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive); } 2065 2066 2067 // ////////////////////////////////////////////////////////////////////////// // 2068 // Frame Management 2069 2070 /** Begin drawing a new frame. 2071 * 2072 * Calls to NanoVega drawing API should be wrapped in [beginFrame] and [endFrame] 2073 * 2074 * [beginFrame] defines the size of the window to render to in relation currently 2075 * set viewport (i.e. glViewport on GL backends). Device pixel ration allows to 2076 * control the rendering on Hi-DPI devices. 2077 * 2078 * For example, GLFW returns two dimension for an opened window: window size and 2079 * frame buffer size. In that case you would set windowWidth/windowHeight to the window size, 2080 * devicePixelRatio to: `windowWidth/windowHeight`. 2081 * 2082 * Default ratio is `1`. 2083 * 2084 * Note that fractional ratio can (and will) distort your fonts and images. 2085 * 2086 * This call also resets pick marks (see picking API for non-rasterized paths), 2087 * path recording, and GPU affine transformatin matrix. 2088 * 2089 * see also [glNVGClearFlags], which returns necessary flags for [glClear]. 2090 * 2091 * Group: frame_management 2092 */ 2093 public void beginFrame (NVGContext ctx, int windowWidth, int windowHeight, float devicePixelRatio=1.0f) nothrow @trusted @nogc { 2094 import std.math : isNaN; 2095 /* 2096 printf("Tris: draws:%d fill:%d stroke:%d text:%d TOT:%d\n", 2097 ctx.drawCallCount, ctx.fillTriCount, ctx.strokeTriCount, ctx.textTriCount, 2098 ctx.fillTriCount+ctx.strokeTriCount+ctx.textTriCount); 2099 */ 2100 if (ctx.nstates > 0) ctx.cancelFrame(); 2101 2102 if (windowWidth < 1) windowWidth = 1; 2103 if (windowHeight < 1) windowHeight = 1; 2104 2105 if (isNaN(devicePixelRatio)) devicePixelRatio = (windowHeight > 0 ? cast(float)windowWidth/cast(float)windowHeight : 1024.0/768.0); 2106 2107 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2108 ctx.nstates = 0; 2109 ctx.save(); 2110 ctx.reset(); 2111 2112 nvg__setDevicePixelRatio(ctx, devicePixelRatio); 2113 2114 ctx.params.renderViewport(ctx.params.userPtr, windowWidth, windowHeight); 2115 ctx.mWidth = windowWidth; 2116 ctx.mHeight = windowHeight; 2117 2118 ctx.recset = null; 2119 ctx.recstart = -1; 2120 2121 ctx.pathPickId = NVGNoPick; 2122 ctx.pathPickRegistered = 0; 2123 2124 ctx.drawCallCount = 0; 2125 ctx.fillTriCount = 0; 2126 ctx.strokeTriCount = 0; 2127 ctx.textTriCount = 0; 2128 2129 ctx.ncommands = 0; 2130 ctx.pathPickRegistered = 0; 2131 nvg__clearPathCache(ctx); 2132 2133 ctx.gpuAffine = NVGMatrix.Identity; 2134 2135 nvg__pickBeginFrame(ctx, windowWidth, windowHeight); 2136 } 2137 2138 /// Cancels drawing the current frame. Cancels path recording. 2139 /// Group: frame_management 2140 public void cancelFrame (NVGContext ctx) nothrow @trusted @nogc { 2141 ctx.cancelRecording(); 2142 //ctx.mWidth = 0; 2143 //ctx.mHeight = 0; 2144 // cancel render queue 2145 ctx.params.renderCancel(ctx.params.userPtr); 2146 // clear saved states (this may free some textures) 2147 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2148 ctx.nstates = 0; 2149 } 2150 2151 /// Ends drawing the current frame (flushing remaining render state). Commits recorded paths. 2152 /// Group: frame_management 2153 public void endFrame (NVGContext ctx) nothrow @trusted @nogc { 2154 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx); 2155 ctx.stopRecording(); 2156 //ctx.mWidth = 0; 2157 //ctx.mHeight = 0; 2158 // flush render queue 2159 NVGstate* state = nvg__getState(ctx); 2160 ctx.params.renderFlush(ctx.params.userPtr); 2161 if (ctx.fontImageIdx != 0) { 2162 auto fontImage = ctx.fontImages[ctx.fontImageIdx]; 2163 int j = 0, iw, ih; 2164 // delete images that smaller than current one 2165 if (!fontImage.valid) return; 2166 ctx.imageSize(fontImage, iw, ih); 2167 foreach (int i; 0..ctx.fontImageIdx) { 2168 if (ctx.fontImages[i].valid) { 2169 int nw, nh; 2170 ctx.imageSize(ctx.fontImages[i], nw, nh); 2171 if (nw < iw || nh < ih) { 2172 ctx.deleteImage(ctx.fontImages[i]); 2173 } else { 2174 ctx.fontImages[j++] = ctx.fontImages[i]; 2175 } 2176 } 2177 } 2178 // make current font image to first 2179 ctx.fontImages[j++] = ctx.fontImages[0]; 2180 ctx.fontImages[0] = fontImage; 2181 ctx.fontImageIdx = 0; 2182 // clear all images after j 2183 ctx.fontImages[j..NVG_MAX_FONTIMAGES] = NVGImage.init; 2184 } 2185 // clear saved states (this may free some textures) 2186 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2187 ctx.nstates = 0; 2188 } 2189 2190 2191 // ////////////////////////////////////////////////////////////////////////// // 2192 // Recording and Replaying Pathes 2193 2194 // Saved path set. 2195 // Group: path_recording 2196 public alias NVGPathSet = NVGPathSetS*; 2197 2198 2199 //TODO: save scissor info? 2200 struct NVGPathSetS { 2201 private: 2202 // either path cache, or text item 2203 static struct Node { 2204 NVGPaint paint; 2205 NVGpathCache* path; 2206 } 2207 2208 private: 2209 Node* nodes; 2210 int nnodes, cnodes; 2211 NVGpickScene* pickscene; 2212 //int npickscenes, cpickscenes; 2213 NVGContext svctx; // used to do some sanity checks, and to free resources 2214 2215 private: 2216 Node* allocNode () nothrow @trusted @nogc { 2217 import core.stdc.string : memset; 2218 // grow buffer if necessary 2219 if (nnodes+1 > cnodes) { 2220 import core.stdc.stdlib : realloc; 2221 int newsz = (cnodes == 0 ? 8 : cnodes <= 1024 ? cnodes*2 : cnodes+1024); 2222 nodes = cast(Node*)realloc(nodes, newsz*Node.sizeof); 2223 if (nodes is null) assert(0, "NanoVega: out of memory"); 2224 //memset(svp.caches+svp.ccaches, 0, (newsz-svp.ccaches)*NVGpathCache.sizeof); 2225 cnodes = newsz; 2226 } 2227 assert(nnodes < cnodes); 2228 memset(nodes+nnodes, 0, Node.sizeof); 2229 return &nodes[nnodes++]; 2230 } 2231 2232 Node* allocPathNode () nothrow @trusted @nogc { 2233 import core.stdc.stdlib : malloc; 2234 import core.stdc.string : memset; 2235 auto node = allocNode(); 2236 // allocate path cache 2237 auto pc = cast(NVGpathCache*)malloc(NVGpathCache.sizeof); 2238 if (pc is null) assert(0, "NanoVega: out of memory"); 2239 node.path = pc; 2240 return node; 2241 } 2242 2243 void clearNode (int idx) nothrow @trusted @nogc { 2244 if (idx < 0 || idx >= nnodes) return; 2245 Node* node = &nodes[idx]; 2246 if (svctx !is null && node.paint.image.valid) node.paint.image.clear(); 2247 if (node.path !is null) node.path.clear(); 2248 } 2249 2250 private: 2251 void takeCurrentPickScene (NVGContext ctx) nothrow @trusted @nogc { 2252 NVGpickScene* ps = ctx.pickScene; 2253 if (ps is null) return; // nothing to do 2254 if (ps.npaths == 0) return; // pick scene is empty 2255 ctx.pickScene = null; 2256 pickscene = ps; 2257 } 2258 2259 void replay (NVGContext ctx, const scope ref NVGColor fillTint, const scope ref NVGColor strokeTint) nothrow @trusted @nogc { 2260 NVGstate* state = nvg__getState(ctx); 2261 foreach (ref node; nodes[0..nnodes]) { 2262 if (auto cc = node.path) { 2263 if (cc.npaths <= 0) continue; 2264 2265 if (cc.fillReady) { 2266 NVGPaint fillPaint = node.paint; 2267 2268 // apply global alpha 2269 fillPaint.innerColor.a *= state.alpha; 2270 fillPaint.middleColor.a *= state.alpha; 2271 fillPaint.outerColor.a *= state.alpha; 2272 2273 fillPaint.innerColor.applyTint(fillTint); 2274 fillPaint.middleColor.applyTint(fillTint); 2275 fillPaint.outerColor.applyTint(fillTint); 2276 2277 ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &fillPaint, &state.scissor, cc.fringeWidth, cc.bounds.ptr, cc.paths, cc.npaths, cc.evenOddMode); 2278 2279 // count triangles 2280 foreach (int i; 0..cc.npaths) { 2281 NVGpath* path = &cc.paths[i]; 2282 ctx.fillTriCount += path.nfill-2; 2283 ctx.fillTriCount += path.nstroke-2; 2284 ctx.drawCallCount += 2; 2285 } 2286 } 2287 2288 if (cc.strokeReady) { 2289 NVGPaint strokePaint = node.paint; 2290 2291 strokePaint.innerColor.a *= cc.strokeAlphaMul; 2292 strokePaint.middleColor.a *= cc.strokeAlphaMul; 2293 strokePaint.outerColor.a *= cc.strokeAlphaMul; 2294 2295 // apply global alpha 2296 strokePaint.innerColor.a *= state.alpha; 2297 strokePaint.middleColor.a *= state.alpha; 2298 strokePaint.outerColor.a *= state.alpha; 2299 2300 strokePaint.innerColor.applyTint(strokeTint); 2301 strokePaint.middleColor.applyTint(strokeTint); 2302 strokePaint.outerColor.applyTint(strokeTint); 2303 2304 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &strokePaint, &state.scissor, cc.fringeWidth, cc.strokeWidth, cc.paths, cc.npaths); 2305 2306 // count triangles 2307 foreach (int i; 0..cc.npaths) { 2308 NVGpath* path = &cc.paths[i]; 2309 ctx.strokeTriCount += path.nstroke-2; 2310 ++ctx.drawCallCount; 2311 } 2312 } 2313 } 2314 } 2315 } 2316 2317 public: 2318 @disable this (this); // no copies 2319 void opAssign() (const scope auto ref NVGPathSetS a) { static assert(0, "no copies!"); } 2320 2321 // pick test 2322 // Call delegate [dg] for each path under the specified position (in no particular order). 2323 // Returns the id of the path for which delegate [dg] returned true or -1. 2324 // dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending). 2325 int hitTestDG(bool bestOrder=false, DG) (in float x, in float y, NVGPickKind kind, scope DG dg) if (IsGoodHitTestDG!DG || IsGoodHitTestInternalDG!DG) { 2326 if (pickscene is null) return -1; 2327 2328 NVGpickScene* ps = pickscene; 2329 int levelwidth = 1<<(ps.nlevels-1); 2330 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth); 2331 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth); 2332 int npicked = 0; 2333 2334 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) { 2335 NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; 2336 while (pp !is null) { 2337 if (nvg__pickPathTestBounds(svctx, ps, pp, x, y)) { 2338 int hit = 0; 2339 if ((kind&NVGPickKind.Stroke) && (pp.flags&NVGPathFlags.Stroke)) hit = nvg__pickPathStroke(ps, pp, x, y); 2340 if (!hit && (kind&NVGPickKind.Fill) && (pp.flags&NVGPathFlags.Fill)) hit = nvg__pickPath(ps, pp, x, y); 2341 if (hit) { 2342 static if (IsGoodHitTestDG!DG) { 2343 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) { 2344 if (dg(pp.id, cast(int)pp.order)) return pp.id; 2345 } else { 2346 dg(pp.id, cast(int)pp.order); 2347 } 2348 } else { 2349 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) { 2350 if (dg(pp)) return pp.id; 2351 } else { 2352 dg(pp); 2353 } 2354 } 2355 } 2356 } 2357 pp = pp.next; 2358 } 2359 cellx >>= 1; 2360 celly >>= 1; 2361 levelwidth >>= 1; 2362 } 2363 2364 return -1; 2365 } 2366 2367 // Fills ids with a list of the top most hit ids under the specified position. 2368 // Returns the slice of [ids]. 2369 int[] hitTestAll (in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc { 2370 if (pickscene is null || ids.length == 0) return ids[0..0]; 2371 2372 int npicked = 0; 2373 NVGpickScene* ps = pickscene; 2374 2375 hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 2376 if (npicked == ps.cpicked) { 2377 int cpicked = ps.cpicked+ps.cpicked; 2378 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked); 2379 if (picked is null) return true; // abort 2380 ps.cpicked = cpicked; 2381 ps.picked = picked; 2382 } 2383 ps.picked[npicked] = pp; 2384 ++npicked; 2385 return false; // go on 2386 }); 2387 2388 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths); 2389 2390 assert(npicked >= 0); 2391 if (npicked > ids.length) npicked = cast(int)ids.length; 2392 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id; 2393 2394 return ids[0..npicked]; 2395 } 2396 2397 // Returns the id of the pickable shape containing x,y or -1 if no shape was found. 2398 int hitTest (in float x, in float y, NVGPickKind kind) nothrow @trusted @nogc { 2399 if (pickscene is null) return -1; 2400 2401 int bestOrder = -1; 2402 int bestID = -1; 2403 2404 hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 2405 if (pp.order > bestOrder) { 2406 bestOrder = pp.order; 2407 bestID = pp.id; 2408 } 2409 }); 2410 2411 return bestID; 2412 } 2413 } 2414 2415 // Append current path to existing path set. Is is safe to call this with `null` [svp]. 2416 void appendCurrentPathToCache (NVGContext ctx, NVGPathSet svp, const scope ref NVGPaint paint) nothrow @trusted @nogc { 2417 if (ctx is null || svp is null) return; 2418 if (ctx !is svp.svctx) assert(0, "NanoVega: cannot save paths from different contexts"); 2419 if (ctx.ncommands == 0) { 2420 assert(ctx.cache.npaths == 0); 2421 return; 2422 } 2423 if (!ctx.cache.fillReady && !ctx.cache.strokeReady) return; 2424 2425 // tesselate current path 2426 //if (!ctx.cache.fillReady) nvg__prepareFill(ctx); 2427 //if (!ctx.cache.strokeReady) nvg__prepareStroke(ctx); 2428 2429 auto node = svp.allocPathNode(); 2430 NVGpathCache* cc = node.path; 2431 cc.copyFrom(ctx.cache); 2432 node.paint = paint; 2433 // copy path commands (we may need 'em for picking) 2434 version(all) { 2435 cc.ncommands = ctx.ncommands; 2436 if (cc.ncommands) { 2437 import core.stdc.stdlib : malloc; 2438 import core.stdc.string : memcpy; 2439 cc.commands = cast(float*)malloc(ctx.ncommands*float.sizeof); 2440 if (cc.commands is null) assert(0, "NanoVega: out of memory"); 2441 memcpy(cc.commands, ctx.commands, ctx.ncommands*float.sizeof); 2442 } else { 2443 cc.commands = null; 2444 } 2445 } 2446 } 2447 2448 // Create new empty path set. 2449 // Group: path_recording 2450 public NVGPathSet newPathSet (NVGContext ctx) nothrow @trusted @nogc { 2451 import core.stdc.stdlib : malloc; 2452 import core.stdc.string : memset; 2453 if (ctx is null) return null; 2454 NVGPathSet res = cast(NVGPathSet)malloc(NVGPathSetS.sizeof); 2455 if (res is null) assert(0, "NanoVega: out of memory"); 2456 memset(res, 0, NVGPathSetS.sizeof); 2457 res.svctx = ctx; 2458 return res; 2459 } 2460 2461 // Is the given path set empty? Empty path set can be `null`. 2462 // Group: path_recording 2463 public bool empty (NVGPathSet svp) pure nothrow @safe @nogc { pragma(inline, true); return (svp is null || svp.nnodes == 0); } 2464 2465 // Clear path set contents. Will release $(B some) allocated memory (this function is meant to clear something that will be reused). 2466 // Group: path_recording 2467 public void clear (NVGPathSet svp) nothrow @trusted @nogc { 2468 if (svp !is null) { 2469 import core.stdc.stdlib : free; 2470 foreach (immutable idx; 0.. svp.nnodes) svp.clearNode(idx); 2471 svp.nnodes = 0; 2472 } 2473 } 2474 2475 // Destroy path set (frees all allocated memory). 2476 // Group: path_recording 2477 public void kill (ref NVGPathSet svp) nothrow @trusted @nogc { 2478 if (svp !is null) { 2479 import core.stdc.stdlib : free; 2480 svp.clear(); 2481 if (svp.nodes !is null) free(svp.nodes); 2482 free(svp); 2483 if (svp.pickscene !is null) nvg__deletePickScene(svp.pickscene); 2484 svp = null; 2485 } 2486 } 2487 2488 // Start path recording. [svp] should be alive until recording is cancelled or stopped. 2489 // Group: path_recording 2490 public void startRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { 2491 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2492 ctx.stopRecording(); 2493 ctx.recset = svp; 2494 ctx.recstart = (svp !is null ? svp.nnodes : -1); 2495 ctx.recblockdraw = false; 2496 } 2497 2498 /* Start path recording. [svp] should be alive until recording is cancelled or stopped. 2499 * 2500 * This will block all rendering, so you can call your rendering functions to record paths without actual drawing. 2501 * Commiting or cancelling will re-enable rendering. 2502 * You can call this with `null` svp to block rendering without recording any paths. 2503 * 2504 * Group: path_recording 2505 */ 2506 public void startBlockingRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { 2507 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2508 ctx.stopRecording(); 2509 ctx.recset = svp; 2510 ctx.recstart = (svp !is null ? svp.nnodes : -1); 2511 ctx.recblockdraw = true; 2512 } 2513 2514 // Commit recorded paths. It is safe to call this when recording is not started. 2515 // Group: path_recording 2516 public void stopRecording (NVGContext ctx) nothrow @trusted @nogc { 2517 if (ctx.recset !is null && ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2518 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx); 2519 ctx.recset = null; 2520 ctx.recstart = -1; 2521 ctx.recblockdraw = false; 2522 } 2523 2524 // Cancel path recording. 2525 // Group: path_recording 2526 public void cancelRecording (NVGContext ctx) nothrow @trusted @nogc { 2527 if (ctx.recset !is null) { 2528 if (ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2529 assert(ctx.recstart >= 0 && ctx.recstart <= ctx.recset.nnodes); 2530 foreach (immutable idx; ctx.recstart..ctx.recset.nnodes) ctx.recset.clearNode(idx); 2531 ctx.recset.nnodes = ctx.recstart; 2532 ctx.recset = null; 2533 ctx.recstart = -1; 2534 } 2535 ctx.recblockdraw = false; 2536 } 2537 2538 /* Replay saved path set. 2539 * 2540 * Replaying record while you're recording another one is undefined behavior. 2541 * 2542 * Group: path_recording 2543 */ 2544 public void replayRecording() (NVGContext ctx, NVGPathSet svp, const scope auto ref NVGColor fillTint, const scope auto ref NVGColor strokeTint) nothrow @trusted @nogc { 2545 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2546 svp.replay(ctx, fillTint, strokeTint); 2547 } 2548 2549 /// Ditto. 2550 public void replayRecording() (NVGContext ctx, NVGPathSet svp, const scope auto ref NVGColor fillTint) nothrow @trusted @nogc { ctx.replayRecording(svp, fillTint, NVGColor.transparent); } 2551 2552 /// Ditto. 2553 public void replayRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { ctx.replayRecording(svp, NVGColor.transparent, NVGColor.transparent); } 2554 2555 2556 // ////////////////////////////////////////////////////////////////////////// // 2557 // Composite operation 2558 2559 /// Sets the composite operation. 2560 /// Group: composite_operation 2561 public void globalCompositeOperation (NVGContext ctx, NVGCompositeOperation op) nothrow @trusted @nogc { 2562 NVGstate* state = nvg__getState(ctx); 2563 state.compositeOperation = nvg__compositeOperationState(op); 2564 } 2565 2566 /// Sets the composite operation with custom pixel arithmetic. 2567 /// Group: composite_operation 2568 public void globalCompositeBlendFunc (NVGContext ctx, NVGBlendFactor sfactor, NVGBlendFactor dfactor) nothrow @trusted @nogc { 2569 ctx.globalCompositeBlendFuncSeparate(sfactor, dfactor, sfactor, dfactor); 2570 } 2571 2572 /// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. 2573 /// Group: composite_operation 2574 public void globalCompositeBlendFuncSeparate (NVGContext ctx, NVGBlendFactor srcRGB, NVGBlendFactor dstRGB, NVGBlendFactor srcAlpha, NVGBlendFactor dstAlpha) nothrow @trusted @nogc { 2575 NVGCompositeOperationState op; 2576 op.simple = false; 2577 op.srcRGB = srcRGB; 2578 op.dstRGB = dstRGB; 2579 op.srcAlpha = srcAlpha; 2580 op.dstAlpha = dstAlpha; 2581 NVGstate* state = nvg__getState(ctx); 2582 state.compositeOperation = op; 2583 } 2584 2585 2586 // ////////////////////////////////////////////////////////////////////////// // 2587 // Color utils 2588 2589 /// Returns a color value from string form. 2590 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb" 2591 /// Group: color_utils 2592 public NVGColor nvgRGB (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); } 2593 2594 /// Ditto. 2595 public NVGColor nvgRGBA (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); } 2596 2597 /// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f). 2598 /// Group: color_utils 2599 public NVGColor nvgRGB (int r, int g, int b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(nvgClampToByte(r), nvgClampToByte(g), nvgClampToByte(b), 255); } 2600 2601 /// Returns a color value from red, green, blue values. Alpha will be set to 1.0f. 2602 /// Group: color_utils 2603 public NVGColor nvgRGBf (float r, float g, float b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(r, g, b, 1.0f); } 2604 2605 /// Returns a color value from red, green, blue and alpha values. 2606 /// Group: color_utils 2607 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)); } 2608 2609 /// Returns a color value from red, green, blue and alpha values. 2610 /// Group: color_utils 2611 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); } 2612 2613 /// Returns new color with transparency (alpha) set to [a]. 2614 /// Group: color_utils 2615 public NVGColor nvgTransRGBA (NVGColor c, ubyte a) nothrow @trusted @nogc { 2616 pragma(inline, true); 2617 c.a = a/255.0f; 2618 return c; 2619 } 2620 2621 /// Ditto. 2622 public NVGColor nvgTransRGBAf (NVGColor c, float a) nothrow @trusted @nogc { 2623 pragma(inline, true); 2624 c.a = a; 2625 return c; 2626 } 2627 2628 /// Linearly interpolates from color c0 to c1, and returns resulting color value. 2629 /// Group: color_utils 2630 public NVGColor nvgLerpRGBA() (const scope auto ref NVGColor c0, const scope auto ref NVGColor c1, float u) nothrow @trusted @nogc { 2631 NVGColor cint = void; 2632 u = nvg__clamp(u, 0.0f, 1.0f); 2633 float oneminu = 1.0f-u; 2634 foreach (uint i; 0..4) cint.rgba.ptr[i] = c0.rgba.ptr[i]*oneminu+c1.rgba.ptr[i]*u; 2635 return cint; 2636 } 2637 2638 /* see below 2639 public NVGColor nvgHSL() (float h, float s, float l) { 2640 //pragma(inline, true); // alas 2641 return nvgHSLA(h, s, l, 255); 2642 } 2643 */ 2644 2645 float nvg__hue (float h, float m1, float m2) pure nothrow @safe @nogc { 2646 if (h < 0) h += 1; 2647 if (h > 1) h -= 1; 2648 if (h < 1.0f/6.0f) return m1+(m2-m1)*h*6.0f; 2649 if (h < 3.0f/6.0f) return m2; 2650 if (h < 4.0f/6.0f) return m1+(m2-m1)*(2.0f/3.0f-h)*6.0f; 2651 return m1; 2652 } 2653 2654 /// Returns color value specified by hue, saturation and lightness. 2655 /// HSL values are all in range [0..1], alpha will be set to 255. 2656 /// Group: color_utils 2657 public alias nvgHSL = nvgHSLA; // trick to allow inlining 2658 2659 /// Returns color value specified by hue, saturation and lightness and alpha. 2660 /// HSL values are all in range [0..1], alpha in range [0..255]. 2661 /// Group: color_utils 2662 public NVGColor nvgHSLA (float h, float s, float l, ubyte a=255) nothrow @trusted @nogc { 2663 pragma(inline, true); 2664 NVGColor col = void; 2665 h = nvg__modf(h, 1.0f); 2666 if (h < 0.0f) h += 1.0f; 2667 s = nvg__clamp(s, 0.0f, 1.0f); 2668 l = nvg__clamp(l, 0.0f, 1.0f); 2669 immutable float m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s); 2670 immutable float m1 = 2*l-m2; 2671 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2672 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f); 2673 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2674 col.a = a/255.0f; 2675 return col; 2676 } 2677 2678 /// Returns color value specified by hue, saturation and lightness and alpha. 2679 /// HSL values and alpha are all in range [0..1]. 2680 /// Group: color_utils 2681 public NVGColor nvgHSLA (float h, float s, float l, float a) nothrow @trusted @nogc { 2682 // sorry for copypasta, it is for inliner 2683 static if (__VERSION__ >= 2072) pragma(inline, true); 2684 NVGColor col = void; 2685 h = nvg__modf(h, 1.0f); 2686 if (h < 0.0f) h += 1.0f; 2687 s = nvg__clamp(s, 0.0f, 1.0f); 2688 l = nvg__clamp(l, 0.0f, 1.0f); 2689 immutable m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s); 2690 immutable m1 = 2*l-m2; 2691 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2692 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f); 2693 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2694 col.a = a; 2695 return col; 2696 } 2697 2698 2699 // ////////////////////////////////////////////////////////////////////////// // 2700 // Matrices and Transformations 2701 2702 /** Matrix class. 2703 * 2704 * Group: matrices 2705 */ 2706 public align(1) struct NVGMatrix { 2707 align(1): 2708 private: 2709 static immutable float[6] IdentityMat = [ 2710 1.0f, 0.0f, 2711 0.0f, 1.0f, 2712 0.0f, 0.0f, 2713 ]; 2714 2715 public: 2716 /// Matrix values. Initial value is identity matrix. 2717 float[6] mat = [ 2718 1.0f, 0.0f, 2719 0.0f, 1.0f, 2720 0.0f, 0.0f, 2721 ]; 2722 2723 public nothrow @trusted @nogc: 2724 /// Create Matrix with the given values. 2725 this (const(float)[] amat...) { 2726 pragma(inline, true); 2727 if (amat.length >= 6) { 2728 mat.ptr[0..6] = amat.ptr[0..6]; 2729 } else { 2730 mat.ptr[0..6] = 0; 2731 mat.ptr[0..amat.length] = amat[]; 2732 } 2733 } 2734 2735 /// Can be used to check validity of [inverted] result 2736 @property bool valid () const { import core.stdc.math : isfinite; return (isfinite(mat.ptr[0]) != 0); } 2737 2738 /// Returns `true` if this matrix is identity matrix. 2739 @property bool isIdentity () const { version(aliced) pragma(inline, true); return (mat[] == IdentityMat[]); } 2740 2741 /// Returns new inverse matrix. 2742 /// If inverted matrix cannot be calculated, `res.valid` fill be `false`. 2743 NVGMatrix inverted () const { 2744 NVGMatrix res = this; 2745 res.invert; 2746 return res; 2747 } 2748 2749 /// Inverts this matrix. 2750 /// If inverted matrix cannot be calculated, `this.valid` fill be `false`. 2751 ref NVGMatrix invert () return { 2752 float[6] inv = void; 2753 immutable double det = cast(double)mat.ptr[0]*mat.ptr[3]-cast(double)mat.ptr[2]*mat.ptr[1]; 2754 if (det > -1e-6 && det < 1e-6) { 2755 inv[] = float.nan; 2756 } else { 2757 immutable double invdet = 1.0/det; 2758 inv.ptr[0] = cast(float)(mat.ptr[3]*invdet); 2759 inv.ptr[2] = cast(float)(-mat.ptr[2]*invdet); 2760 inv.ptr[4] = cast(float)((cast(double)mat.ptr[2]*mat.ptr[5]-cast(double)mat.ptr[3]*mat.ptr[4])*invdet); 2761 inv.ptr[1] = cast(float)(-mat.ptr[1]*invdet); 2762 inv.ptr[3] = cast(float)(mat.ptr[0]*invdet); 2763 inv.ptr[5] = cast(float)((cast(double)mat.ptr[1]*mat.ptr[4]-cast(double)mat.ptr[0]*mat.ptr[5])*invdet); 2764 } 2765 mat.ptr[0..6] = inv.ptr[0..6]; 2766 return this; 2767 } 2768 2769 /// Sets this matrix to identity matrix. 2770 ref NVGMatrix identity () return { version(aliced) pragma(inline, true); mat[] = IdentityMat[]; return this; } 2771 2772 /// Translate this matrix. 2773 ref NVGMatrix translate (in float tx, in float ty) return { 2774 version(aliced) pragma(inline, true); 2775 return this.mul(Translated(tx, ty)); 2776 } 2777 2778 /// Scale this matrix. 2779 ref NVGMatrix scale (in float sx, in float sy) return { 2780 version(aliced) pragma(inline, true); 2781 return this.mul(Scaled(sx, sy)); 2782 } 2783 2784 /// Rotate this matrix. 2785 ref NVGMatrix rotate (in float a) return { 2786 version(aliced) pragma(inline, true); 2787 return this.mul(Rotated(a)); 2788 } 2789 2790 /// Skew this matrix by X axis. 2791 ref NVGMatrix skewX (in float a) return { 2792 version(aliced) pragma(inline, true); 2793 return this.mul(SkewedX(a)); 2794 } 2795 2796 /// Skew this matrix by Y axis. 2797 ref NVGMatrix skewY (in float a) return { 2798 version(aliced) pragma(inline, true); 2799 return this.mul(SkewedY(a)); 2800 } 2801 2802 /// Skew this matrix by both axes. 2803 ref NVGMatrix skewY (in float ax, in float ay) return { 2804 version(aliced) pragma(inline, true); 2805 return this.mul(SkewedXY(ax, ay)); 2806 } 2807 2808 /// Transform point with this matrix. `null` destinations are allowed. 2809 /// [sx] and [sy] is the source point. [dx] and [dy] may point to the same variables. 2810 void point (float* dx, float* dy, float sx, float sy) nothrow @trusted @nogc { 2811 version(aliced) pragma(inline, true); 2812 if (dx !is null) *dx = sx*mat.ptr[0]+sy*mat.ptr[2]+mat.ptr[4]; 2813 if (dy !is null) *dy = sx*mat.ptr[1]+sy*mat.ptr[3]+mat.ptr[5]; 2814 } 2815 2816 /// Transform point with this matrix. 2817 void point (ref float x, ref float y) nothrow @trusted @nogc { 2818 version(aliced) pragma(inline, true); 2819 immutable float nx = x*mat.ptr[0]+y*mat.ptr[2]+mat.ptr[4]; 2820 immutable float ny = x*mat.ptr[1]+y*mat.ptr[3]+mat.ptr[5]; 2821 x = nx; 2822 y = ny; 2823 } 2824 2825 /// Sets this matrix to the result of multiplication of `this` and [s] (this * S). 2826 ref NVGMatrix mul() (const scope auto ref NVGMatrix s) { 2827 immutable float t0 = mat.ptr[0]*s.mat.ptr[0]+mat.ptr[1]*s.mat.ptr[2]; 2828 immutable float t2 = mat.ptr[2]*s.mat.ptr[0]+mat.ptr[3]*s.mat.ptr[2]; 2829 immutable float t4 = mat.ptr[4]*s.mat.ptr[0]+mat.ptr[5]*s.mat.ptr[2]+s.mat.ptr[4]; 2830 mat.ptr[1] = mat.ptr[0]*s.mat.ptr[1]+mat.ptr[1]*s.mat.ptr[3]; 2831 mat.ptr[3] = mat.ptr[2]*s.mat.ptr[1]+mat.ptr[3]*s.mat.ptr[3]; 2832 mat.ptr[5] = mat.ptr[4]*s.mat.ptr[1]+mat.ptr[5]*s.mat.ptr[3]+s.mat.ptr[5]; 2833 mat.ptr[0] = t0; 2834 mat.ptr[2] = t2; 2835 mat.ptr[4] = t4; 2836 return this; 2837 } 2838 2839 /// Sets this matrix to the result of multiplication of [s] and `this` (S * this). 2840 /// Sets the transform to the result of multiplication of two transforms, of A = B*A. 2841 /// Group: matrices 2842 ref NVGMatrix premul() (const scope auto ref NVGMatrix s) { 2843 NVGMatrix s2 = s; 2844 s2.mul(this); 2845 mat[] = s2.mat[]; 2846 return this; 2847 } 2848 2849 /// Multiply this matrix by [s], return result as new matrix. 2850 /// Performs operations in this left-to-right order. 2851 NVGMatrix opBinary(string op="*") (const scope auto ref NVGMatrix s) const { 2852 version(aliced) pragma(inline, true); 2853 NVGMatrix res = this; 2854 res.mul(s); 2855 return res; 2856 } 2857 2858 /// Multiply this matrix by [s]. 2859 /// Performs operations in this left-to-right order. 2860 ref NVGMatrix opOpAssign(string op="*") (const scope auto ref NVGMatrix s) { 2861 version(aliced) pragma(inline, true); 2862 return this.mul(s); 2863 } 2864 2865 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. 2866 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. 2867 float rotation () const { pragma(inline, true); return nvg__atan2f(mat.ptr[1], mat.ptr[0]); } /// Returns rotation of this matrix. 2868 float tx () const { pragma(inline, true); return mat.ptr[4]; } /// Returns x translation of this matrix. 2869 float ty () const { pragma(inline, true); return mat.ptr[5]; } /// Returns y translation of this matrix. 2870 2871 ref NVGMatrix scaleX (in float v) return { pragma(inline, true); return scaleRotateTransform(v, scaleY, rotation, tx, ty); } /// Sets x scaling of this matrix. 2872 ref NVGMatrix scaleY (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, v, rotation, tx, ty); } /// Sets y scaling of this matrix. 2873 ref NVGMatrix rotation (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, scaleY, v, tx, ty); } /// Sets rotation of this matrix. 2874 ref NVGMatrix tx (in float v) return { pragma(inline, true); mat.ptr[4] = v; return this; } /// Sets x translation of this matrix. 2875 ref NVGMatrix ty (in float v) return { pragma(inline, true); mat.ptr[5] = v; return this; } /// Sets y translation of this matrix. 2876 2877 /// Utility function to be used in `setXXX()`. 2878 /// This is the same as doing: `mat.identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster 2879 ref NVGMatrix scaleRotateTransform (in float xscale, in float yscale, in float a, in float tx, in float ty) return { 2880 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2881 mat.ptr[0] = xscale*cs; mat.ptr[1] = yscale*sn; 2882 mat.ptr[2] = xscale*-sn; mat.ptr[3] = yscale*cs; 2883 mat.ptr[4] = tx; mat.ptr[5] = ty; 2884 return this; 2885 } 2886 2887 /// This is the same as doing: `mat.identity.rotate(a).translate(tx, ty)`, only faster 2888 ref NVGMatrix rotateTransform (in float a, in float tx, in float ty) return { 2889 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2890 mat.ptr[0] = cs; mat.ptr[1] = sn; 2891 mat.ptr[2] = -sn; mat.ptr[3] = cs; 2892 mat.ptr[4] = tx; mat.ptr[5] = ty; 2893 return this; 2894 } 2895 2896 /// Returns new identity matrix. 2897 static NVGMatrix Identity () { pragma(inline, true); return NVGMatrix.init; } 2898 2899 /// Returns new translation matrix. 2900 static NVGMatrix Translated (in float tx, in float ty) { 2901 version(aliced) pragma(inline, true); 2902 NVGMatrix res = void; 2903 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f; 2904 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f; 2905 res.mat.ptr[4] = tx; res.mat.ptr[5] = ty; 2906 return res; 2907 } 2908 2909 /// Returns new scaling matrix. 2910 static NVGMatrix Scaled (in float sx, in float sy) { 2911 version(aliced) pragma(inline, true); 2912 NVGMatrix res = void; 2913 res.mat.ptr[0] = sx; res.mat.ptr[1] = 0.0f; 2914 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = sy; 2915 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2916 return res; 2917 } 2918 2919 /// Returns new rotation matrix. Angle is specified in radians. 2920 static NVGMatrix Rotated (in float a) { 2921 version(aliced) pragma(inline, true); 2922 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2923 NVGMatrix res = void; 2924 res.mat.ptr[0] = cs; res.mat.ptr[1] = sn; 2925 res.mat.ptr[2] = -sn; res.mat.ptr[3] = cs; 2926 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2927 return res; 2928 } 2929 2930 /// Returns new x-skewing matrix. Angle is specified in radians. 2931 static NVGMatrix SkewedX (in float a) { 2932 version(aliced) pragma(inline, true); 2933 NVGMatrix res = void; 2934 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f; 2935 res.mat.ptr[2] = nvg__tanf(a); res.mat.ptr[3] = 1.0f; 2936 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2937 return res; 2938 } 2939 2940 /// Returns new y-skewing matrix. Angle is specified in radians. 2941 static NVGMatrix SkewedY (in float a) { 2942 version(aliced) pragma(inline, true); 2943 NVGMatrix res = void; 2944 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(a); 2945 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f; 2946 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2947 return res; 2948 } 2949 2950 /// Returns new xy-skewing matrix. Angles are specified in radians. 2951 static NVGMatrix SkewedXY (in float ax, in float ay) { 2952 version(aliced) pragma(inline, true); 2953 NVGMatrix res = void; 2954 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(ay); 2955 res.mat.ptr[2] = nvg__tanf(ax); res.mat.ptr[3] = 1.0f; 2956 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2957 return res; 2958 } 2959 2960 /// Utility function to be used in `setXXX()`. 2961 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster 2962 static NVGMatrix ScaledRotatedTransformed (in float xscale, in float yscale, in float a, in float tx, in float ty) { 2963 NVGMatrix res = void; 2964 res.scaleRotateTransform(xscale, yscale, a, tx, ty); 2965 return res; 2966 } 2967 2968 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).translate(tx, ty)`, only faster 2969 static NVGMatrix RotatedTransformed (in float a, in float tx, in float ty) { 2970 NVGMatrix res = void; 2971 res.rotateTransform(a, tx, ty); 2972 return res; 2973 } 2974 } 2975 2976 2977 /// Converts degrees to radians. 2978 /// Group: matrices 2979 public float nvgDegToRad() (in float deg) pure nothrow @safe @nogc { pragma(inline, true); return deg/180.0f*NVG_PI; } 2980 2981 /// Converts radians to degrees. 2982 /// Group: matrices 2983 public float nvgRadToDeg() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad/NVG_PI*180.0f; } 2984 2985 public alias nvgDegrees = nvgDegToRad; /// Use this like `42.nvgDegrees` 2986 public float nvgRadians() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad; } /// Use this like `0.1.nvgRadians` 2987 2988 2989 // ////////////////////////////////////////////////////////////////////////// // 2990 void nvg__setPaintColor() (ref NVGPaint p, const scope auto ref NVGColor color) nothrow @trusted @nogc { 2991 p.clear(); 2992 p.xform.identity; 2993 p.radius = 0.0f; 2994 p.feather = 1.0f; 2995 p.innerColor = p.middleColor = p.outerColor = color; 2996 p.midp = -1; 2997 p.simpleColor = true; 2998 } 2999 3000 3001 // ////////////////////////////////////////////////////////////////////////// // 3002 // State handling 3003 3004 version(nanovega_debug_clipping) { 3005 public void nvgClipDumpOn (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, true); } 3006 public void nvgClipDumpOff (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, false); } 3007 } 3008 3009 /** Pushes and saves the current render state into a state stack. 3010 * A matching [restore] must be used to restore the state. 3011 * Returns `false` if state stack overflowed. 3012 * 3013 * Group: state_handling 3014 */ 3015 public bool save (NVGContext ctx) nothrow @trusted @nogc { 3016 if (ctx.nstates >= NVG_MAX_STATES) return false; 3017 if (ctx.nstates > 0) { 3018 //memcpy(&ctx.states[ctx.nstates], &ctx.states[ctx.nstates-1], NVGstate.sizeof); 3019 ctx.states[ctx.nstates] = ctx.states[ctx.nstates-1]; 3020 ctx.params.renderPushClip(ctx.params.userPtr); 3021 } 3022 ++ctx.nstates; 3023 return true; 3024 } 3025 3026 /// Pops and restores current render state. 3027 /// Group: state_handling 3028 public bool restore (NVGContext ctx) nothrow @trusted @nogc { 3029 if (ctx.nstates <= 1) return false; 3030 ctx.states[ctx.nstates-1].clearPaint(); 3031 ctx.params.renderPopClip(ctx.params.userPtr); 3032 --ctx.nstates; 3033 return true; 3034 } 3035 3036 /// Resets current render state to default values. Does not affect the render state stack. 3037 /// Group: state_handling 3038 public void reset (NVGContext ctx) nothrow @trusted @nogc { 3039 NVGstate* state = nvg__getState(ctx); 3040 state.clearPaint(); 3041 3042 nvg__setPaintColor(state.fill, nvgRGBA(255, 255, 255, 255)); 3043 nvg__setPaintColor(state.stroke, nvgRGBA(0, 0, 0, 255)); 3044 state.compositeOperation = nvg__compositeOperationState(NVGCompositeOperation.SourceOver); 3045 state.shapeAntiAlias = true; 3046 state.strokeWidth = 1.0f; 3047 state.miterLimit = 10.0f; 3048 state.lineCap = NVGLineCap.Butt; 3049 state.lineJoin = NVGLineCap.Miter; 3050 state.alpha = 1.0f; 3051 state.xform.identity; 3052 3053 state.scissor.extent[] = -1.0f; 3054 3055 state.fontSize = 16.0f; 3056 state.letterSpacing = 0.0f; 3057 state.lineHeight = 1.0f; 3058 state.fontBlur = 0.0f; 3059 state.textAlign.reset; 3060 state.fontId = 0; 3061 state.evenOddMode = false; 3062 state.dashCount = 0; 3063 state.lastFlattenDashCount = 0; 3064 state.dashStart = 0; 3065 state.firstDashIsGap = false; 3066 state.dasherActive = false; 3067 3068 ctx.params.renderResetClip(ctx.params.userPtr); 3069 } 3070 3071 /** Returns `true` if we have any room in state stack. 3072 * It is guaranteed to have at least 32 stack slots. 3073 * 3074 * Group: state_handling 3075 */ 3076 public bool canSave (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates < NVG_MAX_STATES); } 3077 3078 /** Returns `true` if we have any saved state. 3079 * 3080 * Group: state_handling 3081 */ 3082 public bool canRestore (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates > 1); } 3083 3084 /// Returns `true` if rendering is currently blocked. 3085 /// Group: state_handling 3086 public bool renderBlocked (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.recblockdraw : false); } 3087 3088 /// Blocks/unblocks rendering 3089 /// Group: state_handling 3090 public void renderBlocked (NVGContext ctx, bool v) pure nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null && ctx.contextAlive) ctx.recblockdraw = v; } 3091 3092 /// Blocks/unblocks rendering; returns previous state. 3093 /// Group: state_handling 3094 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; } 3095 3096 3097 // ////////////////////////////////////////////////////////////////////////// // 3098 // Render styles 3099 3100 /// Sets filling mode to "even-odd". 3101 /// Group: render_styles 3102 public void evenOddFill (NVGContext ctx) nothrow @trusted @nogc { 3103 NVGstate* state = nvg__getState(ctx); 3104 state.evenOddMode = true; 3105 } 3106 3107 /// Sets filling mode to "non-zero" (this is default mode). 3108 /// Group: render_styles 3109 public void nonZeroFill (NVGContext ctx) nothrow @trusted @nogc { 3110 NVGstate* state = nvg__getState(ctx); 3111 state.evenOddMode = false; 3112 } 3113 3114 /// Sets whether to draw antialias for [stroke] and [fill]. It's enabled by default. 3115 /// Group: render_styles 3116 public void shapeAntiAlias (NVGContext ctx, bool enabled) { 3117 NVGstate* state = nvg__getState(ctx); 3118 state.shapeAntiAlias = enabled; 3119 } 3120 3121 /// Sets the stroke width of the stroke style. 3122 /// Group: render_styles 3123 @scriptable 3124 public void strokeWidth (NVGContext ctx, float width) nothrow @trusted @nogc { 3125 NVGstate* state = nvg__getState(ctx); 3126 state.strokeWidth = width; 3127 } 3128 3129 /// Sets the miter limit of the stroke style. Miter limit controls when a sharp corner is beveled. 3130 /// Group: render_styles 3131 public void miterLimit (NVGContext ctx, float limit) nothrow @trusted @nogc { 3132 NVGstate* state = nvg__getState(ctx); 3133 state.miterLimit = limit; 3134 } 3135 3136 /// Sets how the end of the line (cap) is drawn, 3137 /// Can be one of: NVGLineCap.Butt (default), NVGLineCap.Round, NVGLineCap.Square. 3138 /// Group: render_styles 3139 public void lineCap (NVGContext ctx, NVGLineCap cap) nothrow @trusted @nogc { 3140 NVGstate* state = nvg__getState(ctx); 3141 state.lineCap = cap; 3142 } 3143 3144 /// Sets how sharp path corners are drawn. 3145 /// Can be one of NVGLineCap.Miter (default), NVGLineCap.Round, NVGLineCap.Bevel. 3146 /// Group: render_styles 3147 public void lineJoin (NVGContext ctx, NVGLineCap join) nothrow @trusted @nogc { 3148 NVGstate* state = nvg__getState(ctx); 3149 state.lineJoin = join; 3150 } 3151 3152 /// Sets stroke dashing, using (dash_length, gap_length) pairs. 3153 /// Current limit is 16 pairs. 3154 /// Resets dash start to zero. 3155 /// Group: render_styles 3156 public void setLineDash (NVGContext ctx, const(float)[] dashdata) nothrow @trusted @nogc { 3157 NVGstate* state = nvg__getState(ctx); 3158 state.dashCount = 0; 3159 state.dashStart = 0; 3160 state.firstDashIsGap = false; 3161 if (dashdata.length >= 2) { 3162 bool curFIsGap = true; // trick 3163 foreach (immutable idx, float f; dashdata) { 3164 curFIsGap = !curFIsGap; 3165 if (f < 0.01f) continue; // skip it 3166 if (idx == 0) { 3167 // register first dash 3168 state.firstDashIsGap = curFIsGap; 3169 state.dashes.ptr[state.dashCount++] = f; 3170 } else { 3171 if ((idx&1) != ((state.dashCount&1)^cast(uint)state.firstDashIsGap)) { 3172 // oops, continuation 3173 state.dashes[state.dashCount-1] += f; 3174 } else { 3175 if (state.dashCount == state.dashes.length) break; 3176 state.dashes[state.dashCount++] = f; 3177 } 3178 } 3179 } 3180 if (state.dashCount&1) { 3181 if (state.dashCount == 1) { 3182 state.dashCount = 0; 3183 } else { 3184 assert(state.dashCount < state.dashes.length); 3185 state.dashes[state.dashCount++] = 0; 3186 } 3187 } 3188 // calculate total dash path length 3189 state.totalDashLen = 0; 3190 foreach (float f; state.dashes.ptr[0..state.dashCount]) state.totalDashLen += f; 3191 if (state.totalDashLen < 0.01f) { 3192 state.dashCount = 0; // nothing to do 3193 } else { 3194 if (state.lastFlattenDashCount != 0) state.lastFlattenDashCount = uint.max; // force re-flattening 3195 } 3196 } 3197 } 3198 3199 public alias lineDash = setLineDash; /// Ditto. 3200 3201 /// Sets stroke dashing, using (dash_length, gap_length) pairs. 3202 /// Current limit is 16 pairs. 3203 /// Group: render_styles 3204 public void setLineDashStart (NVGContext ctx, in float dashStart) nothrow @trusted @nogc { 3205 NVGstate* state = nvg__getState(ctx); 3206 if (state.lastFlattenDashCount != 0 && state.dashStart != dashStart) { 3207 state.lastFlattenDashCount = uint.max; // force re-flattening 3208 } 3209 state.dashStart = dashStart; 3210 } 3211 3212 public alias lineDashStart = setLineDashStart; /// Ditto. 3213 3214 /// Sets the transparency applied to all rendered shapes. 3215 /// Already transparent paths will get proportionally more transparent as well. 3216 /// Group: render_styles 3217 public void globalAlpha (NVGContext ctx, float alpha) nothrow @trusted @nogc { 3218 NVGstate* state = nvg__getState(ctx); 3219 state.alpha = alpha; 3220 } 3221 3222 private void strokeColor() {} 3223 3224 static if (NanoVegaHasArsdColor) { 3225 /// Sets current stroke style to a solid color. 3226 /// Group: render_styles 3227 @scriptable 3228 public void strokeColor (NVGContext ctx, Color color) nothrow @trusted @nogc { 3229 NVGstate* state = nvg__getState(ctx); 3230 nvg__setPaintColor(state.stroke, NVGColor(color)); 3231 } 3232 } 3233 3234 /// Sets current stroke style to a solid color. 3235 /// Group: render_styles 3236 public void strokeColor() (NVGContext ctx, const scope auto ref NVGColor color) nothrow @trusted @nogc { 3237 NVGstate* state = nvg__getState(ctx); 3238 nvg__setPaintColor(state.stroke, color); 3239 } 3240 3241 @scriptable 3242 public void strokePaint(NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc { 3243 strokePaint(ctx, *paint); 3244 } 3245 3246 /// Sets current stroke style to a paint, which can be a one of the gradients or a pattern. 3247 /// Group: render_styles 3248 @scriptable 3249 public void strokePaint() (NVGContext ctx, const scope auto ref NVGPaint paint) nothrow @trusted @nogc { 3250 NVGstate* state = nvg__getState(ctx); 3251 state.stroke = paint; 3252 //nvgTransformMultiply(state.stroke.xform[], state.xform[]); 3253 state.stroke.xform.mul(state.xform); 3254 } 3255 3256 // this is a hack to work around https://issues.dlang.org/show_bug.cgi?id=16206 3257 // for scriptable reflection. it just needs to be declared first among the overloads 3258 private void fillColor (NVGContext ctx) nothrow @trusted @nogc { } 3259 3260 static if (NanoVegaHasArsdColor) { 3261 /// Sets current fill style to a solid color. 3262 /// Group: render_styles 3263 @scriptable 3264 public void fillColor (NVGContext ctx, Color color) nothrow @trusted @nogc { 3265 NVGstate* state = nvg__getState(ctx); 3266 nvg__setPaintColor(state.fill, NVGColor(color)); 3267 } 3268 } 3269 3270 /// Sets current fill style to a solid color. 3271 /// Group: render_styles 3272 public void fillColor() (NVGContext ctx, const scope auto ref NVGColor color) nothrow @trusted @nogc { 3273 NVGstate* state = nvg__getState(ctx); 3274 nvg__setPaintColor(state.fill, color); 3275 3276 } 3277 3278 @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) 3279 public void fillPaint (NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc { 3280 fillPaint(ctx, *paint); 3281 } 3282 3283 /// Sets current fill style to a paint, which can be a one of the gradients or a pattern. 3284 /// Group: render_styles 3285 @scriptable 3286 public void fillPaint() (NVGContext ctx, const scope auto ref NVGPaint paint) nothrow @trusted @nogc { 3287 NVGstate* state = nvg__getState(ctx); 3288 state.fill = paint; 3289 //nvgTransformMultiply(state.fill.xform[], state.xform[]); 3290 state.fill.xform.mul(state.xform); 3291 } 3292 3293 /// Sets current fill style to a multistop linear gradient. 3294 /// Group: render_styles 3295 public void fillPaint() (NVGContext ctx, const scope auto ref NVGLGS lgs) nothrow @trusted @nogc { 3296 if (!lgs.valid) { 3297 NVGPaint p = void; 3298 memset(&p, 0, p.sizeof); 3299 nvg__setPaintColor(p, NVGColor.red); 3300 ctx.fillPaint = p; 3301 } else if (lgs.midp >= -1) { 3302 //{ import core.stdc.stdio; printf("SIMPLE! midp=%f\n", cast(double)lgs.midp); } 3303 ctx.fillPaint = ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc); 3304 } else { 3305 ctx.fillPaint = ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid); 3306 } 3307 } 3308 3309 /// Returns current transformation matrix. 3310 /// Group: render_transformations 3311 public NVGMatrix currTransform (NVGContext ctx) pure nothrow @trusted @nogc { 3312 NVGstate* state = nvg__getState(ctx); 3313 return state.xform; 3314 } 3315 3316 /// Sets current transformation matrix. 3317 /// Group: render_transformations 3318 public void currTransform() (NVGContext ctx, const scope auto ref NVGMatrix m) nothrow @trusted @nogc { 3319 NVGstate* state = nvg__getState(ctx); 3320 state.xform = m; 3321 } 3322 3323 /// Resets current transform to an identity matrix. 3324 /// Group: render_transformations 3325 @scriptable 3326 public void resetTransform (NVGContext ctx) nothrow @trusted @nogc { 3327 NVGstate* state = nvg__getState(ctx); 3328 state.xform.identity; 3329 } 3330 3331 /// Premultiplies current coordinate system by specified matrix. 3332 /// Group: render_transformations 3333 public void transform() (NVGContext ctx, const scope auto ref NVGMatrix mt) nothrow @trusted @nogc { 3334 NVGstate* state = nvg__getState(ctx); 3335 //nvgTransformPremultiply(state.xform[], t[]); 3336 state.xform *= mt; 3337 } 3338 3339 /// Translates current coordinate system. 3340 /// Group: render_transformations 3341 @scriptable 3342 public void translate (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 3343 NVGstate* state = nvg__getState(ctx); 3344 //NVGMatrix t = void; 3345 //nvgTransformTranslate(t[], x, y); 3346 //nvgTransformPremultiply(state.xform[], t[]); 3347 state.xform.premul(NVGMatrix.Translated(x, y)); 3348 } 3349 3350 /// Rotates current coordinate system. Angle is specified in radians. 3351 /// Group: render_transformations 3352 @scriptable 3353 public void rotate (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3354 NVGstate* state = nvg__getState(ctx); 3355 //NVGMatrix t = void; 3356 //nvgTransformRotate(t[], angle); 3357 //nvgTransformPremultiply(state.xform[], t[]); 3358 state.xform.premul(NVGMatrix.Rotated(angle)); 3359 } 3360 3361 /// Skews the current coordinate system along X axis. Angle is specified in radians. 3362 /// Group: render_transformations 3363 @scriptable 3364 public void skewX (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3365 NVGstate* state = nvg__getState(ctx); 3366 //NVGMatrix t = void; 3367 //nvgTransformSkewX(t[], angle); 3368 //nvgTransformPremultiply(state.xform[], t[]); 3369 state.xform.premul(NVGMatrix.SkewedX(angle)); 3370 } 3371 3372 /// Skews the current coordinate system along Y axis. Angle is specified in radians. 3373 /// Group: render_transformations 3374 @scriptable 3375 public void skewY (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3376 NVGstate* state = nvg__getState(ctx); 3377 //NVGMatrix t = void; 3378 //nvgTransformSkewY(t[], angle); 3379 //nvgTransformPremultiply(state.xform[], t[]); 3380 state.xform.premul(NVGMatrix.SkewedY(angle)); 3381 } 3382 3383 /// Scales the current coordinate system. 3384 /// Group: render_transformations 3385 @scriptable 3386 public void scale (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 3387 NVGstate* state = nvg__getState(ctx); 3388 //NVGMatrix t = void; 3389 //nvgTransformScale(t[], x, y); 3390 //nvgTransformPremultiply(state.xform[], t[]); 3391 state.xform.premul(NVGMatrix.Scaled(x, y)); 3392 } 3393 3394 3395 // ////////////////////////////////////////////////////////////////////////// // 3396 // Images 3397 3398 /// Creates image by loading it from the disk from specified file name. 3399 /// Returns handle to the image or 0 on error. 3400 /// Group: images 3401 public NVGImage createImage() (NVGContext ctx, const(char)[] filename, const(NVGImageFlag)[] imageFlagsList...) { 3402 static if (NanoVegaHasArsdImage) { 3403 import arsd.image; 3404 // do we have new arsd API to load images? 3405 static if (!is(typeof(MemoryImage.fromImageFile)) || !is(typeof(MemoryImage.clearInternal))) { 3406 static assert(0, "Sorry, your ARSD is too old. Please, update it."); 3407 } 3408 try { 3409 auto oimg = MemoryImage.fromImageFile(filename); 3410 if (auto img = cast(TrueColorImage)oimg) { 3411 scope(exit) oimg.clearInternal(); 3412 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList); 3413 } else { 3414 TrueColorImage img = oimg.getAsTrueColorImage; 3415 scope(exit) img.clearInternal(); 3416 oimg.clearInternal(); // drop original image, as `getAsTrueColorImage()` MUST create a new one here 3417 oimg = null; 3418 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList); 3419 } 3420 } catch (Exception) {} 3421 return NVGImage.init; 3422 } else { 3423 import std.internal.cstring; 3424 ubyte* img; 3425 int w, h, n; 3426 stbi_set_unpremultiply_on_load(1); 3427 stbi_convert_iphone_png_to_rgb(1); 3428 img = stbi_load(filename.tempCString, &w, &h, &n, 4); 3429 if (img is null) { 3430 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); 3431 return NVGImage.init; 3432 } 3433 auto image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList); 3434 stbi_image_free(img); 3435 return image; 3436 } 3437 } 3438 3439 static if (NanoVegaHasArsdImage) { 3440 /// Creates image by loading it from the specified memory image. 3441 /// Returns handle to the image or 0 on error. 3442 /// Group: images 3443 public NVGImage createImageFromMemoryImage() (NVGContext ctx, MemoryImage img, const(NVGImageFlag)[] imageFlagsList...) { 3444 if (img is null) return NVGImage.init; 3445 if (auto tc = cast(TrueColorImage)img) { 3446 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList); 3447 } else { 3448 auto tc = img.getAsTrueColorImage; 3449 scope(exit) tc.clearInternal(); // here, it is guaranteed that `tc` is newly allocated image, so it is safe to kill it 3450 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList); 3451 } 3452 } 3453 } else { 3454 /// Creates image by loading it from the specified chunk of memory. 3455 /// Returns handle to the image or 0 on error. 3456 /// Group: images 3457 public NVGImage createImageMem() (NVGContext ctx, const(ubyte)* data, int ndata, const(NVGImageFlag)[] imageFlagsList...) { 3458 int w, h, n, image; 3459 ubyte* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4); 3460 if (img is null) { 3461 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); 3462 return NVGImage.init; 3463 } 3464 image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList); 3465 stbi_image_free(img); 3466 return image; 3467 } 3468 } 3469 3470 /// Creates image from specified image data. 3471 /// Returns handle to the image or 0 on error. 3472 /// Group: images 3473 public NVGImage createImageRGBA (NVGContext ctx, int w, int h, const(void)[] data, const(NVGImageFlag)[] imageFlagsList...) nothrow @trusted @nogc { 3474 if (w < 1 || h < 1 || data.length < w*h*4) return NVGImage.init; 3475 uint imageFlags = 0; 3476 foreach (immutable uint flag; imageFlagsList) imageFlags |= flag; 3477 NVGImage res; 3478 res.id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.RGBA, w, h, imageFlags, cast(const(ubyte)*)data.ptr); 3479 if (res.id > 0) { 3480 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); } 3481 res.ctx = ctx; 3482 ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount 3483 } 3484 return res; 3485 } 3486 3487 /// Updates image data specified by image handle. 3488 /// Group: images 3489 public void updateImage() (NVGContext ctx, auto ref NVGImage image, const(void)[] data) nothrow @trusted @nogc { 3490 if (image.valid) { 3491 int w, h; 3492 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3493 ctx.params.renderGetTextureSize(ctx.params.userPtr, image.id, &w, &h); 3494 ctx.params.renderUpdateTexture(ctx.params.userPtr, image.id, 0, 0, w, h, cast(const(ubyte)*)data.ptr); 3495 } 3496 } 3497 3498 /// Returns the dimensions of a created image. 3499 /// Group: images 3500 public void imageSize() (NVGContext ctx, const scope auto ref NVGImage image, out int w, out int h) nothrow @trusted @nogc { 3501 if (image.valid) { 3502 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3503 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, image.id, &w, &h); 3504 } 3505 } 3506 3507 /// Deletes created image. 3508 /// Group: images 3509 public void deleteImage() (NVGContext ctx, ref NVGImage image) nothrow @trusted @nogc { 3510 if (ctx is null || !image.valid) return; 3511 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3512 image.clear(); 3513 } 3514 3515 3516 // ////////////////////////////////////////////////////////////////////////// // 3517 // Paints 3518 3519 private void linearGradient() {} // hack for dmd bug 3520 3521 static if (NanoVegaHasArsdColor) { 3522 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates 3523 * of the linear gradient, icol specifies the start color and ocol the end color. 3524 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3525 * 3526 * Group: paints 3527 */ 3528 @scriptable 3529 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 { 3530 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), NVGColor(ocol)); 3531 } 3532 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start 3533 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in 3534 * range `(0..1)`, and ocol the end color. 3535 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3536 * 3537 * Group: paints 3538 */ 3539 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 { 3540 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), midp, NVGColor(mcol), NVGColor(ocol)); 3541 } 3542 } 3543 3544 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates 3545 * of the linear gradient, icol specifies the start color and ocol the end color. 3546 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3547 * 3548 * Group: paints 3549 */ 3550 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 { 3551 enum large = 1e5f; 3552 3553 NVGPaint p = void; 3554 memset(&p, 0, p.sizeof); 3555 p.simpleColor = false; 3556 3557 // Calculate transform aligned to the line 3558 float dx = ex-sx; 3559 float dy = ey-sy; 3560 immutable float d = nvg__sqrtf(dx*dx+dy*dy); 3561 if (d > 0.0001f) { 3562 dx /= d; 3563 dy /= d; 3564 } else { 3565 dx = 0; 3566 dy = 1; 3567 } 3568 3569 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx; 3570 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy; 3571 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large; 3572 3573 p.extent.ptr[0] = large; 3574 p.extent.ptr[1] = large+d*0.5f; 3575 3576 p.radius = 0.0f; 3577 3578 p.feather = nvg__max(NVG_MIN_FEATHER, d); 3579 3580 p.innerColor = p.middleColor = icol; 3581 p.outerColor = ocol; 3582 p.midp = -1; 3583 3584 return p; 3585 } 3586 3587 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start 3588 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in 3589 * range `(0..1)`, and ocol the end color. 3590 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3591 * 3592 * Group: paints 3593 */ 3594 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 { 3595 enum large = 1e5f; 3596 3597 NVGPaint p = void; 3598 memset(&p, 0, p.sizeof); 3599 p.simpleColor = false; 3600 3601 // Calculate transform aligned to the line 3602 float dx = ex-sx; 3603 float dy = ey-sy; 3604 immutable float d = nvg__sqrtf(dx*dx+dy*dy); 3605 if (d > 0.0001f) { 3606 dx /= d; 3607 dy /= d; 3608 } else { 3609 dx = 0; 3610 dy = 1; 3611 } 3612 3613 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx; 3614 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy; 3615 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large; 3616 3617 p.extent.ptr[0] = large; 3618 p.extent.ptr[1] = large+d*0.5f; 3619 3620 p.radius = 0.0f; 3621 3622 p.feather = nvg__max(NVG_MIN_FEATHER, d); 3623 3624 if (midp <= 0) { 3625 p.innerColor = p.middleColor = mcol; 3626 p.midp = -1; 3627 } else if (midp > 1) { 3628 p.innerColor = p.middleColor = icol; 3629 p.midp = -1; 3630 } else { 3631 p.innerColor = icol; 3632 p.middleColor = mcol; 3633 p.midp = midp; 3634 } 3635 p.outerColor = ocol; 3636 3637 return p; 3638 } 3639 3640 static if (NanoVegaHasArsdColor) { 3641 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify 3642 * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. 3643 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3644 * 3645 * Group: paints 3646 */ 3647 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 { 3648 return ctx.radialGradient(cx, cy, inr, outr, NVGColor(icol), NVGColor(ocol)); 3649 } 3650 } 3651 3652 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify 3653 * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. 3654 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3655 * 3656 * Group: paints 3657 */ 3658 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 { 3659 immutable float r = (inr+outr)*0.5f; 3660 immutable float f = (outr-inr); 3661 3662 NVGPaint p = void; 3663 memset(&p, 0, p.sizeof); 3664 p.simpleColor = false; 3665 3666 p.xform.identity; 3667 p.xform.mat.ptr[4] = cx; 3668 p.xform.mat.ptr[5] = cy; 3669 3670 p.extent.ptr[0] = r; 3671 p.extent.ptr[1] = r; 3672 3673 p.radius = r; 3674 3675 p.feather = nvg__max(NVG_MIN_FEATHER, f); 3676 3677 p.innerColor = p.middleColor = icol; 3678 p.outerColor = ocol; 3679 p.midp = -1; 3680 3681 return p; 3682 } 3683 3684 static if (NanoVegaHasArsdColor) { 3685 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering 3686 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle, 3687 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry 3688 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. 3689 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3690 * 3691 * Group: paints 3692 */ 3693 public NVGPaint 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 { 3694 return ctx.boxGradient(x, y, w, h, r, f, NVGColor(icol), NVGColor(ocol)); 3695 } 3696 } 3697 3698 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering 3699 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle, 3700 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry 3701 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. 3702 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3703 * 3704 * Group: paints 3705 */ 3706 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 { 3707 NVGPaint p = void; 3708 memset(&p, 0, p.sizeof); 3709 p.simpleColor = false; 3710 3711 p.xform.identity; 3712 p.xform.mat.ptr[4] = x+w*0.5f; 3713 p.xform.mat.ptr[5] = y+h*0.5f; 3714 3715 p.extent.ptr[0] = w*0.5f; 3716 p.extent.ptr[1] = h*0.5f; 3717 3718 p.radius = r; 3719 3720 p.feather = nvg__max(NVG_MIN_FEATHER, f); 3721 3722 p.innerColor = p.middleColor = icol; 3723 p.outerColor = ocol; 3724 p.midp = -1; 3725 3726 return p; 3727 } 3728 3729 /** Creates and returns an image pattern. Parameters `(cx, cy)` specify the left-top location of the image pattern, 3730 * `(w, h)` the size of one image, [angle] rotation around the top-left corner, [image] is handle to the image to render. 3731 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3732 * 3733 * Group: paints 3734 */ 3735 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 { 3736 NVGPaint p = void; 3737 memset(&p, 0, p.sizeof); 3738 p.simpleColor = false; 3739 3740 p.xform.identity.rotate(angle); 3741 p.xform.mat.ptr[4] = cx; 3742 p.xform.mat.ptr[5] = cy; 3743 3744 p.extent.ptr[0] = w; 3745 p.extent.ptr[1] = h; 3746 3747 p.image = image; 3748 3749 p.innerColor = p.middleColor = p.outerColor = nvgRGBAf(1, 1, 1, alpha); 3750 p.midp = -1; 3751 3752 return p; 3753 } 3754 3755 /// Linear gradient with multiple stops. 3756 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3757 /// Group: paints 3758 public struct NVGLGS { 3759 private: 3760 NVGColor ic, mc, oc; // inner, middle, out 3761 float midp; 3762 NVGImage imgid; 3763 // [imagePattern] arguments 3764 float cx, cy, dimx, dimy; // dimx and dimy are ex and ey for simple gradients 3765 public float angle; /// 3766 3767 public: 3768 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (imgid.valid || midp >= -1); } /// 3769 void clear () nothrow @safe @nogc { pragma(inline, true); imgid.clear(); midp = float.nan; } /// 3770 } 3771 3772 /** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops]. 3773 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3774 * 3775 * $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3776 * Group: paints 3777 */ 3778 public NVGPaint asPaint() (NVGContext ctx, const scope auto ref NVGLGS lgs) nothrow @trusted @nogc { 3779 if (!lgs.valid) { 3780 NVGPaint p = void; 3781 memset(&p, 0, p.sizeof); 3782 nvg__setPaintColor(p, NVGColor.red); 3783 return p; 3784 } else if (lgs.midp >= -1) { 3785 return ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc); 3786 } else { 3787 return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid); 3788 } 3789 } 3790 3791 /// Gradient Stop Point. 3792 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3793 /// Group: paints 3794 public struct NVGGradientStop { 3795 float offset = 0; /// [0..1] 3796 NVGColor color; /// 3797 3798 this() (in float aofs, const scope auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } /// 3799 static if (NanoVegaHasArsdColor) { 3800 this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } /// 3801 } 3802 } 3803 3804 /// Create linear gradient data suitable to use with `linearGradient(res)`. 3805 /// Don't forget to destroy the result when you don't need it anymore with `ctx.kill(res);`. 3806 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3807 /// Group: paints 3808 public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, const(NVGGradientStop)[] stops...) nothrow @trusted @nogc { 3809 // based on the code by Jorge Acereda <jacereda@gmail.com> 3810 enum NVG_GRADIENT_SAMPLES = 1024; 3811 static void gradientSpan (uint* dst, const(NVGGradientStop)* s0, const(NVGGradientStop)* s1) nothrow @trusted @nogc { 3812 immutable float s0o = nvg__clamp(s0.offset, 0.0f, 1.0f); 3813 immutable float s1o = nvg__clamp(s1.offset, 0.0f, 1.0f); 3814 uint s = cast(uint)(s0o*NVG_GRADIENT_SAMPLES); 3815 uint e = cast(uint)(s1o*NVG_GRADIENT_SAMPLES); 3816 uint sc = 0xffffffffU; 3817 uint sh = 24; 3818 uint r = cast(uint)(s0.color.rgba[0]*sc); 3819 uint g = cast(uint)(s0.color.rgba[1]*sc); 3820 uint b = cast(uint)(s0.color.rgba[2]*sc); 3821 uint a = cast(uint)(s0.color.rgba[3]*sc); 3822 uint dr = cast(uint)((s1.color.rgba[0]*sc-r)/(e-s)); 3823 uint dg = cast(uint)((s1.color.rgba[1]*sc-g)/(e-s)); 3824 uint db = cast(uint)((s1.color.rgba[2]*sc-b)/(e-s)); 3825 uint da = cast(uint)((s1.color.rgba[3]*sc-a)/(e-s)); 3826 dst += s; 3827 foreach (immutable _; s..e) { 3828 version(BigEndian) { 3829 *dst++ = ((r>>sh)<<24)+((g>>sh)<<16)+((b>>sh)<<8)+((a>>sh)<<0); 3830 } else { 3831 *dst++ = ((a>>sh)<<24)+((b>>sh)<<16)+((g>>sh)<<8)+((r>>sh)<<0); 3832 } 3833 r += dr; 3834 g += dg; 3835 b += db; 3836 a += da; 3837 } 3838 } 3839 3840 NVGLGS res; 3841 res.cx = sx; 3842 res.cy = sy; 3843 3844 if (stops.length == 2 && stops.ptr[0].offset <= 0 && stops.ptr[1].offset >= 1) { 3845 // create simple linear gradient 3846 res.ic = res.mc = stops.ptr[0].color; 3847 res.oc = stops.ptr[1].color; 3848 res.midp = -1; 3849 res.dimx = ex; 3850 res.dimy = ey; 3851 } else if (stops.length == 3 && stops.ptr[0].offset <= 0 && stops.ptr[2].offset >= 1) { 3852 // create simple linear gradient with middle stop 3853 res.ic = stops.ptr[0].color; 3854 res.mc = stops.ptr[1].color; 3855 res.oc = stops.ptr[2].color; 3856 res.midp = stops.ptr[1].offset; 3857 res.dimx = ex; 3858 res.dimy = ey; 3859 } else { 3860 // create image gradient 3861 uint[NVG_GRADIENT_SAMPLES] data = void; 3862 immutable float w = ex-sx; 3863 immutable float h = ey-sy; 3864 res.dimx = nvg__sqrtf(w*w+h*h); 3865 res.dimy = 1; //??? 3866 3867 res.angle = 3868 (/*nvg__absf(h) < 0.0001 ? 0 : 3869 nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/ 3870 nvg__atan2f(h/*ey-sy*/, w/*ex-sx*/)); 3871 3872 if (stops.length > 0) { 3873 auto s0 = NVGGradientStop(0, nvgRGBAf(0, 0, 0, 1)); 3874 auto s1 = NVGGradientStop(1, nvgRGBAf(1, 1, 1, 1)); 3875 if (stops.length > 64) stops = stops[0..64]; 3876 if (stops.length) { 3877 s0.color = stops[0].color; 3878 s1.color = stops[$-1].color; 3879 } 3880 gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1)); 3881 foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1); 3882 gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1); 3883 res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[]/*, NVGImageFlag.RepeatX, NVGImageFlag.RepeatY*/); 3884 } 3885 } 3886 return res; 3887 } 3888 3889 3890 // ////////////////////////////////////////////////////////////////////////// // 3891 // Scissoring 3892 3893 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform. 3894 /// Group: scissoring 3895 public void scissor (NVGContext ctx, in float x, in float y, float w, float h) nothrow @trusted @nogc { 3896 NVGstate* state = nvg__getState(ctx); 3897 3898 w = nvg__max(0.0f, w); 3899 h = nvg__max(0.0f, h); 3900 3901 state.scissor.xform.identity; 3902 state.scissor.xform.mat.ptr[4] = x+w*0.5f; 3903 state.scissor.xform.mat.ptr[5] = y+h*0.5f; 3904 //nvgTransformMultiply(state.scissor.xform[], state.xform[]); 3905 state.scissor.xform.mul(state.xform); 3906 3907 state.scissor.extent.ptr[0] = w*0.5f; 3908 state.scissor.extent.ptr[1] = h*0.5f; 3909 } 3910 3911 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform. 3912 /// Arguments: [x, y, w, h]* 3913 /// Group: scissoring 3914 public void scissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 3915 enum ArgC = 4; 3916 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [scissor] call"); 3917 if (args.length < ArgC) return; 3918 NVGstate* state = nvg__getState(ctx); 3919 const(float)* aptr = args.ptr; 3920 foreach (immutable idx; 0..args.length/ArgC) { 3921 immutable x = *aptr++; 3922 immutable y = *aptr++; 3923 immutable w = nvg__max(0.0f, *aptr++); 3924 immutable h = nvg__max(0.0f, *aptr++); 3925 3926 state.scissor.xform.identity; 3927 state.scissor.xform.mat.ptr[4] = x+w*0.5f; 3928 state.scissor.xform.mat.ptr[5] = y+h*0.5f; 3929 //nvgTransformMultiply(state.scissor.xform[], state.xform[]); 3930 state.scissor.xform.mul(state.xform); 3931 3932 state.scissor.extent.ptr[0] = w*0.5f; 3933 state.scissor.extent.ptr[1] = h*0.5f; 3934 } 3935 } 3936 3937 void nvg__isectRects (float* dst, float ax, float ay, float aw, float ah, float bx, float by, float bw, float bh) nothrow @trusted @nogc { 3938 immutable float minx = nvg__max(ax, bx); 3939 immutable float miny = nvg__max(ay, by); 3940 immutable float maxx = nvg__min(ax+aw, bx+bw); 3941 immutable float maxy = nvg__min(ay+ah, by+bh); 3942 dst[0] = minx; 3943 dst[1] = miny; 3944 dst[2] = nvg__max(0.0f, maxx-minx); 3945 dst[3] = nvg__max(0.0f, maxy-miny); 3946 } 3947 3948 /** Intersects current scissor rectangle with the specified rectangle. 3949 * The scissor rectangle is transformed by the current transform. 3950 * Note: in case the rotation of previous scissor rect differs from 3951 * the current one, the intersection will be done between the specified 3952 * rectangle and the previous scissor rectangle transformed in the current 3953 * transform space. The resulting shape is always rectangle. 3954 * 3955 * Group: scissoring 3956 */ 3957 public void intersectScissor (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc { 3958 NVGstate* state = nvg__getState(ctx); 3959 3960 // If no previous scissor has been set, set the scissor as current scissor. 3961 if (state.scissor.extent.ptr[0] < 0) { 3962 ctx.scissor(x, y, w, h); 3963 return; 3964 } 3965 3966 NVGMatrix pxform = void; 3967 NVGMatrix invxorm = void; 3968 float[4] rect = void; 3969 3970 // Transform the current scissor rect into current transform space. 3971 // If there is difference in rotation, this will be approximation. 3972 //memcpy(pxform.mat.ptr, state.scissor.xform.ptr, float.sizeof*6); 3973 pxform = state.scissor.xform; 3974 immutable float ex = state.scissor.extent.ptr[0]; 3975 immutable float ey = state.scissor.extent.ptr[1]; 3976 //nvgTransformInverse(invxorm[], state.xform[]); 3977 invxorm = state.xform.inverted; 3978 //nvgTransformMultiply(pxform[], invxorm[]); 3979 pxform.mul(invxorm); 3980 immutable float tex = ex*nvg__absf(pxform.mat.ptr[0])+ey*nvg__absf(pxform.mat.ptr[2]); 3981 immutable float tey = ex*nvg__absf(pxform.mat.ptr[1])+ey*nvg__absf(pxform.mat.ptr[3]); 3982 3983 // Intersect rects. 3984 nvg__isectRects(rect.ptr, pxform.mat.ptr[4]-tex, pxform.mat.ptr[5]-tey, tex*2, tey*2, x, y, w, h); 3985 3986 //ctx.scissor(rect.ptr[0], rect.ptr[1], rect.ptr[2], rect.ptr[3]); 3987 ctx.scissor(rect.ptr[0..4]); 3988 } 3989 3990 /** Intersects current scissor rectangle with the specified rectangle. 3991 * The scissor rectangle is transformed by the current transform. 3992 * Note: in case the rotation of previous scissor rect differs from 3993 * the current one, the intersection will be done between the specified 3994 * rectangle and the previous scissor rectangle transformed in the current 3995 * transform space. The resulting shape is always rectangle. 3996 * 3997 * Arguments: [x, y, w, h]* 3998 * 3999 * Group: scissoring 4000 */ 4001 public void intersectScissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 4002 enum ArgC = 4; 4003 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [intersectScissor] call"); 4004 if (args.length < ArgC) return; 4005 const(float)* aptr = args.ptr; 4006 foreach (immutable idx; 0..args.length/ArgC) { 4007 immutable x = *aptr++; 4008 immutable y = *aptr++; 4009 immutable w = *aptr++; 4010 immutable h = *aptr++; 4011 ctx.intersectScissor(x, y, w, h); 4012 } 4013 } 4014 4015 /// Reset and disables scissoring. 4016 /// Group: scissoring 4017 public void resetScissor (NVGContext ctx) nothrow @trusted @nogc { 4018 NVGstate* state = nvg__getState(ctx); 4019 state.scissor.xform.mat[] = 0.0f; 4020 state.scissor.extent[] = -1.0f; 4021 } 4022 4023 4024 // ////////////////////////////////////////////////////////////////////////// // 4025 // Render-Time Affine Transformations 4026 4027 /// Sets GPU affine transformatin matrix. Don't do scaling or skewing here. 4028 /// This matrix won't be saved/restored with context state save/restore operations, as it is not a part of that state. 4029 /// Group: gpu_affine 4030 public void affineGPU() (NVGContext ctx, const scope auto ref NVGMatrix mat) nothrow @trusted @nogc { 4031 ctx.gpuAffine = mat; 4032 ctx.params.renderSetAffine(ctx.params.userPtr, ctx.gpuAffine); 4033 } 4034 4035 /// Get current GPU affine transformatin matrix. 4036 /// Group: gpu_affine 4037 public NVGMatrix affineGPU (NVGContext ctx) nothrow @safe @nogc { 4038 pragma(inline, true); 4039 return ctx.gpuAffine; 4040 } 4041 4042 /// "Untransform" point using current GPU affine matrix. 4043 /// Group: gpu_affine 4044 public void gpuUntransformPoint (NVGContext ctx, float *dx, float *dy, in float x, in float y) nothrow @safe @nogc { 4045 if (ctx.gpuAffine.isIdentity) { 4046 if (dx !is null) *dx = x; 4047 if (dy !is null) *dy = y; 4048 } else { 4049 // inverse GPU transformation 4050 NVGMatrix igpu = ctx.gpuAffine.inverted; 4051 igpu.point(dx, dy, x, y); 4052 } 4053 } 4054 4055 4056 // ////////////////////////////////////////////////////////////////////////// // 4057 // rasterization (tesselation) code 4058 4059 int nvg__ptEquals (float x1, float y1, float x2, float y2, float tol) pure nothrow @safe @nogc { 4060 //pragma(inline, true); 4061 immutable float dx = x2-x1; 4062 immutable float dy = y2-y1; 4063 return dx*dx+dy*dy < tol*tol; 4064 } 4065 4066 float nvg__distPtSeg (float x, float y, float px, float py, float qx, float qy) pure nothrow @safe @nogc { 4067 immutable float pqx = qx-px; 4068 immutable float pqy = qy-py; 4069 float dx = x-px; 4070 float dy = y-py; 4071 immutable float d = pqx*pqx+pqy*pqy; 4072 float t = pqx*dx+pqy*dy; 4073 if (d > 0) t /= d; 4074 if (t < 0) t = 0; else if (t > 1) t = 1; 4075 dx = px+t*pqx-x; 4076 dy = py+t*pqy-y; 4077 return dx*dx+dy*dy; 4078 } 4079 4080 void nvg__appendCommands(bool useCommand=true) (NVGContext ctx, Command acmd, const(float)[] vals...) nothrow @trusted @nogc { 4081 int nvals = cast(int)vals.length; 4082 static if (useCommand) { 4083 enum addon = 1; 4084 } else { 4085 enum addon = 0; 4086 if (nvals == 0) return; // nothing to do 4087 } 4088 4089 NVGstate* state = nvg__getState(ctx); 4090 4091 if (ctx.ncommands+nvals+addon > ctx.ccommands) { 4092 //int ccommands = ctx.ncommands+nvals+ctx.ccommands/2; 4093 int ccommands = ((ctx.ncommands+(nvals+addon))|0xfff)+1; 4094 float* commands = cast(float*)realloc(ctx.commands, float.sizeof*ccommands); 4095 if (commands is null) assert(0, "NanoVega: out of memory"); 4096 ctx.commands = commands; 4097 ctx.ccommands = ccommands; 4098 assert(ctx.ncommands+(nvals+addon) <= ctx.ccommands); 4099 } 4100 4101 static if (!useCommand) acmd = cast(Command)vals.ptr[0]; 4102 4103 if (acmd != Command.Close && acmd != Command.Winding) { 4104 //assert(nvals+addon >= 3); 4105 ctx.commandx = vals.ptr[nvals-2]; 4106 ctx.commandy = vals.ptr[nvals-1]; 4107 } 4108 4109 // copy commands 4110 float* vp = ctx.commands+ctx.ncommands; 4111 static if (useCommand) { 4112 vp[0] = cast(float)acmd; 4113 if (nvals > 0) memcpy(vp+1, vals.ptr, nvals*float.sizeof); 4114 } else { 4115 memcpy(vp, vals.ptr, nvals*float.sizeof); 4116 } 4117 ctx.ncommands += nvals+addon; 4118 4119 // transform commands 4120 int i = nvals+addon; 4121 while (i > 0) { 4122 int nlen = 1; 4123 final switch (cast(Command)(*vp)) { 4124 case Command.MoveTo: 4125 case Command.LineTo: 4126 assert(i >= 3); 4127 state.xform.point(vp+1, vp+2, vp[1], vp[2]); 4128 nlen = 3; 4129 break; 4130 case Command.BezierTo: 4131 assert(i >= 7); 4132 state.xform.point(vp+1, vp+2, vp[1], vp[2]); 4133 state.xform.point(vp+3, vp+4, vp[3], vp[4]); 4134 state.xform.point(vp+5, vp+6, vp[5], vp[6]); 4135 nlen = 7; 4136 break; 4137 case Command.Close: 4138 nlen = 1; 4139 break; 4140 case Command.Winding: 4141 nlen = 2; 4142 break; 4143 } 4144 assert(nlen > 0 && nlen <= i); 4145 i -= nlen; 4146 vp += nlen; 4147 } 4148 } 4149 4150 void nvg__clearPathCache (NVGContext ctx) nothrow @trusted @nogc { 4151 // no need to clear paths, as data is not copied there 4152 //foreach (ref p; ctx.cache.paths[0..ctx.cache.npaths]) p.clear(); 4153 ctx.cache.npoints = 0; 4154 ctx.cache.npaths = 0; 4155 ctx.cache.fillReady = ctx.cache.strokeReady = false; 4156 ctx.cache.clipmode = NVGClipMode.None; 4157 } 4158 4159 NVGpath* nvg__lastPath (NVGContext ctx) nothrow @trusted @nogc { 4160 return (ctx.cache.npaths > 0 ? &ctx.cache.paths[ctx.cache.npaths-1] : null); 4161 } 4162 4163 void nvg__addPath (NVGContext ctx) nothrow @trusted @nogc { 4164 import core.stdc.stdlib : realloc; 4165 import core.stdc.string : memset; 4166 4167 if (ctx.cache.npaths+1 > ctx.cache.cpaths) { 4168 int cpaths = ctx.cache.npaths+1+ctx.cache.cpaths/2; 4169 NVGpath* paths = cast(NVGpath*)realloc(ctx.cache.paths, NVGpath.sizeof*cpaths); 4170 if (paths is null) assert(0, "NanoVega: out of memory"); 4171 ctx.cache.paths = paths; 4172 ctx.cache.cpaths = cpaths; 4173 } 4174 4175 NVGpath* path = &ctx.cache.paths[ctx.cache.npaths++]; 4176 memset(path, 0, NVGpath.sizeof); 4177 path.first = ctx.cache.npoints; 4178 path.mWinding = NVGWinding.CCW; 4179 } 4180 4181 NVGpoint* nvg__lastPoint (NVGContext ctx) nothrow @trusted @nogc { 4182 return (ctx.cache.npoints > 0 ? &ctx.cache.points[ctx.cache.npoints-1] : null); 4183 } 4184 4185 void nvg__addPoint (NVGContext ctx, float x, float y, int flags) nothrow @trusted @nogc { 4186 NVGpath* path = nvg__lastPath(ctx); 4187 if (path is null) return; 4188 4189 if (path.count > 0 && ctx.cache.npoints > 0) { 4190 NVGpoint* pt = nvg__lastPoint(ctx); 4191 if (nvg__ptEquals(pt.x, pt.y, x, y, ctx.distTol)) { 4192 pt.flags |= flags; 4193 return; 4194 } 4195 } 4196 4197 if (ctx.cache.npoints+1 > ctx.cache.cpoints) { 4198 int cpoints = ctx.cache.npoints+1+ctx.cache.cpoints/2; 4199 NVGpoint* points = cast(NVGpoint*)realloc(ctx.cache.points, NVGpoint.sizeof*cpoints); 4200 if (points is null) return; 4201 ctx.cache.points = points; 4202 ctx.cache.cpoints = cpoints; 4203 } 4204 4205 NVGpoint* pt = &ctx.cache.points[ctx.cache.npoints]; 4206 memset(pt, 0, (*pt).sizeof); 4207 pt.x = x; 4208 pt.y = y; 4209 pt.flags = cast(ubyte)flags; 4210 4211 ++ctx.cache.npoints; 4212 ++path.count; 4213 } 4214 4215 void nvg__closePath (NVGContext ctx) nothrow @trusted @nogc { 4216 NVGpath* path = nvg__lastPath(ctx); 4217 if (path is null) return; 4218 path.closed = true; 4219 } 4220 4221 void nvg__pathWinding (NVGContext ctx, NVGWinding winding) nothrow @trusted @nogc { 4222 NVGpath* path = nvg__lastPath(ctx); 4223 if (path is null) return; 4224 path.mWinding = winding; 4225 } 4226 4227 float nvg__getAverageScale() (const scope auto ref NVGMatrix t) /*pure*/ nothrow @trusted @nogc { 4228 immutable float sx = nvg__sqrtf(t.mat.ptr[0]*t.mat.ptr[0]+t.mat.ptr[2]*t.mat.ptr[2]); 4229 immutable float sy = nvg__sqrtf(t.mat.ptr[1]*t.mat.ptr[1]+t.mat.ptr[3]*t.mat.ptr[3]); 4230 return (sx+sy)*0.5f; 4231 } 4232 4233 NVGVertex* nvg__allocTempVerts (NVGContext ctx, int nverts) nothrow @trusted @nogc { 4234 if (nverts > ctx.cache.cverts) { 4235 int cverts = (nverts+0xff)&~0xff; // Round up to prevent allocations when things change just slightly. 4236 NVGVertex* verts = cast(NVGVertex*)realloc(ctx.cache.verts, NVGVertex.sizeof*cverts); 4237 if (verts is null) return null; 4238 ctx.cache.verts = verts; 4239 ctx.cache.cverts = cverts; 4240 } 4241 4242 return ctx.cache.verts; 4243 } 4244 4245 float nvg__triarea2 (float ax, float ay, float bx, float by, float cx, float cy) pure nothrow @safe @nogc { 4246 immutable float abx = bx-ax; 4247 immutable float aby = by-ay; 4248 immutable float acx = cx-ax; 4249 immutable float acy = cy-ay; 4250 return acx*aby-abx*acy; 4251 } 4252 4253 float nvg__polyArea (NVGpoint* pts, int npts) nothrow @trusted @nogc { 4254 float area = 0; 4255 foreach (int i; 2..npts) { 4256 NVGpoint* a = &pts[0]; 4257 NVGpoint* b = &pts[i-1]; 4258 NVGpoint* c = &pts[i]; 4259 area += nvg__triarea2(a.x, a.y, b.x, b.y, c.x, c.y); 4260 } 4261 return area*0.5f; 4262 } 4263 4264 void nvg__polyReverse (NVGpoint* pts, int npts) nothrow @trusted @nogc { 4265 NVGpoint tmp = void; 4266 int i = 0, j = npts-1; 4267 while (i < j) { 4268 tmp = pts[i]; 4269 pts[i] = pts[j]; 4270 pts[j] = tmp; 4271 ++i; 4272 --j; 4273 } 4274 } 4275 4276 void nvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc { 4277 vtx.x = x; 4278 vtx.y = y; 4279 vtx.u = u; 4280 vtx.v = v; 4281 } 4282 4283 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 { 4284 if (level > 10) return; 4285 4286 // check for collinear points, and use AFD tesselator on such curves (it is WAY faster for this case) 4287 /* 4288 if (level == 0 && ctx.tesselatortype == NVGTesselation.Combined) { 4289 static bool collinear (in float v0x, in float v0y, in float v1x, in float v1y, in float v2x, in float v2y) nothrow @trusted @nogc { 4290 immutable float cz = (v1x-v0x)*(v2y-v0y)-(v2x-v0x)*(v1y-v0y); 4291 return (nvg__absf(cz*cz) <= 0.01f); // arbitrary number, seems to work ok with NanoSVG output 4292 } 4293 if (collinear(x1, y1, x2, y2, x3, y3) && collinear(x2, y2, x3, y3, x3, y4)) { 4294 //{ import core.stdc.stdio; printf("AFD fallback!\n"); } 4295 ctx.nvg__tesselateBezierAFD(x1, y1, x2, y2, x3, y3, x4, y4, type); 4296 return; 4297 } 4298 } 4299 */ 4300 4301 immutable float x12 = (x1+x2)*0.5f; 4302 immutable float y12 = (y1+y2)*0.5f; 4303 immutable float x23 = (x2+x3)*0.5f; 4304 immutable float y23 = (y2+y3)*0.5f; 4305 immutable float x34 = (x3+x4)*0.5f; 4306 immutable float y34 = (y3+y4)*0.5f; 4307 immutable float x123 = (x12+x23)*0.5f; 4308 immutable float y123 = (y12+y23)*0.5f; 4309 4310 immutable float dx = x4-x1; 4311 immutable float dy = y4-y1; 4312 immutable float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx)); 4313 immutable float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx)); 4314 4315 if ((d2+d3)*(d2+d3) < ctx.tessTol*(dx*dx+dy*dy)) { 4316 nvg__addPoint(ctx, x4, y4, type); 4317 return; 4318 } 4319 4320 immutable float x234 = (x23+x34)*0.5f; 4321 immutable float y234 = (y23+y34)*0.5f; 4322 immutable float x1234 = (x123+x234)*0.5f; 4323 immutable float y1234 = (y123+y234)*0.5f; 4324 4325 // "taxicab" / "manhattan" check for flat curves 4326 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) { 4327 nvg__addPoint(ctx, x1234, y1234, type); 4328 return; 4329 } 4330 4331 nvg__tesselateBezier(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0); 4332 nvg__tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type); 4333 } 4334 4335 // based on the ideas and code of Maxim Shemanarev. Rest in Peace, bro! 4336 // see http://www.antigrain.com/research/adaptive_bezier/index.html 4337 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 { 4338 enum CollinearEPS = 0.00000001f; // 0.00001f; 4339 enum AngleTolEPS = 0.01f; 4340 4341 static float distSquared (in float x1, in float y1, in float x2, in float y2) pure nothrow @safe @nogc { 4342 pragma(inline, true); 4343 immutable float dx = x2-x1; 4344 immutable float dy = y2-y1; 4345 return dx*dx+dy*dy; 4346 } 4347 4348 if (level == 0) { 4349 nvg__addPoint(ctx, x1, y1, 0); 4350 nvg__tesselateBezierMcSeem(ctx, x1, y1, x2, y2, x3, y3, x4, y4, 1, type); 4351 nvg__addPoint(ctx, x4, y4, type); 4352 return; 4353 } 4354 4355 if (level >= 32) return; // recurse limit; practically, it should be never reached, but... 4356 4357 // calculate all the mid-points of the line segments 4358 immutable float x12 = (x1+x2)*0.5f; 4359 immutable float y12 = (y1+y2)*0.5f; 4360 immutable float x23 = (x2+x3)*0.5f; 4361 immutable float y23 = (y2+y3)*0.5f; 4362 immutable float x34 = (x3+x4)*0.5f; 4363 immutable float y34 = (y3+y4)*0.5f; 4364 immutable float x123 = (x12+x23)*0.5f; 4365 immutable float y123 = (y12+y23)*0.5f; 4366 immutable float x234 = (x23+x34)*0.5f; 4367 immutable float y234 = (y23+y34)*0.5f; 4368 immutable float x1234 = (x123+x234)*0.5f; 4369 immutable float y1234 = (y123+y234)*0.5f; 4370 4371 // try to approximate the full cubic curve by a single straight line 4372 immutable float dx = x4-x1; 4373 immutable float dy = y4-y1; 4374 4375 float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx)); 4376 float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx)); 4377 //immutable float da1, da2, k; 4378 4379 final switch ((cast(int)(d2 > CollinearEPS)<<1)+cast(int)(d3 > CollinearEPS)) { 4380 case 0: 4381 // all collinear or p1 == p4 4382 float k = dx*dx+dy*dy; 4383 if (k == 0) { 4384 d2 = distSquared(x1, y1, x2, y2); 4385 d3 = distSquared(x4, y4, x3, y3); 4386 } else { 4387 k = 1.0f/k; 4388 float da1 = x2-x1; 4389 float da2 = y2-y1; 4390 d2 = k*(da1*dx+da2*dy); 4391 da1 = x3-x1; 4392 da2 = y3-y1; 4393 d3 = k*(da1*dx+da2*dy); 4394 if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) { 4395 // Simple collinear case, 1---2---3---4 4396 // We can leave just two endpoints 4397 return; 4398 } 4399 if (d2 <= 0) d2 = distSquared(x2, y2, x1, y1); 4400 else if (d2 >= 1) d2 = distSquared(x2, y2, x4, y4); 4401 else d2 = distSquared(x2, y2, x1+d2*dx, y1+d2*dy); 4402 4403 if (d3 <= 0) d3 = distSquared(x3, y3, x1, y1); 4404 else if (d3 >= 1) d3 = distSquared(x3, y3, x4, y4); 4405 else d3 = distSquared(x3, y3, x1+d3*dx, y1+d3*dy); 4406 } 4407 if (d2 > d3) { 4408 if (d2 < ctx.tessTol) { 4409 nvg__addPoint(ctx, x2, y2, type); 4410 return; 4411 } 4412 } if (d3 < ctx.tessTol) { 4413 nvg__addPoint(ctx, x3, y3, type); 4414 return; 4415 } 4416 break; 4417 case 1: 4418 // p1,p2,p4 are collinear, p3 is significant 4419 if (d3*d3 <= ctx.tessTol*(dx*dx+dy*dy)) { 4420 if (ctx.angleTol < AngleTolEPS) { 4421 nvg__addPoint(ctx, x23, y23, type); 4422 return; 4423 } else { 4424 // angle condition 4425 float da1 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-nvg__atan2f(y3-y2, x3-x2)); 4426 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4427 if (da1 < ctx.angleTol) { 4428 nvg__addPoint(ctx, x2, y2, type); 4429 nvg__addPoint(ctx, x3, y3, type); 4430 return; 4431 } 4432 if (ctx.cuspLimit != 0.0) { 4433 if (da1 > ctx.cuspLimit) { 4434 nvg__addPoint(ctx, x3, y3, type); 4435 return; 4436 } 4437 } 4438 } 4439 } 4440 break; 4441 case 2: 4442 // p1,p3,p4 are collinear, p2 is significant 4443 if (d2*d2 <= ctx.tessTol*(dx*dx+dy*dy)) { 4444 if (ctx.angleTol < AngleTolEPS) { 4445 nvg__addPoint(ctx, x23, y23, type); 4446 return; 4447 } else { 4448 // angle condition 4449 float da1 = nvg__absf(nvg__atan2f(y3-y2, x3-x2)-nvg__atan2f(y2-y1, x2-x1)); 4450 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4451 if (da1 < ctx.angleTol) { 4452 nvg__addPoint(ctx, x2, y2, type); 4453 nvg__addPoint(ctx, x3, y3, type); 4454 return; 4455 } 4456 if (ctx.cuspLimit != 0.0) { 4457 if (da1 > ctx.cuspLimit) { 4458 nvg__addPoint(ctx, x2, y2, type); 4459 return; 4460 } 4461 } 4462 } 4463 } 4464 break; 4465 case 3: 4466 // regular case 4467 if ((d2+d3)*(d2+d3) <= ctx.tessTol*(dx*dx+dy*dy)) { 4468 // if the curvature doesn't exceed the distance tolerance value, we tend to finish subdivisions 4469 if (ctx.angleTol < AngleTolEPS) { 4470 nvg__addPoint(ctx, x23, y23, type); 4471 return; 4472 } else { 4473 // angle and cusp condition 4474 immutable float k = nvg__atan2f(y3-y2, x3-x2); 4475 float da1 = nvg__absf(k-nvg__atan2f(y2-y1, x2-x1)); 4476 float da2 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-k); 4477 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4478 if (da2 >= NVG_PI) da2 = 2*NVG_PI-da2; 4479 if (da1+da2 < ctx.angleTol) { 4480 // finally we can stop the recursion 4481 nvg__addPoint(ctx, x23, y23, type); 4482 return; 4483 } 4484 if (ctx.cuspLimit != 0.0) { 4485 if (da1 > ctx.cuspLimit) { 4486 nvg__addPoint(ctx, x2, y2, type); 4487 return; 4488 } 4489 if (da2 > ctx.cuspLimit) { 4490 nvg__addPoint(ctx, x3, y3, type); 4491 return; 4492 } 4493 } 4494 } 4495 } 4496 break; 4497 } 4498 4499 // continue subdivision 4500 nvg__tesselateBezierMcSeem(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0); 4501 nvg__tesselateBezierMcSeem(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type); 4502 } 4503 4504 4505 // Adaptive forward differencing for bezier tesselation. 4506 // See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt. 4507 // "Adaptive forward differencing for rendering curves and surfaces." 4508 // ACM SIGGRAPH Computer Graphics. Vol. 21. No. 4. ACM, 1987. 4509 // original code by Taylor Holliday <taylor@audulus.com> 4510 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 { 4511 enum AFD_ONE = (1<<10); 4512 4513 // power basis 4514 immutable float ax = -x1+3*x2-3*x3+x4; 4515 immutable float ay = -y1+3*y2-3*y3+y4; 4516 immutable float bx = 3*x1-6*x2+3*x3; 4517 immutable float by = 3*y1-6*y2+3*y3; 4518 immutable float cx = -3*x1+3*x2; 4519 immutable float cy = -3*y1+3*y2; 4520 4521 // Transform to forward difference basis (stepsize 1) 4522 float px = x1; 4523 float py = y1; 4524 float dx = ax+bx+cx; 4525 float dy = ay+by+cy; 4526 float ddx = 6*ax+2*bx; 4527 float ddy = 6*ay+2*by; 4528 float dddx = 6*ax; 4529 float dddy = 6*ay; 4530 4531 //printf("dx: %f, dy: %f\n", dx, dy); 4532 //printf("ddx: %f, ddy: %f\n", ddx, ddy); 4533 //printf("dddx: %f, dddy: %f\n", dddx, dddy); 4534 4535 int t = 0; 4536 int dt = AFD_ONE; 4537 4538 immutable float tol = ctx.tessTol*4; 4539 4540 while (t < AFD_ONE) { 4541 // Flatness measure. 4542 float d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4543 4544 // printf("d: %f, th: %f\n", d, th); 4545 4546 // Go to higher resolution if we're moving a lot or overshooting the end. 4547 while ((d > tol && dt > 1) || (t+dt > AFD_ONE)) { 4548 // printf("up\n"); 4549 4550 // Apply L to the curve. Increase curve resolution. 4551 dx = 0.5f*dx-(1.0f/8.0f)*ddx+(1.0f/16.0f)*dddx; 4552 dy = 0.5f*dy-(1.0f/8.0f)*ddy+(1.0f/16.0f)*dddy; 4553 ddx = (1.0f/4.0f)*ddx-(1.0f/8.0f)*dddx; 4554 ddy = (1.0f/4.0f)*ddy-(1.0f/8.0f)*dddy; 4555 dddx = (1.0f/8.0f)*dddx; 4556 dddy = (1.0f/8.0f)*dddy; 4557 4558 // Half the stepsize. 4559 dt >>= 1; 4560 4561 // Recompute d 4562 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4563 } 4564 4565 // Go to lower resolution if we're really flat 4566 // and we aren't going to overshoot the end. 4567 // XXX: tol/32 is just a guess for when we are too flat. 4568 while ((d > 0 && d < tol/32.0f && dt < AFD_ONE) && (t+2*dt <= AFD_ONE)) { 4569 // printf("down\n"); 4570 4571 // Apply L^(-1) to the curve. Decrease curve resolution. 4572 dx = 2*dx+ddx; 4573 dy = 2*dy+ddy; 4574 ddx = 4*ddx+4*dddx; 4575 ddy = 4*ddy+4*dddy; 4576 dddx = 8*dddx; 4577 dddy = 8*dddy; 4578 4579 // Double the stepsize. 4580 dt <<= 1; 4581 4582 // Recompute d 4583 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4584 } 4585 4586 // Forward differencing. 4587 px += dx; 4588 py += dy; 4589 dx += ddx; 4590 dy += ddy; 4591 ddx += dddx; 4592 ddy += dddy; 4593 4594 // Output a point. 4595 nvg__addPoint(ctx, px, py, (t > 0 ? type : 0)); 4596 4597 // Advance along the curve. 4598 t += dt; 4599 4600 // Ensure we don't overshoot. 4601 assert(t <= AFD_ONE); 4602 } 4603 } 4604 4605 4606 void nvg__dashLastPath (NVGContext ctx) nothrow @trusted @nogc { 4607 import core.stdc.stdlib : realloc; 4608 import core.stdc.string : memcpy; 4609 4610 NVGpathCache* cache = ctx.cache; 4611 if (cache.npaths == 0) return; 4612 4613 NVGpath* path = nvg__lastPath(ctx); 4614 if (path is null) return; 4615 4616 NVGstate* state = nvg__getState(ctx); 4617 if (!state.dasherActive) return; 4618 4619 static NVGpoint* pts = null; 4620 static uint ptsCount = 0; 4621 static uint ptsSize = 0; 4622 4623 if (path.count < 2) return; // just in case 4624 4625 // copy path points (reserve one point for closed pathes) 4626 if (ptsSize < path.count+1) { 4627 ptsSize = cast(uint)(path.count+1); 4628 pts = cast(NVGpoint*)realloc(pts, ptsSize*NVGpoint.sizeof); 4629 if (pts is null) assert(0, "NanoVega: out of memory"); 4630 } 4631 ptsCount = cast(uint)path.count; 4632 memcpy(pts, &cache.points[path.first], ptsCount*NVGpoint.sizeof); 4633 // add closing point for closed pathes 4634 if (path.closed && !nvg__ptEquals(pts[0].x, pts[0].y, pts[ptsCount-1].x, pts[ptsCount-1].y, ctx.distTol)) { 4635 pts[ptsCount++] = pts[0]; 4636 } 4637 4638 // remove last path (with its points) 4639 --cache.npaths; 4640 cache.npoints -= path.count; 4641 4642 // add stroked pathes 4643 const(float)* dashes = state.dashes.ptr; 4644 immutable uint dashCount = state.dashCount; 4645 float currDashStart = 0; 4646 uint currDashIdx = 0; 4647 immutable bool firstIsGap = state.firstDashIsGap; 4648 4649 // calculate lengthes 4650 { 4651 NVGpoint* v1 = &pts[0]; 4652 NVGpoint* v2 = &pts[1]; 4653 foreach (immutable _; 0..ptsCount) { 4654 float dx = v2.x-v1.x; 4655 float dy = v2.y-v1.y; 4656 v1.len = nvg__normalize(&dx, &dy); 4657 v1 = v2++; 4658 } 4659 } 4660 4661 void calcDashStart (float ds) { 4662 if (ds < 0) { 4663 ds = ds%state.totalDashLen; 4664 while (ds < 0) ds += state.totalDashLen; 4665 } 4666 currDashIdx = 0; 4667 currDashStart = 0; 4668 while (ds > 0) { 4669 if (ds > dashes[currDashIdx]) { 4670 ds -= dashes[currDashIdx]; 4671 ++currDashIdx; 4672 currDashStart = 0; 4673 if (currDashIdx >= dashCount) currDashIdx = 0; 4674 } else { 4675 currDashStart = ds; 4676 ds = 0; 4677 } 4678 } 4679 } 4680 4681 calcDashStart(state.dashStart); 4682 4683 uint srcPointIdx = 1; 4684 const(NVGpoint)* v1 = &pts[0]; 4685 const(NVGpoint)* v2 = &pts[1]; 4686 float currRest = v1.len; 4687 nvg__addPath(ctx); 4688 nvg__addPoint(ctx, v1.x, v1.y, PointFlag.Corner); 4689 4690 void fixLastPoint () { 4691 auto lpt = nvg__lastPath(ctx); 4692 if (lpt !is null && lpt.count > 0) { 4693 // fix last point 4694 if (auto lps = nvg__lastPoint(ctx)) lps.flags = PointFlag.Corner; 4695 // fix first point 4696 NVGpathCache* cache = ctx.cache; 4697 cache.points[lpt.first].flags = PointFlag.Corner; 4698 } 4699 } 4700 4701 for (;;) { 4702 immutable float dlen = dashes[currDashIdx]; 4703 if (dlen == 0) { 4704 ++currDashIdx; 4705 if (currDashIdx >= dashCount) currDashIdx = 0; 4706 continue; 4707 } 4708 immutable float dashRest = dlen-currDashStart; 4709 if ((currDashIdx&1) != firstIsGap) { 4710 // this is "moveto" command, so create new path 4711 fixLastPoint(); 4712 nvg__addPath(ctx); 4713 } 4714 if (currRest > dashRest) { 4715 currRest -= dashRest; 4716 ++currDashIdx; 4717 if (currDashIdx >= dashCount) currDashIdx = 0; 4718 currDashStart = 0; 4719 nvg__addPoint(ctx, 4720 v2.x-(v2.x-v1.x)*currRest/v1.len, 4721 v2.y-(v2.y-v1.y)*currRest/v1.len, 4722 PointFlag.Corner 4723 ); 4724 } else { 4725 currDashStart += currRest; 4726 nvg__addPoint(ctx, v2.x, v2.y, v1.flags); //k8:fix flags here? 4727 ++srcPointIdx; 4728 v1 = v2; 4729 currRest = v1.len; 4730 if (srcPointIdx >= ptsCount) break; 4731 v2 = &pts[srcPointIdx]; 4732 } 4733 } 4734 fixLastPoint(); 4735 } 4736 4737 4738 version(nanovg_bench_flatten) import iv.timer : Timer; 4739 4740 void nvg__flattenPaths(bool asStroke) (NVGContext ctx) nothrow @trusted @nogc { 4741 version(nanovg_bench_flatten) { 4742 Timer timer; 4743 char[128] tmbuf; 4744 int bzcount; 4745 } 4746 NVGpathCache* cache = ctx.cache; 4747 NVGstate* state = nvg__getState(ctx); 4748 4749 // check if we already did flattening 4750 static if (asStroke) { 4751 if (state.dashCount >= 2) { 4752 if (cache.npaths > 0 && state.lastFlattenDashCount == state.dashCount) return; // already flattened 4753 state.dasherActive = true; 4754 state.lastFlattenDashCount = state.dashCount; 4755 } else { 4756 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened 4757 state.dasherActive = false; 4758 state.lastFlattenDashCount = 0; 4759 } 4760 } else { 4761 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened 4762 state.lastFlattenDashCount = 0; // so next stroke flattening will redo it 4763 state.dasherActive = false; 4764 } 4765 4766 // clear path cache 4767 cache.npaths = 0; 4768 cache.npoints = 0; 4769 4770 // flatten 4771 version(nanovg_bench_flatten) timer.restart(); 4772 int i = 0; 4773 while (i < ctx.ncommands) { 4774 final switch (cast(Command)ctx.commands[i]) { 4775 case Command.MoveTo: 4776 //assert(i+3 <= ctx.ncommands); 4777 static if (asStroke) { 4778 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx); 4779 } 4780 nvg__addPath(ctx); 4781 const p = &ctx.commands[i+1]; 4782 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner); 4783 i += 3; 4784 break; 4785 case Command.LineTo: 4786 //assert(i+3 <= ctx.ncommands); 4787 const p = &ctx.commands[i+1]; 4788 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner); 4789 i += 3; 4790 break; 4791 case Command.BezierTo: 4792 //assert(i+7 <= ctx.ncommands); 4793 const last = nvg__lastPoint(ctx); 4794 if (last !is null) { 4795 const cp1 = &ctx.commands[i+1]; 4796 const cp2 = &ctx.commands[i+3]; 4797 const p = &ctx.commands[i+5]; 4798 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) { 4799 nvg__tesselateBezier(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner); 4800 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) { 4801 nvg__tesselateBezierMcSeem(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner); 4802 } else { 4803 nvg__tesselateBezierAFD(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], PointFlag.Corner); 4804 } 4805 version(nanovg_bench_flatten) ++bzcount; 4806 } 4807 i += 7; 4808 break; 4809 case Command.Close: 4810 //assert(i+1 <= ctx.ncommands); 4811 nvg__closePath(ctx); 4812 i += 1; 4813 break; 4814 case Command.Winding: 4815 //assert(i+2 <= ctx.ncommands); 4816 nvg__pathWinding(ctx, cast(NVGWinding)ctx.commands[i+1]); 4817 i += 2; 4818 break; 4819 } 4820 } 4821 static if (asStroke) { 4822 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx); 4823 } 4824 version(nanovg_bench_flatten) {{ 4825 timer.stop(); 4826 auto xb = timer.toBuffer(tmbuf[]); 4827 import core.stdc.stdio : printf; 4828 printf("flattening time: [%.*s] (%d beziers)\n", cast(uint)xb.length, xb.ptr, bzcount); 4829 }} 4830 4831 cache.bounds.ptr[0] = cache.bounds.ptr[1] = float.max; 4832 cache.bounds.ptr[2] = cache.bounds.ptr[3] = -float.max; 4833 4834 // calculate the direction and length of line segments 4835 version(nanovg_bench_flatten) timer.restart(); 4836 foreach (int j; 0..cache.npaths) { 4837 NVGpath* path = &cache.paths[j]; 4838 NVGpoint* pts = &cache.points[path.first]; 4839 4840 // if the first and last points are the same, remove the last, mark as closed path 4841 NVGpoint* p0 = &pts[path.count-1]; 4842 NVGpoint* p1 = &pts[0]; 4843 if (nvg__ptEquals(p0.x, p0.y, p1.x, p1.y, ctx.distTol)) { 4844 --path.count; 4845 p0 = &pts[path.count-1]; 4846 path.closed = true; 4847 } 4848 4849 // enforce winding 4850 if (path.count > 2) { 4851 immutable float area = nvg__polyArea(pts, path.count); 4852 if (path.mWinding == NVGWinding.CCW && area < 0.0f) nvg__polyReverse(pts, path.count); 4853 if (path.mWinding == NVGWinding.CW && area > 0.0f) nvg__polyReverse(pts, path.count); 4854 } 4855 4856 foreach (immutable _; 0..path.count) { 4857 // calculate segment direction and length 4858 p0.dx = p1.x-p0.x; 4859 p0.dy = p1.y-p0.y; 4860 p0.len = nvg__normalize(&p0.dx, &p0.dy); 4861 // update bounds 4862 cache.bounds.ptr[0] = nvg__min(cache.bounds.ptr[0], p0.x); 4863 cache.bounds.ptr[1] = nvg__min(cache.bounds.ptr[1], p0.y); 4864 cache.bounds.ptr[2] = nvg__max(cache.bounds.ptr[2], p0.x); 4865 cache.bounds.ptr[3] = nvg__max(cache.bounds.ptr[3], p0.y); 4866 // advance 4867 p0 = p1++; 4868 } 4869 } 4870 version(nanovg_bench_flatten) {{ 4871 timer.stop(); 4872 auto xb = timer.toBuffer(tmbuf[]); 4873 import core.stdc.stdio : printf; 4874 printf("segment calculation time: [%.*s]\n", cast(uint)xb.length, xb.ptr); 4875 }} 4876 } 4877 4878 int nvg__curveDivs (float r, float arc, float tol) nothrow @trusted @nogc { 4879 immutable float da = nvg__acosf(r/(r+tol))*2.0f; 4880 return nvg__max(2, cast(int)nvg__ceilf(arc/da)); 4881 } 4882 4883 void nvg__chooseBevel (int bevel, NVGpoint* p0, NVGpoint* p1, float w, float* x0, float* y0, float* x1, float* y1) nothrow @trusted @nogc { 4884 if (bevel) { 4885 *x0 = p1.x+p0.dy*w; 4886 *y0 = p1.y-p0.dx*w; 4887 *x1 = p1.x+p1.dy*w; 4888 *y1 = p1.y-p1.dx*w; 4889 } else { 4890 *x0 = p1.x+p1.dmx*w; 4891 *y0 = p1.y+p1.dmy*w; 4892 *x1 = p1.x+p1.dmx*w; 4893 *y1 = p1.y+p1.dmy*w; 4894 } 4895 } 4896 4897 NVGVertex* nvg__roundJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, int ncap, float fringe) nothrow @trusted @nogc { 4898 float dlx0 = p0.dy; 4899 float dly0 = -p0.dx; 4900 float dlx1 = p1.dy; 4901 float dly1 = -p1.dx; 4902 //NVG_NOTUSED(fringe); 4903 4904 if (p1.flags&PointFlag.Left) { 4905 float lx0 = void, ly0 = void, lx1 = void, ly1 = void; 4906 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1); 4907 immutable float a0 = nvg__atan2f(-dly0, -dlx0); 4908 float a1 = nvg__atan2f(-dly1, -dlx1); 4909 if (a1 > a0) a1 -= NVG_PI*2; 4910 4911 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4912 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4913 4914 int n = nvg__clamp(cast(int)nvg__ceilf(((a0-a1)/NVG_PI)*ncap), 2, ncap); 4915 for (int i = 0; i < n; ++i) { 4916 float u = i/cast(float)(n-1); 4917 float a = a0+u*(a1-a0); 4918 float rx = p1.x+nvg__cosf(a)*rw; 4919 float ry = p1.y+nvg__sinf(a)*rw; 4920 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4921 nvg__vset(dst, rx, ry, ru, 1); ++dst; 4922 } 4923 4924 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4925 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4926 4927 } else { 4928 float rx0 = void, ry0 = void, rx1 = void, ry1 = void; 4929 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1); 4930 immutable float a0 = nvg__atan2f(dly0, dlx0); 4931 float a1 = nvg__atan2f(dly1, dlx1); 4932 if (a1 < a0) a1 += NVG_PI*2; 4933 4934 nvg__vset(dst, p1.x+dlx0*rw, p1.y+dly0*rw, lu, 1); ++dst; 4935 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4936 4937 int n = nvg__clamp(cast(int)nvg__ceilf(((a1-a0)/NVG_PI)*ncap), 2, ncap); 4938 for (int i = 0; i < n; i++) { 4939 float u = i/cast(float)(n-1); 4940 float a = a0+u*(a1-a0); 4941 float lx = p1.x+nvg__cosf(a)*lw; 4942 float ly = p1.y+nvg__sinf(a)*lw; 4943 nvg__vset(dst, lx, ly, lu, 1); ++dst; 4944 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4945 } 4946 4947 nvg__vset(dst, p1.x+dlx1*rw, p1.y+dly1*rw, lu, 1); ++dst; 4948 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 4949 4950 } 4951 return dst; 4952 } 4953 4954 NVGVertex* nvg__bevelJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, float fringe) nothrow @trusted @nogc { 4955 float rx0, ry0, rx1, ry1; 4956 float lx0, ly0, lx1, ly1; 4957 float dlx0 = p0.dy; 4958 float dly0 = -p0.dx; 4959 float dlx1 = p1.dy; 4960 float dly1 = -p1.dx; 4961 //NVG_NOTUSED(fringe); 4962 4963 if (p1.flags&PointFlag.Left) { 4964 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1); 4965 4966 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4967 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4968 4969 if (p1.flags&PointFlag.Bevel) { 4970 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4971 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4972 4973 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4974 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4975 } else { 4976 rx0 = p1.x-p1.dmx*rw; 4977 ry0 = p1.y-p1.dmy*rw; 4978 4979 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4980 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4981 4982 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4983 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4984 4985 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4986 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4987 } 4988 4989 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4990 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4991 4992 } else { 4993 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1); 4994 4995 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 4996 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4997 4998 if (p1.flags&PointFlag.Bevel) { 4999 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 5000 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 5001 5002 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 5003 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 5004 } else { 5005 lx0 = p1.x+p1.dmx*lw; 5006 ly0 = p1.y+p1.dmy*lw; 5007 5008 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 5009 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 5010 5011 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 5012 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 5013 5014 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 5015 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 5016 } 5017 5018 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 5019 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 5020 } 5021 5022 return dst; 5023 } 5024 5025 NVGVertex* nvg__buttCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc { 5026 immutable float px = p.x-dx*d; 5027 immutable float py = p.y-dy*d; 5028 immutable float dlx = dy; 5029 immutable float dly = -dx; 5030 nvg__vset(dst, px+dlx*w-dx*aa, py+dly*w-dy*aa, 0, 0); ++dst; 5031 nvg__vset(dst, px-dlx*w-dx*aa, py-dly*w-dy*aa, 1, 0); ++dst; 5032 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5033 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5034 return dst; 5035 } 5036 5037 NVGVertex* nvg__buttCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc { 5038 immutable float px = p.x+dx*d; 5039 immutable float py = p.y+dy*d; 5040 immutable float dlx = dy; 5041 immutable float dly = -dx; 5042 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5043 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5044 nvg__vset(dst, px+dlx*w+dx*aa, py+dly*w+dy*aa, 0, 0); ++dst; 5045 nvg__vset(dst, px-dlx*w+dx*aa, py-dly*w+dy*aa, 1, 0); ++dst; 5046 return dst; 5047 } 5048 5049 NVGVertex* nvg__roundCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc { 5050 immutable float px = p.x; 5051 immutable float py = p.y; 5052 immutable float dlx = dy; 5053 immutable float dly = -dx; 5054 //NVG_NOTUSED(aa); 5055 immutable float ncpf = cast(float)(ncap-1); 5056 foreach (int i; 0..ncap) { 5057 float a = i/*/cast(float)(ncap-1)*//ncpf*NVG_PI; 5058 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w; 5059 nvg__vset(dst, px-dlx*ax-dx*ay, py-dly*ax-dy*ay, 0, 1); ++dst; 5060 nvg__vset(dst, px, py, 0.5f, 1); ++dst; 5061 } 5062 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5063 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5064 return dst; 5065 } 5066 5067 NVGVertex* nvg__roundCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc { 5068 immutable float px = p.x; 5069 immutable float py = p.y; 5070 immutable float dlx = dy; 5071 immutable float dly = -dx; 5072 //NVG_NOTUSED(aa); 5073 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5074 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5075 immutable float ncpf = cast(float)(ncap-1); 5076 foreach (int i; 0..ncap) { 5077 float a = i/*cast(float)(ncap-1)*//ncpf*NVG_PI; 5078 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w; 5079 nvg__vset(dst, px, py, 0.5f, 1); ++dst; 5080 nvg__vset(dst, px-dlx*ax+dx*ay, py-dly*ax+dy*ay, 0, 1); ++dst; 5081 } 5082 return dst; 5083 } 5084 5085 void nvg__calculateJoins (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5086 NVGpathCache* cache = ctx.cache; 5087 float iw = 0.0f; 5088 5089 if (w > 0.0f) iw = 1.0f/w; 5090 5091 // Calculate which joins needs extra vertices to append, and gather vertex count. 5092 foreach (int i; 0..cache.npaths) { 5093 NVGpath* path = &cache.paths[i]; 5094 NVGpoint* pts = &cache.points[path.first]; 5095 NVGpoint* p0 = &pts[path.count-1]; 5096 NVGpoint* p1 = &pts[0]; 5097 int nleft = 0; 5098 5099 path.nbevel = 0; 5100 5101 foreach (int j; 0..path.count) { 5102 //float dlx0, dly0, dlx1, dly1, dmr2, cross, limit; 5103 immutable float dlx0 = p0.dy; 5104 immutable float dly0 = -p0.dx; 5105 immutable float dlx1 = p1.dy; 5106 immutable float dly1 = -p1.dx; 5107 // Calculate extrusions 5108 p1.dmx = (dlx0+dlx1)*0.5f; 5109 p1.dmy = (dly0+dly1)*0.5f; 5110 immutable float dmr2 = p1.dmx*p1.dmx+p1.dmy*p1.dmy; 5111 if (dmr2 > 0.000001f) { 5112 float scale = 1.0f/dmr2; 5113 if (scale > 600.0f) scale = 600.0f; 5114 p1.dmx *= scale; 5115 p1.dmy *= scale; 5116 } 5117 5118 // Clear flags, but keep the corner. 5119 p1.flags = (p1.flags&PointFlag.Corner) ? PointFlag.Corner : 0; 5120 5121 // Keep track of left turns. 5122 immutable float cross = p1.dx*p0.dy-p0.dx*p1.dy; 5123 if (cross > 0.0f) { 5124 nleft++; 5125 p1.flags |= PointFlag.Left; 5126 } 5127 5128 // Calculate if we should use bevel or miter for inner join. 5129 immutable float limit = nvg__max(1.01f, nvg__min(p0.len, p1.len)*iw); 5130 if ((dmr2*limit*limit) < 1.0f) p1.flags |= PointFlag.InnerBevelPR; 5131 5132 // Check to see if the corner needs to be beveled. 5133 if (p1.flags&PointFlag.Corner) { 5134 if ((dmr2*miterLimit*miterLimit) < 1.0f || lineJoin == NVGLineCap.Bevel || lineJoin == NVGLineCap.Round) { 5135 p1.flags |= PointFlag.Bevel; 5136 } 5137 } 5138 5139 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) path.nbevel++; 5140 5141 p0 = p1++; 5142 } 5143 5144 path.convex = (nleft == path.count); 5145 } 5146 } 5147 5148 void nvg__expandStroke (NVGContext ctx, float w, int lineCap, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5149 NVGpathCache* cache = ctx.cache; 5150 immutable float aa = ctx.fringeWidth; 5151 int ncap = nvg__curveDivs(w, NVG_PI, ctx.tessTol); // Calculate divisions per half circle. 5152 5153 nvg__calculateJoins(ctx, w, lineJoin, miterLimit); 5154 5155 // Calculate max vertex usage. 5156 int cverts = 0; 5157 foreach (int i; 0..cache.npaths) { 5158 NVGpath* path = &cache.paths[i]; 5159 immutable bool loop = path.closed; 5160 if (lineJoin == NVGLineCap.Round) { 5161 cverts += (path.count+path.nbevel*(ncap+2)+1)*2; // plus one for loop 5162 } else { 5163 cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop 5164 } 5165 if (!loop) { 5166 // space for caps 5167 if (lineCap == NVGLineCap.Round) { 5168 cverts += (ncap*2+2)*2; 5169 } else { 5170 cverts += (3+3)*2; 5171 } 5172 } 5173 } 5174 5175 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts); 5176 if (verts is null) return; 5177 5178 foreach (int i; 0..cache.npaths) { 5179 NVGpath* path = &cache.paths[i]; 5180 NVGpoint* pts = &cache.points[path.first]; 5181 NVGpoint* p0; 5182 NVGpoint* p1; 5183 int s, e; 5184 5185 path.fill = null; 5186 path.nfill = 0; 5187 5188 // Calculate fringe or stroke 5189 immutable bool loop = path.closed; 5190 NVGVertex* dst = verts; 5191 path.stroke = dst; 5192 5193 if (loop) { 5194 // Looping 5195 p0 = &pts[path.count-1]; 5196 p1 = &pts[0]; 5197 s = 0; 5198 e = path.count; 5199 } else { 5200 // Add cap 5201 p0 = &pts[0]; 5202 p1 = &pts[1]; 5203 s = 1; 5204 e = path.count-1; 5205 } 5206 5207 if (!loop) { 5208 // Add cap 5209 float dx = p1.x-p0.x; 5210 float dy = p1.y-p0.y; 5211 nvg__normalize(&dx, &dy); 5212 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa); 5213 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa); 5214 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa); 5215 } 5216 5217 foreach (int j; s..e) { 5218 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) { 5219 if (lineJoin == NVGLineCap.Round) { 5220 dst = nvg__roundJoin(dst, p0, p1, w, w, 0, 1, ncap, aa); 5221 } else { 5222 dst = nvg__bevelJoin(dst, p0, p1, w, w, 0, 1, aa); 5223 } 5224 } else { 5225 nvg__vset(dst, p1.x+(p1.dmx*w), p1.y+(p1.dmy*w), 0, 1); ++dst; 5226 nvg__vset(dst, p1.x-(p1.dmx*w), p1.y-(p1.dmy*w), 1, 1); ++dst; 5227 } 5228 p0 = p1++; 5229 } 5230 5231 if (loop) { 5232 // Loop it 5233 nvg__vset(dst, verts[0].x, verts[0].y, 0, 1); ++dst; 5234 nvg__vset(dst, verts[1].x, verts[1].y, 1, 1); ++dst; 5235 } else { 5236 // Add cap 5237 float dx = p1.x-p0.x; 5238 float dy = p1.y-p0.y; 5239 nvg__normalize(&dx, &dy); 5240 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa); 5241 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa); 5242 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa); 5243 } 5244 5245 path.nstroke = cast(int)(dst-verts); 5246 5247 verts = dst; 5248 } 5249 } 5250 5251 void nvg__expandFill (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5252 NVGpathCache* cache = ctx.cache; 5253 immutable float aa = ctx.fringeWidth; 5254 bool fringe = (w > 0.0f); 5255 5256 nvg__calculateJoins(ctx, w, lineJoin, miterLimit); 5257 5258 // Calculate max vertex usage. 5259 int cverts = 0; 5260 foreach (int i; 0..cache.npaths) { 5261 NVGpath* path = &cache.paths[i]; 5262 cverts += path.count+path.nbevel+1; 5263 if (fringe) cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop 5264 } 5265 5266 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts); 5267 if (verts is null) return; 5268 5269 bool convex = (cache.npaths == 1 && cache.paths[0].convex); 5270 5271 foreach (int i; 0..cache.npaths) { 5272 NVGpath* path = &cache.paths[i]; 5273 NVGpoint* pts = &cache.points[path.first]; 5274 5275 // Calculate shape vertices. 5276 immutable float woff = 0.5f*aa; 5277 NVGVertex* dst = verts; 5278 path.fill = dst; 5279 5280 if (fringe) { 5281 // Looping 5282 NVGpoint* p0 = &pts[path.count-1]; 5283 NVGpoint* p1 = &pts[0]; 5284 foreach (int j; 0..path.count) { 5285 if (p1.flags&PointFlag.Bevel) { 5286 immutable float dlx0 = p0.dy; 5287 immutable float dly0 = -p0.dx; 5288 immutable float dlx1 = p1.dy; 5289 immutable float dly1 = -p1.dx; 5290 if (p1.flags&PointFlag.Left) { 5291 immutable float lx = p1.x+p1.dmx*woff; 5292 immutable float ly = p1.y+p1.dmy*woff; 5293 nvg__vset(dst, lx, ly, 0.5f, 1); ++dst; 5294 } else { 5295 immutable float lx0 = p1.x+dlx0*woff; 5296 immutable float ly0 = p1.y+dly0*woff; 5297 immutable float lx1 = p1.x+dlx1*woff; 5298 immutable float ly1 = p1.y+dly1*woff; 5299 nvg__vset(dst, lx0, ly0, 0.5f, 1); ++dst; 5300 nvg__vset(dst, lx1, ly1, 0.5f, 1); ++dst; 5301 } 5302 } else { 5303 nvg__vset(dst, p1.x+(p1.dmx*woff), p1.y+(p1.dmy*woff), 0.5f, 1); ++dst; 5304 } 5305 p0 = p1++; 5306 } 5307 } else { 5308 foreach (int j; 0..path.count) { 5309 nvg__vset(dst, pts[j].x, pts[j].y, 0.5f, 1); 5310 ++dst; 5311 } 5312 } 5313 5314 path.nfill = cast(int)(dst-verts); 5315 verts = dst; 5316 5317 // Calculate fringe 5318 if (fringe) { 5319 float lw = w+woff; 5320 immutable float rw = w-woff; 5321 float lu = 0; 5322 immutable float ru = 1; 5323 dst = verts; 5324 path.stroke = dst; 5325 5326 // Create only half a fringe for convex shapes so that 5327 // the shape can be rendered without stenciling. 5328 if (convex) { 5329 lw = woff; // This should generate the same vertex as fill inset above. 5330 lu = 0.5f; // Set outline fade at middle. 5331 } 5332 5333 // Looping 5334 NVGpoint* p0 = &pts[path.count-1]; 5335 NVGpoint* p1 = &pts[0]; 5336 5337 foreach (int j; 0..path.count) { 5338 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) { 5339 dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx.fringeWidth); 5340 } else { 5341 nvg__vset(dst, p1.x+(p1.dmx*lw), p1.y+(p1.dmy*lw), lu, 1); ++dst; 5342 nvg__vset(dst, p1.x-(p1.dmx*rw), p1.y-(p1.dmy*rw), ru, 1); ++dst; 5343 } 5344 p0 = p1++; 5345 } 5346 5347 // Loop it 5348 nvg__vset(dst, verts[0].x, verts[0].y, lu, 1); ++dst; 5349 nvg__vset(dst, verts[1].x, verts[1].y, ru, 1); ++dst; 5350 5351 path.nstroke = cast(int)(dst-verts); 5352 verts = dst; 5353 } else { 5354 path.stroke = null; 5355 path.nstroke = 0; 5356 } 5357 } 5358 } 5359 5360 5361 // ////////////////////////////////////////////////////////////////////////// // 5362 // Paths 5363 5364 /// Clears the current path and sub-paths. 5365 /// Group: paths 5366 @scriptable 5367 public void beginPath (NVGContext ctx) nothrow @trusted @nogc { 5368 ctx.ncommands = 0; 5369 ctx.pathPickRegistered &= NVGPickKind.All; // reset "registered" flags 5370 nvg__clearPathCache(ctx); 5371 } 5372 5373 public alias newPath = beginPath; /// Ditto. 5374 5375 /// Starts new sub-path with specified point as first point. 5376 /// Group: paths 5377 @scriptable 5378 public void moveTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 5379 nvg__appendCommands(ctx, Command.MoveTo, x, y); 5380 } 5381 5382 /// Starts new sub-path with specified point as first point. 5383 /// Arguments: [x, y]* 5384 /// Group: paths 5385 public void moveTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5386 enum ArgC = 2; 5387 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [moveTo] call"); 5388 if (args.length < ArgC) return; 5389 nvg__appendCommands(ctx, Command.MoveTo, args[$-2..$]); 5390 } 5391 5392 /// Adds line segment from the last point in the path to the specified point. 5393 /// Group: paths 5394 @scriptable 5395 public void lineTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 5396 nvg__appendCommands(ctx, Command.LineTo, x, y); 5397 } 5398 5399 /// Adds line segment from the last point in the path to the specified point. 5400 /// Arguments: [x, y]* 5401 /// Group: paths 5402 public void lineTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5403 enum ArgC = 2; 5404 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [lineTo] call"); 5405 if (args.length < ArgC) return; 5406 foreach (immutable idx; 0..args.length/ArgC) { 5407 nvg__appendCommands(ctx, Command.LineTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]); 5408 } 5409 } 5410 5411 /// Adds cubic bezier segment from last point in the path via two control points to the specified point. 5412 /// Group: paths 5413 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 { 5414 nvg__appendCommands(ctx, Command.BezierTo, c1x, c1y, c2x, c2y, x, y); 5415 } 5416 5417 /// Adds cubic bezier segment from last point in the path via two control points to the specified point. 5418 /// Arguments: [c1x, c1y, c2x, c2y, x, y]* 5419 /// Group: paths 5420 public void bezierTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5421 enum ArgC = 6; 5422 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [bezierTo] call"); 5423 if (args.length < ArgC) return; 5424 foreach (immutable idx; 0..args.length/ArgC) { 5425 nvg__appendCommands(ctx, Command.BezierTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]); 5426 } 5427 } 5428 5429 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point. 5430 /// Group: paths 5431 public void quadTo (NVGContext ctx, in float cx, in float cy, in float x, in float y) nothrow @trusted @nogc { 5432 immutable float x0 = ctx.commandx; 5433 immutable float y0 = ctx.commandy; 5434 nvg__appendCommands(ctx, 5435 Command.BezierTo, 5436 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 5437 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 5438 x, y, 5439 ); 5440 } 5441 5442 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point. 5443 /// Arguments: [cx, cy, x, y]* 5444 /// Group: paths 5445 public void quadTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5446 enum ArgC = 4; 5447 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [quadTo] call"); 5448 if (args.length < ArgC) return; 5449 const(float)* aptr = args.ptr; 5450 foreach (immutable idx; 0..args.length/ArgC) { 5451 immutable float x0 = ctx.commandx; 5452 immutable float y0 = ctx.commandy; 5453 immutable float cx = *aptr++; 5454 immutable float cy = *aptr++; 5455 immutable float x = *aptr++; 5456 immutable float y = *aptr++; 5457 nvg__appendCommands(ctx, 5458 Command.BezierTo, 5459 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 5460 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 5461 x, y, 5462 ); 5463 } 5464 } 5465 5466 /// Adds an arc segment at the corner defined by the last path point, and two specified points. 5467 /// Group: paths 5468 public void arcTo (NVGContext ctx, in float x1, in float y1, in float x2, in float y2, in float radius) nothrow @trusted @nogc { 5469 if (ctx.ncommands == 0) return; 5470 5471 immutable float x0 = ctx.commandx; 5472 immutable float y0 = ctx.commandy; 5473 5474 // handle degenerate cases 5475 if (nvg__ptEquals(x0, y0, x1, y1, ctx.distTol) || 5476 nvg__ptEquals(x1, y1, x2, y2, ctx.distTol) || 5477 nvg__distPtSeg(x1, y1, x0, y0, x2, y2) < ctx.distTol*ctx.distTol || 5478 radius < ctx.distTol) 5479 { 5480 ctx.lineTo(x1, y1); 5481 return; 5482 } 5483 5484 // calculate tangential circle to lines (x0, y0)-(x1, y1) and (x1, y1)-(x2, y2) 5485 float dx0 = x0-x1; 5486 float dy0 = y0-y1; 5487 float dx1 = x2-x1; 5488 float dy1 = y2-y1; 5489 nvg__normalize(&dx0, &dy0); 5490 nvg__normalize(&dx1, &dy1); 5491 immutable float a = nvg__acosf(dx0*dx1+dy0*dy1); 5492 immutable float d = radius/nvg__tanf(a/2.0f); 5493 5494 //printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d); 5495 5496 if (d > 10000.0f) { 5497 ctx.lineTo(x1, y1); 5498 return; 5499 } 5500 5501 float cx = void, cy = void, a0 = void, a1 = void; 5502 NVGWinding dir; 5503 if (nvg__cross(dx0, dy0, dx1, dy1) > 0.0f) { 5504 cx = x1+dx0*d+dy0*radius; 5505 cy = y1+dy0*d+-dx0*radius; 5506 a0 = nvg__atan2f(dx0, -dy0); 5507 a1 = nvg__atan2f(-dx1, dy1); 5508 dir = NVGWinding.CW; 5509 //printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); 5510 } else { 5511 cx = x1+dx0*d+-dy0*radius; 5512 cy = y1+dy0*d+dx0*radius; 5513 a0 = nvg__atan2f(-dx0, dy0); 5514 a1 = nvg__atan2f(dx1, -dy1); 5515 dir = NVGWinding.CCW; 5516 //printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); 5517 } 5518 5519 ctx.arc(dir, cx, cy, radius, a0, a1); // first is line 5520 } 5521 5522 5523 /// Adds an arc segment at the corner defined by the last path point, and two specified points. 5524 /// Arguments: [x1, y1, x2, y2, radius]* 5525 /// Group: paths 5526 public void arcTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5527 enum ArgC = 5; 5528 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arcTo] call"); 5529 if (args.length < ArgC) return; 5530 if (ctx.ncommands == 0) return; 5531 const(float)* aptr = args.ptr; 5532 foreach (immutable idx; 0..args.length/ArgC) { 5533 immutable float x0 = ctx.commandx; 5534 immutable float y0 = ctx.commandy; 5535 immutable float x1 = *aptr++; 5536 immutable float y1 = *aptr++; 5537 immutable float x2 = *aptr++; 5538 immutable float y2 = *aptr++; 5539 immutable float radius = *aptr++; 5540 ctx.arcTo(x1, y1, x2, y2, radius); 5541 } 5542 } 5543 5544 /// Closes current sub-path with a line segment. 5545 /// Group: paths 5546 @scriptable 5547 public void closePath (NVGContext ctx) nothrow @trusted @nogc { 5548 nvg__appendCommands(ctx, Command.Close); 5549 } 5550 5551 /// Sets the current sub-path winding, see NVGWinding and NVGSolidity. 5552 /// Group: paths 5553 public void pathWinding (NVGContext ctx, NVGWinding dir) nothrow @trusted @nogc { 5554 nvg__appendCommands(ctx, Command.Winding, cast(float)dir); 5555 } 5556 5557 /// Ditto. 5558 public void pathWinding (NVGContext ctx, NVGSolidity dir) nothrow @trusted @nogc { 5559 nvg__appendCommands(ctx, Command.Winding, cast(float)dir); 5560 } 5561 5562 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r, 5563 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW). 5564 * Angles are specified in radians. 5565 * 5566 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo 5567 * 5568 * Group: paths 5569 */ 5570 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 { 5571 static assert(mode == "original" || mode == "move" || mode == "line"); 5572 5573 float[3+5*7+100] vals = void; 5574 //int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo); 5575 static if (mode == "original") { 5576 immutable int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo); 5577 } else static if (mode == "move") { 5578 enum move = Command.MoveTo; 5579 } else static if (mode == "line") { 5580 enum move = Command.LineTo; 5581 } else { 5582 static assert(0, "wtf?!"); 5583 } 5584 5585 // Clamp angles 5586 float da = a1-a0; 5587 if (dir == NVGWinding.CW) { 5588 if (nvg__absf(da) >= NVG_PI*2) { 5589 da = NVG_PI*2; 5590 } else { 5591 while (da < 0.0f) da += NVG_PI*2; 5592 } 5593 } else { 5594 if (nvg__absf(da) >= NVG_PI*2) { 5595 da = -NVG_PI*2; 5596 } else { 5597 while (da > 0.0f) da -= NVG_PI*2; 5598 } 5599 } 5600 5601 // Split arc into max 90 degree segments. 5602 immutable int ndivs = nvg__max(1, nvg__min(cast(int)(nvg__absf(da)/(NVG_PI*0.5f)+0.5f), 5)); 5603 immutable float hda = (da/cast(float)ndivs)/2.0f; 5604 float kappa = nvg__absf(4.0f/3.0f*(1.0f-nvg__cosf(hda))/nvg__sinf(hda)); 5605 5606 if (dir == NVGWinding.CCW) kappa = -kappa; 5607 5608 int nvals = 0; 5609 float px = 0, py = 0, ptanx = 0, ptany = 0; 5610 foreach (int i; 0..ndivs+1) { 5611 immutable float a = a0+da*(i/cast(float)ndivs); 5612 immutable float dx = nvg__cosf(a); 5613 immutable float dy = nvg__sinf(a); 5614 immutable float x = cx+dx*r; 5615 immutable float y = cy+dy*r; 5616 immutable float tanx = -dy*r*kappa; 5617 immutable float tany = dx*r*kappa; 5618 5619 if (i == 0) { 5620 if (vals.length-nvals < 3) { 5621 // flush 5622 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5623 nvals = 0; 5624 } 5625 vals.ptr[nvals++] = cast(float)move; 5626 vals.ptr[nvals++] = x; 5627 vals.ptr[nvals++] = y; 5628 } else { 5629 if (vals.length-nvals < 7) { 5630 // flush 5631 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5632 nvals = 0; 5633 } 5634 vals.ptr[nvals++] = Command.BezierTo; 5635 vals.ptr[nvals++] = px+ptanx; 5636 vals.ptr[nvals++] = py+ptany; 5637 vals.ptr[nvals++] = x-tanx; 5638 vals.ptr[nvals++] = y-tany; 5639 vals.ptr[nvals++] = x; 5640 vals.ptr[nvals++] = y; 5641 } 5642 px = x; 5643 py = y; 5644 ptanx = tanx; 5645 ptany = tany; 5646 } 5647 5648 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5649 } 5650 5651 5652 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r, 5653 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW). 5654 * Angles are specified in radians. 5655 * 5656 * Arguments: [cx, cy, r, a0, a1]* 5657 * 5658 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo 5659 * 5660 * Group: paths 5661 */ 5662 public void arc(string mode="original") (NVGContext ctx, NVGWinding dir, in float[] args) nothrow @trusted @nogc { 5663 static assert(mode == "original" || mode == "move" || mode == "line"); 5664 enum ArgC = 5; 5665 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arc] call"); 5666 if (args.length < ArgC) return; 5667 const(float)* aptr = args.ptr; 5668 foreach (immutable idx; 0..args.length/ArgC) { 5669 immutable cx = *aptr++; 5670 immutable cy = *aptr++; 5671 immutable r = *aptr++; 5672 immutable a0 = *aptr++; 5673 immutable a1 = *aptr++; 5674 ctx.arc!mode(dir, cx, cy, r, a0, a1); 5675 } 5676 } 5677 5678 /// Creates new rectangle shaped sub-path. 5679 /// Group: paths 5680 @scriptable 5681 public void rect (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc { 5682 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5683 Command.MoveTo, x, y, 5684 Command.LineTo, x, y+h, 5685 Command.LineTo, x+w, y+h, 5686 Command.LineTo, x+w, y, 5687 Command.Close, 5688 ); 5689 } 5690 5691 /// Creates new rectangle shaped sub-path. 5692 /// Arguments: [x, y, w, h]* 5693 /// Group: paths 5694 public void rect (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5695 enum ArgC = 4; 5696 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [rect] call"); 5697 if (args.length < ArgC) return; 5698 const(float)* aptr = args.ptr; 5699 foreach (immutable idx; 0..args.length/ArgC) { 5700 immutable x = *aptr++; 5701 immutable y = *aptr++; 5702 immutable w = *aptr++; 5703 immutable h = *aptr++; 5704 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5705 Command.MoveTo, x, y, 5706 Command.LineTo, x, y+h, 5707 Command.LineTo, x+w, y+h, 5708 Command.LineTo, x+w, y, 5709 Command.Close, 5710 ); 5711 } 5712 } 5713 5714 /// Creates new rounded rectangle shaped sub-path. 5715 /// Group: paths 5716 @scriptable 5717 public void roundedRect (NVGContext ctx, in float x, in float y, in float w, in float h, in float radius) nothrow @trusted @nogc { 5718 ctx.roundedRectVarying(x, y, w, h, radius, radius, radius, radius); 5719 } 5720 5721 /// Creates new rounded rectangle shaped sub-path. 5722 /// Arguments: [x, y, w, h, radius]* 5723 /// Group: paths 5724 public void roundedRect (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5725 enum ArgC = 5; 5726 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRect] call"); 5727 if (args.length < ArgC) return; 5728 const(float)* aptr = args.ptr; 5729 foreach (immutable idx; 0..args.length/ArgC) { 5730 immutable x = *aptr++; 5731 immutable y = *aptr++; 5732 immutable w = *aptr++; 5733 immutable h = *aptr++; 5734 immutable r = *aptr++; 5735 ctx.roundedRectVarying(x, y, w, h, r, r, r, r); 5736 } 5737 } 5738 5739 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it. 5740 /// Group: paths 5741 @scriptable 5742 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 { 5743 if (rw < 0.1f || rh < 0.1f) { 5744 rect(ctx, x, y, w, h); 5745 } else { 5746 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5747 Command.MoveTo, x+rw, y, 5748 Command.LineTo, x+w-rw, y, 5749 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh, 5750 Command.LineTo, x+w, y+h-rh, 5751 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h, 5752 Command.LineTo, x+rw, y+h, 5753 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh, 5754 Command.LineTo, x, y+rh, 5755 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y, 5756 Command.Close, 5757 ); 5758 } 5759 } 5760 5761 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it. 5762 /// Arguments: [x, y, w, h, rw, rh]* 5763 /// Group: paths 5764 public void roundedRectEllipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5765 enum ArgC = 6; 5766 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectEllipse] call"); 5767 if (args.length < ArgC) return; 5768 const(float)* aptr = args.ptr; 5769 foreach (immutable idx; 0..args.length/ArgC) { 5770 immutable x = *aptr++; 5771 immutable y = *aptr++; 5772 immutable w = *aptr++; 5773 immutable h = *aptr++; 5774 immutable rw = *aptr++; 5775 immutable rh = *aptr++; 5776 if (rw < 0.1f || rh < 0.1f) { 5777 rect(ctx, x, y, w, h); 5778 } else { 5779 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5780 Command.MoveTo, x+rw, y, 5781 Command.LineTo, x+w-rw, y, 5782 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh, 5783 Command.LineTo, x+w, y+h-rh, 5784 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h, 5785 Command.LineTo, x+rw, y+h, 5786 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh, 5787 Command.LineTo, x, y+rh, 5788 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y, 5789 Command.Close, 5790 ); 5791 } 5792 } 5793 } 5794 5795 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner. 5796 /// Group: paths 5797 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 { 5798 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { 5799 ctx.rect(x, y, w, h); 5800 } else { 5801 immutable float halfw = nvg__absf(w)*0.5f; 5802 immutable float halfh = nvg__absf(h)*0.5f; 5803 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h); 5804 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h); 5805 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h); 5806 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h); 5807 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5808 Command.MoveTo, x, y+ryTL, 5809 Command.LineTo, x, y+h-ryBL, 5810 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h, 5811 Command.LineTo, x+w-rxBR, y+h, 5812 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR, 5813 Command.LineTo, x+w, y+ryTR, 5814 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y, 5815 Command.LineTo, x+rxTL, y, 5816 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL, 5817 Command.Close, 5818 ); 5819 } 5820 } 5821 5822 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner. 5823 /// Arguments: [x, y, w, h, radTopLeft, radTopRight, radBottomRight, radBottomLeft]* 5824 /// Group: paths 5825 public void roundedRectVarying (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5826 enum ArgC = 8; 5827 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectVarying] call"); 5828 if (args.length < ArgC) return; 5829 const(float)* aptr = args.ptr; 5830 foreach (immutable idx; 0..args.length/ArgC) { 5831 immutable x = *aptr++; 5832 immutable y = *aptr++; 5833 immutable w = *aptr++; 5834 immutable h = *aptr++; 5835 immutable radTopLeft = *aptr++; 5836 immutable radTopRight = *aptr++; 5837 immutable radBottomRight = *aptr++; 5838 immutable radBottomLeft = *aptr++; 5839 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { 5840 ctx.rect(x, y, w, h); 5841 } else { 5842 immutable float halfw = nvg__absf(w)*0.5f; 5843 immutable float halfh = nvg__absf(h)*0.5f; 5844 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h); 5845 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h); 5846 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h); 5847 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h); 5848 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5849 Command.MoveTo, x, y+ryTL, 5850 Command.LineTo, x, y+h-ryBL, 5851 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h, 5852 Command.LineTo, x+w-rxBR, y+h, 5853 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR, 5854 Command.LineTo, x+w, y+ryTR, 5855 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y, 5856 Command.LineTo, x+rxTL, y, 5857 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL, 5858 Command.Close, 5859 ); 5860 } 5861 } 5862 } 5863 5864 /// Creates new ellipse shaped sub-path. 5865 /// Group: paths 5866 public void ellipse (NVGContext ctx, in float cx, in float cy, in float rx, in float ry) nothrow @trusted @nogc { 5867 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5868 Command.MoveTo, cx-rx, cy, 5869 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry, 5870 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy, 5871 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry, 5872 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy, 5873 Command.Close, 5874 ); 5875 } 5876 5877 /// Creates new ellipse shaped sub-path. 5878 /// Arguments: [cx, cy, rx, ry]* 5879 /// Group: paths 5880 public void ellipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5881 enum ArgC = 4; 5882 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [ellipse] call"); 5883 if (args.length < ArgC) return; 5884 const(float)* aptr = args.ptr; 5885 foreach (immutable idx; 0..args.length/ArgC) { 5886 immutable cx = *aptr++; 5887 immutable cy = *aptr++; 5888 immutable rx = *aptr++; 5889 immutable ry = *aptr++; 5890 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5891 Command.MoveTo, cx-rx, cy, 5892 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry, 5893 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy, 5894 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry, 5895 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy, 5896 Command.Close, 5897 ); 5898 } 5899 } 5900 5901 /// Creates new circle shaped sub-path. 5902 /// Group: paths 5903 public void circle (NVGContext ctx, in float cx, in float cy, in float r) nothrow @trusted @nogc { 5904 ctx.ellipse(cx, cy, r, r); 5905 } 5906 5907 /// Creates new circle shaped sub-path. 5908 /// Arguments: [cx, cy, r]* 5909 /// Group: paths 5910 public void circle (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5911 enum ArgC = 3; 5912 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [circle] call"); 5913 if (args.length < ArgC) return; 5914 const(float)* aptr = args.ptr; 5915 foreach (immutable idx; 0..args.length/ArgC) { 5916 immutable cx = *aptr++; 5917 immutable cy = *aptr++; 5918 immutable r = *aptr++; 5919 ctx.ellipse(cx, cy, r, r); 5920 } 5921 } 5922 5923 // Debug function to dump cached path data. 5924 debug public void debugDumpPathCache (NVGContext ctx) nothrow @trusted @nogc { 5925 import core.stdc.stdio : printf; 5926 const(NVGpath)* path; 5927 printf("Dumping %d cached paths\n", ctx.cache.npaths); 5928 for (int i = 0; i < ctx.cache.npaths; ++i) { 5929 path = &ctx.cache.paths[i]; 5930 printf("-Path %d\n", i); 5931 if (path.nfill) { 5932 printf("-fill: %d\n", path.nfill); 5933 for (int j = 0; j < path.nfill; ++j) printf("%f\t%f\n", path.fill[j].x, path.fill[j].y); 5934 } 5935 if (path.nstroke) { 5936 printf("-stroke: %d\n", path.nstroke); 5937 for (int j = 0; j < path.nstroke; ++j) printf("%f\t%f\n", path.stroke[j].x, path.stroke[j].y); 5938 } 5939 } 5940 } 5941 5942 // Flatten path, prepare it for fill operation. 5943 void nvg__prepareFill (NVGContext ctx) nothrow @trusted @nogc { 5944 NVGpathCache* cache = ctx.cache; 5945 NVGstate* state = nvg__getState(ctx); 5946 5947 nvg__flattenPaths!false(ctx); 5948 5949 if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) { 5950 nvg__expandFill(ctx, ctx.fringeWidth, NVGLineCap.Miter, 2.4f); 5951 } else { 5952 nvg__expandFill(ctx, 0.0f, NVGLineCap.Miter, 2.4f); 5953 } 5954 5955 cache.evenOddMode = state.evenOddMode; 5956 cache.fringeWidth = ctx.fringeWidth; 5957 cache.fillReady = true; 5958 cache.strokeReady = false; 5959 cache.clipmode = NVGClipMode.None; 5960 } 5961 5962 // Flatten path, prepare it for stroke operation. 5963 void nvg__prepareStroke (NVGContext ctx) nothrow @trusted @nogc { 5964 NVGstate* state = nvg__getState(ctx); 5965 NVGpathCache* cache = ctx.cache; 5966 5967 nvg__flattenPaths!true(ctx); 5968 5969 immutable float scale = nvg__getAverageScale(state.xform); 5970 float strokeWidth = nvg__clamp(state.strokeWidth*scale, 0.0f, 200.0f); 5971 5972 if (strokeWidth < ctx.fringeWidth) { 5973 // If the stroke width is less than pixel size, use alpha to emulate coverage. 5974 // Since coverage is area, scale by alpha*alpha. 5975 immutable float alpha = nvg__clamp(strokeWidth/ctx.fringeWidth, 0.0f, 1.0f); 5976 cache.strokeAlphaMul = alpha*alpha; 5977 strokeWidth = ctx.fringeWidth; 5978 } else { 5979 cache.strokeAlphaMul = 1.0f; 5980 } 5981 cache.strokeWidth = strokeWidth; 5982 5983 if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) { 5984 nvg__expandStroke(ctx, strokeWidth*0.5f+ctx.fringeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit); 5985 } else { 5986 nvg__expandStroke(ctx, strokeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit); 5987 } 5988 5989 cache.fringeWidth = ctx.fringeWidth; 5990 cache.fillReady = false; 5991 cache.strokeReady = true; 5992 cache.clipmode = NVGClipMode.None; 5993 } 5994 5995 /// Fills the current path with current fill style. 5996 /// Group: paths 5997 @scriptable 5998 public void fill (NVGContext ctx) nothrow @trusted @nogc { 5999 NVGstate* state = nvg__getState(ctx); 6000 6001 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) { 6002 ctx.pathPickRegistered |= NVGPickKind.Fill<<16; 6003 ctx.currFillHitId = ctx.pathPickId; 6004 } 6005 6006 nvg__prepareFill(ctx); 6007 6008 // apply global alpha 6009 NVGPaint fillPaint = state.fill; 6010 fillPaint.innerColor.a *= state.alpha; 6011 fillPaint.middleColor.a *= state.alpha; 6012 fillPaint.outerColor.a *= state.alpha; 6013 6014 ctx.appendCurrentPathToCache(ctx.recset, state.fill); 6015 6016 if (ctx.recblockdraw) return; 6017 6018 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); 6019 6020 // count triangles 6021 foreach (int i; 0..ctx.cache.npaths) { 6022 NVGpath* path = &ctx.cache.paths[i]; 6023 ctx.fillTriCount += path.nfill-2; 6024 ctx.fillTriCount += path.nstroke-2; 6025 ctx.drawCallCount += 2; 6026 } 6027 } 6028 6029 /// Fills the current path with current stroke style. 6030 /// Group: paths 6031 @scriptable 6032 public void stroke (NVGContext ctx) nothrow @trusted @nogc { 6033 NVGstate* state = nvg__getState(ctx); 6034 6035 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) { 6036 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16; 6037 ctx.currStrokeHitId = ctx.pathPickId; 6038 } 6039 6040 nvg__prepareStroke(ctx); 6041 6042 NVGpathCache* cache = ctx.cache; 6043 6044 NVGPaint strokePaint = state.stroke; 6045 strokePaint.innerColor.a *= cache.strokeAlphaMul; 6046 strokePaint.middleColor.a *= cache.strokeAlphaMul; 6047 strokePaint.outerColor.a *= cache.strokeAlphaMul; 6048 6049 // apply global alpha 6050 strokePaint.innerColor.a *= state.alpha; 6051 strokePaint.middleColor.a *= state.alpha; 6052 strokePaint.outerColor.a *= state.alpha; 6053 6054 ctx.appendCurrentPathToCache(ctx.recset, state.stroke); 6055 6056 if (ctx.recblockdraw) return; 6057 6058 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths); 6059 6060 // count triangles 6061 foreach (int i; 0..ctx.cache.npaths) { 6062 NVGpath* path = &ctx.cache.paths[i]; 6063 ctx.strokeTriCount += path.nstroke-2; 6064 ++ctx.drawCallCount; 6065 } 6066 } 6067 6068 /// Sets current path as clipping region. 6069 /// Group: clipping 6070 public void clip (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc { 6071 NVGstate* state = nvg__getState(ctx); 6072 6073 if (aclipmode == NVGClipMode.None) return; 6074 if (ctx.recblockdraw) return; //??? 6075 6076 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr); 6077 6078 /* 6079 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) { 6080 ctx.pathPickRegistered |= NVGPickKind.Fill<<16; 6081 ctx.currFillHitId = ctx.pathPickId; 6082 } 6083 */ 6084 6085 nvg__prepareFill(ctx); 6086 6087 // apply global alpha 6088 NVGPaint fillPaint = state.fill; 6089 fillPaint.innerColor.a *= state.alpha; 6090 fillPaint.middleColor.a *= state.alpha; 6091 fillPaint.outerColor.a *= state.alpha; 6092 6093 //ctx.appendCurrentPathToCache(ctx.recset, state.fill); 6094 6095 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); 6096 6097 // count triangles 6098 foreach (int i; 0..ctx.cache.npaths) { 6099 NVGpath* path = &ctx.cache.paths[i]; 6100 ctx.fillTriCount += path.nfill-2; 6101 ctx.fillTriCount += path.nstroke-2; 6102 ctx.drawCallCount += 2; 6103 } 6104 } 6105 6106 /// Sets current path as clipping region. 6107 /// Group: clipping 6108 public alias clipFill = clip; 6109 6110 /// Sets current path' stroke as clipping region. 6111 /// Group: clipping 6112 public void clipStroke (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc { 6113 NVGstate* state = nvg__getState(ctx); 6114 6115 if (aclipmode == NVGClipMode.None) return; 6116 if (ctx.recblockdraw) return; //??? 6117 6118 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr); 6119 6120 /* 6121 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) { 6122 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16; 6123 ctx.currStrokeHitId = ctx.pathPickId; 6124 } 6125 */ 6126 6127 nvg__prepareStroke(ctx); 6128 6129 NVGpathCache* cache = ctx.cache; 6130 6131 NVGPaint strokePaint = state.stroke; 6132 strokePaint.innerColor.a *= cache.strokeAlphaMul; 6133 strokePaint.middleColor.a *= cache.strokeAlphaMul; 6134 strokePaint.outerColor.a *= cache.strokeAlphaMul; 6135 6136 // apply global alpha 6137 strokePaint.innerColor.a *= state.alpha; 6138 strokePaint.middleColor.a *= state.alpha; 6139 strokePaint.outerColor.a *= state.alpha; 6140 6141 //ctx.appendCurrentPathToCache(ctx.recset, state.stroke); 6142 6143 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, aclipmode, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths); 6144 6145 // count triangles 6146 foreach (int i; 0..ctx.cache.npaths) { 6147 NVGpath* path = &ctx.cache.paths[i]; 6148 ctx.strokeTriCount += path.nstroke-2; 6149 ++ctx.drawCallCount; 6150 } 6151 } 6152 6153 6154 // ////////////////////////////////////////////////////////////////////////// // 6155 // Picking API 6156 6157 // most of the code is by Michael Wynne <mike@mikesspace.net> 6158 // https://github.com/memononen/nanovg/pull/230 6159 // https://github.com/MikeWW/nanovg 6160 6161 /// Pick type query. Used in [hitTest] and [hitTestAll]. 6162 /// Group: picking_api 6163 public enum NVGPickKind : ubyte { 6164 Fill = 0x01, /// 6165 Stroke = 0x02, /// 6166 All = 0x03, /// 6167 } 6168 6169 /// Marks the fill of the current path as pickable with the specified id. 6170 /// Note that you can create and mark path without rasterizing it. 6171 /// Group: picking_api 6172 public void currFillHitId (NVGContext ctx, int id) nothrow @trusted @nogc { 6173 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6174 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/false); 6175 nvg__pickSceneInsert(ps, pp); 6176 } 6177 6178 public alias currFillPickId = currFillHitId; /// Ditto. 6179 6180 /// Marks the stroke of the current path as pickable with the specified id. 6181 /// Note that you can create and mark path without rasterizing it. 6182 /// Group: picking_api 6183 public void currStrokeHitId (NVGContext ctx, int id) nothrow @trusted @nogc { 6184 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6185 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/true); 6186 nvg__pickSceneInsert(ps, pp); 6187 } 6188 6189 public alias currStrokePickId = currStrokeHitId; /// Ditto. 6190 6191 // Marks the saved path set (fill) as pickable with the specified id. 6192 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!) 6193 // Group: picking_api 6194 /+ 6195 public void pathSetFillHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc { 6196 if (svp is null) return; 6197 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context"); 6198 foreach (ref cp; svp.caches[0..svp.ncaches]) { 6199 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6200 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/false); 6201 nvg__pickSceneInsert(ps, pp); 6202 } 6203 } 6204 +/ 6205 6206 // Marks the saved path set (stroke) as pickable with the specified id. 6207 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!) 6208 // Group: picking_api 6209 /+ 6210 public void pathSetStrokeHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc { 6211 if (svp is null) return; 6212 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context"); 6213 foreach (ref cp; svp.caches[0..svp.ncaches]) { 6214 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6215 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/true); 6216 nvg__pickSceneInsert(ps, pp); 6217 } 6218 } 6219 +/ 6220 6221 private template IsGoodHitTestDG(DG) { 6222 enum IsGoodHitTestDG = 6223 __traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); }) || 6224 __traits(compiles, (){ DG dg; dg(cast(int)42, cast(int)666); }); 6225 } 6226 6227 private template IsGoodHitTestInternalDG(DG) { 6228 enum IsGoodHitTestInternalDG = 6229 __traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); }) || 6230 __traits(compiles, (){ DG dg; NVGpickPath* pp; dg(pp); }); 6231 } 6232 6233 /// Call delegate [dg] for each path under the specified position (in no particular order). 6234 /// Returns the id of the path for which delegate [dg] returned true or [NVGNoPick]. 6235 /// dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending). 6236 /// Group: picking_api 6237 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) { 6238 if (ctx.pickScene is null || ctx.pickScene.npaths == 0 || (kind&NVGPickKind.All) == 0) return -1; 6239 6240 NVGpickScene* ps = ctx.pickScene; 6241 int levelwidth = 1<<(ps.nlevels-1); 6242 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth); 6243 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth); 6244 int npicked = 0; 6245 6246 // if we are interested only in most-toplevel path, there is no reason to check paths with worser order. 6247 // but we cannot just get out on the first path found, 'cause we are using quad tree to speed up bounds 6248 // checking, so path walking order is not guaranteed. 6249 static if (bestOrder) { 6250 int lastBestOrder = int.min; 6251 } 6252 6253 //{ import core.stdc.stdio; printf("npaths=%d\n", ps.npaths); } 6254 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) { 6255 for (NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; pp !is null; pp = pp.next) { 6256 //{ 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); } 6257 static if (bestOrder) { 6258 // reject earlier paths 6259 if (pp.order <= lastBestOrder) continue; // not interesting 6260 } 6261 immutable uint kpx = kind&pp.flags&3; 6262 if (kpx == 0) continue; // not interesting 6263 if (!nvg__pickPathTestBounds(ctx, ps, pp, x, y)) continue; // not interesting 6264 //{ import core.stdc.stdio; printf("in bounds!\n"); } 6265 int hit = 0; 6266 if (kpx&NVGPickKind.Stroke) hit = nvg__pickPathStroke(ps, pp, x, y); 6267 if (!hit && (kpx&NVGPickKind.Fill)) hit = nvg__pickPath(ps, pp, x, y); 6268 if (!hit) continue; 6269 //{ import core.stdc.stdio; printf(" HIT!\n"); } 6270 static if (bestOrder) lastBestOrder = pp.order; 6271 static if (IsGoodHitTestDG!DG) { 6272 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) { 6273 if (dg(pp.id, cast(int)pp.order)) return pp.id; 6274 } else { 6275 dg(pp.id, cast(int)pp.order); 6276 } 6277 } else { 6278 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) { 6279 if (dg(pp)) return pp.id; 6280 } else { 6281 dg(pp); 6282 } 6283 } 6284 } 6285 cellx >>= 1; 6286 celly >>= 1; 6287 levelwidth >>= 1; 6288 } 6289 6290 return -1; 6291 } 6292 6293 /// Fills ids with a list of the top most hit ids (from bottom to top) under the specified position. 6294 /// Returns the slice of [ids]. 6295 /// Group: picking_api 6296 public int[] hitTestAll (NVGContext ctx, in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc { 6297 if (ctx.pickScene is null || ids.length == 0) return ids[0..0]; 6298 6299 int npicked = 0; 6300 NVGpickScene* ps = ctx.pickScene; 6301 6302 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 6303 if (npicked == ps.cpicked) { 6304 int cpicked = ps.cpicked+ps.cpicked; 6305 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked); 6306 if (picked is null) return true; // abort 6307 ps.cpicked = cpicked; 6308 ps.picked = picked; 6309 } 6310 ps.picked[npicked] = pp; 6311 ++npicked; 6312 return false; // go on 6313 }); 6314 6315 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths); 6316 6317 assert(npicked >= 0); 6318 if (npicked > ids.length) npicked = cast(int)ids.length; 6319 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id; 6320 6321 return ids[0..npicked]; 6322 } 6323 6324 /// Returns the id of the pickable shape containing x,y or [NVGNoPick] if no shape was found. 6325 /// Group: picking_api 6326 public int hitTest (NVGContext ctx, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc { 6327 if (ctx.pickScene is null) return -1; 6328 6329 int bestOrder = int.min; 6330 int bestID = -1; 6331 6332 ctx.hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) { 6333 if (pp.order > bestOrder) { 6334 bestOrder = pp.order; 6335 bestID = pp.id; 6336 } 6337 }); 6338 6339 return bestID; 6340 } 6341 6342 /// Returns `true` if the path with the given id contains x,y. 6343 /// Group: picking_api 6344 public bool hitTestForId (NVGContext ctx, in int id, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc { 6345 if (ctx.pickScene is null || id == NVGNoPick) return false; 6346 6347 bool res = false; 6348 6349 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) { 6350 if (pp.id == id) { 6351 res = true; 6352 return true; // stop 6353 } 6354 return false; // continue 6355 }); 6356 6357 return res; 6358 } 6359 6360 /// Returns `true` if the given point is within the fill of the currently defined path. 6361 /// This operation can be done before rasterizing the current path. 6362 /// Group: picking_api 6363 public bool hitTestCurrFill (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 6364 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6365 int oldnpoints = ps.npoints; 6366 int oldnsegments = ps.nsegments; 6367 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/false); 6368 if (pp is null) return false; // oops 6369 scope(exit) { 6370 nvg__freePickPath(ps, pp); 6371 ps.npoints = oldnpoints; 6372 ps.nsegments = oldnsegments; 6373 } 6374 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPath(ps, pp, x, y) : false); 6375 } 6376 6377 public alias isPointInPath = hitTestCurrFill; /// Ditto. 6378 6379 /// Returns `true` if the given point is within the stroke of the currently defined path. 6380 /// This operation can be done before rasterizing the current path. 6381 /// Group: picking_api 6382 public bool hitTestCurrStroke (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 6383 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6384 int oldnpoints = ps.npoints; 6385 int oldnsegments = ps.nsegments; 6386 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/true); 6387 if (pp is null) return false; // oops 6388 scope(exit) { 6389 nvg__freePickPath(ps, pp); 6390 ps.npoints = oldnpoints; 6391 ps.nsegments = oldnsegments; 6392 } 6393 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPathStroke(ps, pp, x, y) : false); 6394 } 6395 6396 6397 nothrow @trusted @nogc { 6398 extern(C) { 6399 private alias _compare_fp_t = int function (const void*, const void*) nothrow @nogc; 6400 private extern(C) void qsort (scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc; 6401 6402 extern(C) int nvg__comparePaths (const void* a, const void* b) { 6403 return (*cast(const(NVGpickPath)**)b).order-(*cast(const(NVGpickPath)**)a).order; 6404 } 6405 } 6406 6407 enum NVGPickEPS = 0.0001f; 6408 6409 // Segment flags 6410 enum NVGSegmentFlags { 6411 Corner = 1, 6412 Bevel = 2, 6413 InnerBevel = 4, 6414 Cap = 8, 6415 Endcap = 16, 6416 } 6417 6418 // Path flags 6419 enum NVGPathFlags : ushort { 6420 Fill = NVGPickKind.Fill, 6421 Stroke = NVGPickKind.Stroke, 6422 Scissor = 0x80, 6423 } 6424 6425 struct NVGsegment { 6426 int firstPoint; // Index into NVGpickScene.points 6427 short type; // NVG_LINETO or NVG_BEZIERTO 6428 short flags; // Flags relate to the corner between the prev segment and this one. 6429 float[4] bounds; 6430 float[2] startDir; // Direction at t == 0 6431 float[2] endDir; // Direction at t == 1 6432 float[2] miterDir; // Direction of miter of corner between the prev segment and this one. 6433 } 6434 6435 struct NVGpickSubPath { 6436 short winding; // TODO: Merge to flag field 6437 bool closed; // TODO: Merge to flag field 6438 6439 int firstSegment; // Index into NVGpickScene.segments 6440 int nsegments; 6441 6442 float[4] bounds; 6443 6444 NVGpickSubPath* next; 6445 } 6446 6447 struct NVGpickPath { 6448 int id; 6449 short flags; 6450 short order; 6451 float strokeWidth; 6452 float miterLimit; 6453 short lineCap; 6454 short lineJoin; 6455 bool evenOddMode; 6456 6457 float[4] bounds; 6458 int scissor; // Indexes into ps->points and defines scissor rect as XVec, YVec and Center 6459 6460 NVGpickSubPath* subPaths; 6461 NVGpickPath* next; 6462 NVGpickPath* cellnext; 6463 } 6464 6465 struct NVGpickScene { 6466 int npaths; 6467 6468 NVGpickPath* paths; // Linked list of paths 6469 NVGpickPath* lastPath; // The last path in the paths linked list (the first path added) 6470 NVGpickPath* freePaths; // Linked list of free paths 6471 6472 NVGpickSubPath* freeSubPaths; // Linked list of free sub paths 6473 6474 int width; 6475 int height; 6476 6477 // Points for all path sub paths. 6478 float* points; 6479 int npoints; 6480 int cpoints; 6481 6482 // Segments for all path sub paths 6483 NVGsegment* segments; 6484 int nsegments; 6485 int csegments; 6486 6487 // Implicit quadtree 6488 float xdim; // Width / (1 << nlevels) 6489 float ydim; // Height / (1 << nlevels) 6490 int ncells; // Total number of cells in all levels 6491 int nlevels; 6492 NVGpickPath*** levels; // Index: [Level][LevelY * LevelW + LevelX] Value: Linked list of paths 6493 6494 // Temp storage for picking 6495 int cpicked; 6496 NVGpickPath** picked; 6497 } 6498 6499 6500 // bounds utilities 6501 void nvg__initBounds (ref float[4] bounds) { 6502 bounds.ptr[0] = bounds.ptr[1] = float.max; 6503 bounds.ptr[2] = bounds.ptr[3] = -float.max; 6504 } 6505 6506 void nvg__expandBounds (ref float[4] bounds, const(float)* points, int npoints) { 6507 npoints *= 2; 6508 for (int i = 0; i < npoints; i += 2) { 6509 bounds.ptr[0] = nvg__min(bounds.ptr[0], points[i]); 6510 bounds.ptr[1] = nvg__min(bounds.ptr[1], points[i+1]); 6511 bounds.ptr[2] = nvg__max(bounds.ptr[2], points[i]); 6512 bounds.ptr[3] = nvg__max(bounds.ptr[3], points[i+1]); 6513 } 6514 } 6515 6516 void nvg__unionBounds (ref float[4] bounds, const scope ref float[4] boundsB) { 6517 bounds.ptr[0] = nvg__min(bounds.ptr[0], boundsB.ptr[0]); 6518 bounds.ptr[1] = nvg__min(bounds.ptr[1], boundsB.ptr[1]); 6519 bounds.ptr[2] = nvg__max(bounds.ptr[2], boundsB.ptr[2]); 6520 bounds.ptr[3] = nvg__max(bounds.ptr[3], boundsB.ptr[3]); 6521 } 6522 6523 void nvg__intersectBounds (ref float[4] bounds, const scope ref float[4] boundsB) { 6524 bounds.ptr[0] = nvg__max(boundsB.ptr[0], bounds.ptr[0]); 6525 bounds.ptr[1] = nvg__max(boundsB.ptr[1], bounds.ptr[1]); 6526 bounds.ptr[2] = nvg__min(boundsB.ptr[2], bounds.ptr[2]); 6527 bounds.ptr[3] = nvg__min(boundsB.ptr[3], bounds.ptr[3]); 6528 6529 bounds.ptr[2] = nvg__max(bounds.ptr[0], bounds.ptr[2]); 6530 bounds.ptr[3] = nvg__max(bounds.ptr[1], bounds.ptr[3]); 6531 } 6532 6533 bool nvg__pointInBounds (in float x, in float y, const scope ref float[4] bounds) { 6534 pragma(inline, true); 6535 return (x >= bounds.ptr[0] && x <= bounds.ptr[2] && y >= bounds.ptr[1] && y <= bounds.ptr[3]); 6536 } 6537 6538 // building paths & sub paths 6539 int nvg__pickSceneAddPoints (NVGpickScene* ps, const(float)* xy, int n) { 6540 import core.stdc.string : memcpy; 6541 if (ps.npoints+n > ps.cpoints) { 6542 import core.stdc.stdlib : realloc; 6543 int cpoints = ps.npoints+n+(ps.cpoints<<1); 6544 float* points = cast(float*)realloc(ps.points, float.sizeof*2*cpoints); 6545 if (points is null) assert(0, "NanoVega: out of memory"); 6546 ps.points = points; 6547 ps.cpoints = cpoints; 6548 } 6549 int i = ps.npoints; 6550 if (xy !is null) memcpy(&ps.points[i*2], xy, float.sizeof*2*n); 6551 ps.npoints += n; 6552 return i; 6553 } 6554 6555 void nvg__pickSubPathAddSegment (NVGpickScene* ps, NVGpickSubPath* psp, int firstPoint, int type, short flags) { 6556 NVGsegment* seg = null; 6557 if (ps.nsegments == ps.csegments) { 6558 int csegments = 1+ps.csegments+(ps.csegments<<1); 6559 NVGsegment* segments = cast(NVGsegment*)realloc(ps.segments, NVGsegment.sizeof*csegments); 6560 if (segments is null) assert(0, "NanoVega: out of memory"); 6561 ps.segments = segments; 6562 ps.csegments = csegments; 6563 } 6564 6565 if (psp.firstSegment == -1) psp.firstSegment = ps.nsegments; 6566 6567 seg = &ps.segments[ps.nsegments]; 6568 ++ps.nsegments; 6569 seg.firstPoint = firstPoint; 6570 seg.type = cast(short)type; 6571 seg.flags = flags; 6572 ++psp.nsegments; 6573 6574 nvg__segmentDir(ps, psp, seg, 0, seg.startDir); 6575 nvg__segmentDir(ps, psp, seg, 1, seg.endDir); 6576 } 6577 6578 void nvg__segmentDir (NVGpickScene* ps, NVGpickSubPath* psp, NVGsegment* seg, float t, ref float[2] d) { 6579 const(float)* points = &ps.points[seg.firstPoint*2]; 6580 immutable float x0 = points[0*2+0], x1 = points[1*2+0]; 6581 immutable float y0 = points[0*2+1], y1 = points[1*2+1]; 6582 switch (seg.type) { 6583 case Command.LineTo: 6584 d.ptr[0] = x1-x0; 6585 d.ptr[1] = y1-y0; 6586 nvg__normalize(&d.ptr[0], &d.ptr[1]); 6587 break; 6588 case Command.BezierTo: 6589 immutable float x2 = points[2*2+0]; 6590 immutable float y2 = points[2*2+1]; 6591 immutable float x3 = points[3*2+0]; 6592 immutable float y3 = points[3*2+1]; 6593 6594 immutable float omt = 1.0f-t; 6595 immutable float omt2 = omt*omt; 6596 immutable float t2 = t*t; 6597 6598 d.ptr[0] = 6599 3.0f*omt2*(x1-x0)+ 6600 6.0f*omt*t*(x2-x1)+ 6601 3.0f*t2*(x3-x2); 6602 6603 d.ptr[1] = 6604 3.0f*omt2*(y1-y0)+ 6605 6.0f*omt*t*(y2-y1)+ 6606 3.0f*t2*(y3-y2); 6607 6608 nvg__normalize(&d.ptr[0], &d.ptr[1]); 6609 break; 6610 default: 6611 break; 6612 } 6613 } 6614 6615 void nvg__pickSubPathAddFillSupports (NVGpickScene* ps, NVGpickSubPath* psp) { 6616 if (psp.firstSegment == -1) return; 6617 NVGsegment* segments = &ps.segments[psp.firstSegment]; 6618 for (int s = 0; s < psp.nsegments; ++s) { 6619 NVGsegment* seg = &segments[s]; 6620 const(float)* points = &ps.points[seg.firstPoint*2]; 6621 if (seg.type == Command.LineTo) { 6622 nvg__initBounds(seg.bounds); 6623 nvg__expandBounds(seg.bounds, points, 2); 6624 } else { 6625 nvg__bezierBounds(points, seg.bounds); 6626 } 6627 } 6628 } 6629 6630 void nvg__pickSubPathAddStrokeSupports (NVGpickScene* ps, NVGpickSubPath* psp, float strokeWidth, int lineCap, int lineJoin, float miterLimit) { 6631 if (psp.firstSegment == -1) return; 6632 immutable bool closed = psp.closed; 6633 const(float)* points = ps.points; 6634 NVGsegment* seg = null; 6635 NVGsegment* segments = &ps.segments[psp.firstSegment]; 6636 int nsegments = psp.nsegments; 6637 NVGsegment* prevseg = (closed ? &segments[psp.nsegments-1] : null); 6638 6639 int ns = 0; // nsupports 6640 float[32] supportingPoints = void; 6641 int firstPoint, lastPoint; 6642 6643 if (!closed) { 6644 segments[0].flags |= NVGSegmentFlags.Cap; 6645 segments[nsegments-1].flags |= NVGSegmentFlags.Endcap; 6646 } 6647 6648 for (int s = 0; s < nsegments; ++s) { 6649 seg = &segments[s]; 6650 nvg__initBounds(seg.bounds); 6651 6652 firstPoint = seg.firstPoint*2; 6653 lastPoint = firstPoint+(seg.type == Command.LineTo ? 2 : 6); 6654 6655 ns = 0; 6656 6657 // First two supporting points are either side of the start point 6658 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[1]*strokeWidth; 6659 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.startDir.ptr[0]*strokeWidth; 6660 6661 supportingPoints.ptr[ns++] = points[firstPoint]+seg.startDir.ptr[1]*strokeWidth; 6662 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[0]*strokeWidth; 6663 6664 // Second two supporting points are either side of the end point 6665 supportingPoints.ptr[ns++] = points[lastPoint]-seg.endDir.ptr[1]*strokeWidth; 6666 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[0]*strokeWidth; 6667 6668 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[1]*strokeWidth; 6669 supportingPoints.ptr[ns++] = points[lastPoint+1]-seg.endDir.ptr[0]*strokeWidth; 6670 6671 if ((seg.flags&NVGSegmentFlags.Corner) && prevseg !is null) { 6672 seg.miterDir.ptr[0] = 0.5f*(-prevseg.endDir.ptr[1]-seg.startDir.ptr[1]); 6673 seg.miterDir.ptr[1] = 0.5f*(prevseg.endDir.ptr[0]+seg.startDir.ptr[0]); 6674 6675 immutable float M2 = seg.miterDir.ptr[0]*seg.miterDir.ptr[0]+seg.miterDir.ptr[1]*seg.miterDir.ptr[1]; 6676 6677 if (M2 > 0.000001f) { 6678 float scale = 1.0f/M2; 6679 if (scale > 600.0f) scale = 600.0f; 6680 seg.miterDir.ptr[0] *= scale; 6681 seg.miterDir.ptr[1] *= scale; 6682 } 6683 6684 //NVG_PICK_DEBUG_VECTOR_SCALE(&points[firstPoint], seg.miterDir, 10); 6685 6686 // Add an additional support at the corner on the other line 6687 supportingPoints.ptr[ns++] = points[firstPoint]-prevseg.endDir.ptr[1]*strokeWidth; 6688 supportingPoints.ptr[ns++] = points[firstPoint+1]+prevseg.endDir.ptr[0]*strokeWidth; 6689 6690 if (lineJoin == NVGLineCap.Miter || lineJoin == NVGLineCap.Bevel) { 6691 // Set a corner as beveled if the join type is bevel or mitered and 6692 // miterLimit is hit. 6693 if (lineJoin == NVGLineCap.Bevel || (M2*miterLimit*miterLimit) < 1.0f) { 6694 seg.flags |= NVGSegmentFlags.Bevel; 6695 } else { 6696 // Corner is mitered - add miter point as a support 6697 supportingPoints.ptr[ns++] = points[firstPoint]+seg.miterDir.ptr[0]*strokeWidth; 6698 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.miterDir.ptr[1]*strokeWidth; 6699 } 6700 } else if (lineJoin == NVGLineCap.Round) { 6701 // ... and at the midpoint of the corner arc 6702 float[2] vertexN = [ -seg.startDir.ptr[0]+prevseg.endDir.ptr[0], -seg.startDir.ptr[1]+prevseg.endDir.ptr[1] ]; 6703 nvg__normalize(&vertexN[0], &vertexN[1]); 6704 6705 supportingPoints.ptr[ns++] = points[firstPoint]+vertexN[0]*strokeWidth; 6706 supportingPoints.ptr[ns++] = points[firstPoint+1]+vertexN[1]*strokeWidth; 6707 } 6708 } 6709 6710 if (seg.flags&NVGSegmentFlags.Cap) { 6711 switch (lineCap) { 6712 case NVGLineCap.Butt: 6713 // supports for butt already added 6714 break; 6715 case NVGLineCap.Square: 6716 // square cap supports are just the original two supports moved out along the direction 6717 supportingPoints.ptr[ns++] = supportingPoints.ptr[0]-seg.startDir.ptr[0]*strokeWidth; 6718 supportingPoints.ptr[ns++] = supportingPoints.ptr[1]-seg.startDir.ptr[1]*strokeWidth; 6719 supportingPoints.ptr[ns++] = supportingPoints.ptr[2]-seg.startDir.ptr[0]*strokeWidth; 6720 supportingPoints.ptr[ns++] = supportingPoints.ptr[3]-seg.startDir.ptr[1]*strokeWidth; 6721 break; 6722 case NVGLineCap.Round: 6723 // add one additional support for the round cap along the dir 6724 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[0]*strokeWidth; 6725 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[1]*strokeWidth; 6726 break; 6727 default: 6728 break; 6729 } 6730 } 6731 6732 if (seg.flags&NVGSegmentFlags.Endcap) { 6733 // end supporting points, either side of line 6734 int end = 4; 6735 switch(lineCap) { 6736 case NVGLineCap.Butt: 6737 // supports for butt already added 6738 break; 6739 case NVGLineCap.Square: 6740 // square cap supports are just the original two supports moved out along the direction 6741 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+0]+seg.endDir.ptr[0]*strokeWidth; 6742 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+1]+seg.endDir.ptr[1]*strokeWidth; 6743 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+2]+seg.endDir.ptr[0]*strokeWidth; 6744 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+3]+seg.endDir.ptr[1]*strokeWidth; 6745 break; 6746 case NVGLineCap.Round: 6747 // add one additional support for the round cap along the dir 6748 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[0]*strokeWidth; 6749 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[1]*strokeWidth; 6750 break; 6751 default: 6752 break; 6753 } 6754 } 6755 6756 nvg__expandBounds(seg.bounds, supportingPoints.ptr, ns/2); 6757 6758 prevseg = seg; 6759 } 6760 } 6761 6762 NVGpickPath* nvg__pickPathCreate (NVGContext context, const(float)[] acommands, int id, bool forStroke) { 6763 NVGpickScene* ps = nvg__pickSceneGet(context); 6764 if (ps is null) return null; 6765 6766 int i = 0; 6767 6768 int ncommands = cast(int)acommands.length; 6769 const(float)* commands = acommands.ptr; 6770 6771 NVGpickPath* pp = null; 6772 NVGpickSubPath* psp = null; 6773 float[2] start = void; 6774 int firstPoint; 6775 6776 //bool hasHoles = false; 6777 NVGpickSubPath* prev = null; 6778 6779 float[8] points = void; 6780 float[2] inflections = void; 6781 int ninflections = 0; 6782 6783 NVGstate* state = nvg__getState(context); 6784 float[4] totalBounds = void; 6785 NVGsegment* segments = null; 6786 const(NVGsegment)* seg = null; 6787 NVGpickSubPath *curpsp; 6788 6789 pp = nvg__allocPickPath(ps); 6790 if (pp is null) return null; 6791 6792 pp.id = id; 6793 6794 bool hasPoints = false; 6795 6796 void closeIt () { 6797 if (psp is null || !hasPoints) return; 6798 if (ps.points[(ps.npoints-1)*2] != start.ptr[0] || ps.points[(ps.npoints-1)*2+1] != start.ptr[1]) { 6799 firstPoint = nvg__pickSceneAddPoints(ps, start.ptr, 1); 6800 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, Command.LineTo, NVGSegmentFlags.Corner); 6801 } 6802 psp.closed = true; 6803 } 6804 6805 while (i < ncommands) { 6806 int cmd = cast(int)commands[i++]; 6807 switch (cmd) { 6808 case Command.MoveTo: // one coordinate pair 6809 const(float)* tfxy = commands+i; 6810 i += 2; 6811 6812 // new starting point 6813 start.ptr[0..2] = tfxy[0..2]; 6814 6815 // start a new path for each sub path to handle sub paths that intersect other sub paths 6816 prev = psp; 6817 psp = nvg__allocPickSubPath(ps); 6818 if (psp is null) { psp = prev; break; } 6819 psp.firstSegment = -1; 6820 psp.winding = NVGSolidity.Solid; 6821 psp.next = prev; 6822 6823 nvg__pickSceneAddPoints(ps, tfxy, 1); 6824 hasPoints = true; 6825 break; 6826 case Command.LineTo: // one coordinate pair 6827 const(float)* tfxy = commands+i; 6828 i += 2; 6829 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 1); 6830 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner); 6831 hasPoints = true; 6832 break; 6833 case Command.BezierTo: // three coordinate pairs 6834 const(float)* tfxy = commands+i; 6835 i += 3*2; 6836 6837 // Split the curve at it's dx==0 or dy==0 inflection points. 6838 // Thus: 6839 // A horizontal line only ever interects the curves once. 6840 // and 6841 // Finding the closest point on any curve converges more reliably. 6842 6843 // NOTE: We could just split on dy==0 here. 6844 6845 memcpy(&points.ptr[0], &ps.points[(ps.npoints-1)*2], float.sizeof*2); 6846 memcpy(&points.ptr[2], tfxy, float.sizeof*2*3); 6847 6848 ninflections = 0; 6849 nvg__bezierInflections(points.ptr, 1, &ninflections, inflections.ptr); 6850 nvg__bezierInflections(points.ptr, 0, &ninflections, inflections.ptr); 6851 6852 if (ninflections) { 6853 float previnfl = 0; 6854 float[8] pointsA = void, pointsB = void; 6855 6856 nvg__smallsort(inflections.ptr, ninflections); 6857 6858 for (int infl = 0; infl < ninflections; ++infl) { 6859 if (nvg__absf(inflections.ptr[infl]-previnfl) < NVGPickEPS) continue; 6860 6861 immutable float t = (inflections.ptr[infl]-previnfl)*(1.0f/(1.0f-previnfl)); 6862 6863 previnfl = inflections.ptr[infl]; 6864 6865 nvg__splitBezier(points.ptr, t, pointsA.ptr, pointsB.ptr); 6866 6867 firstPoint = nvg__pickSceneAddPoints(ps, &pointsA.ptr[2], 3); 6868 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, (infl == 0) ? NVGSegmentFlags.Corner : 0); 6869 6870 memcpy(points.ptr, pointsB.ptr, float.sizeof*8); 6871 } 6872 6873 firstPoint = nvg__pickSceneAddPoints(ps, &pointsB.ptr[2], 3); 6874 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, 0); 6875 } else { 6876 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 3); 6877 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner); 6878 } 6879 hasPoints = true; 6880 break; 6881 case Command.Close: 6882 closeIt(); 6883 break; 6884 case Command.Winding: 6885 psp.winding = cast(short)cast(int)commands[i]; 6886 //if (psp.winding == NVGSolidity.Hole) hasHoles = true; 6887 i += 1; 6888 break; 6889 default: 6890 break; 6891 } 6892 } 6893 6894 // force-close filled paths 6895 if (psp !is null && !forStroke && hasPoints && !psp.closed) closeIt(); 6896 6897 pp.flags = (forStroke ? NVGPathFlags.Stroke : NVGPathFlags.Fill); 6898 pp.subPaths = psp; 6899 pp.strokeWidth = state.strokeWidth*0.5f; 6900 pp.miterLimit = state.miterLimit; 6901 pp.lineCap = cast(short)state.lineCap; 6902 pp.lineJoin = cast(short)state.lineJoin; 6903 pp.evenOddMode = nvg__getState(context).evenOddMode; 6904 6905 nvg__initBounds(totalBounds); 6906 6907 for (curpsp = psp; curpsp; curpsp = curpsp.next) { 6908 if (forStroke) { 6909 nvg__pickSubPathAddStrokeSupports(ps, curpsp, pp.strokeWidth, pp.lineCap, pp.lineJoin, pp.miterLimit); 6910 } else { 6911 nvg__pickSubPathAddFillSupports(ps, curpsp); 6912 } 6913 6914 if (curpsp.firstSegment == -1) continue; 6915 segments = &ps.segments[curpsp.firstSegment]; 6916 nvg__initBounds(curpsp.bounds); 6917 for (int s = 0; s < curpsp.nsegments; ++s) { 6918 seg = &segments[s]; 6919 //NVG_PICK_DEBUG_BOUNDS(seg.bounds); 6920 nvg__unionBounds(curpsp.bounds, seg.bounds); 6921 } 6922 6923 nvg__unionBounds(totalBounds, curpsp.bounds); 6924 } 6925 6926 // Store the scissor rect if present. 6927 if (state.scissor.extent.ptr[0] != -1.0f) { 6928 // Use points storage to store the scissor data 6929 pp.scissor = nvg__pickSceneAddPoints(ps, null, 4); 6930 float* scissor = &ps.points[pp.scissor*2]; 6931 6932 //memcpy(scissor, state.scissor.xform.ptr, 6*float.sizeof); 6933 scissor[0..6] = state.scissor.xform.mat[]; 6934 memcpy(scissor+6, state.scissor.extent.ptr, 2*float.sizeof); 6935 6936 pp.flags |= NVGPathFlags.Scissor; 6937 } 6938 6939 memcpy(pp.bounds.ptr, totalBounds.ptr, float.sizeof*4); 6940 6941 return pp; 6942 } 6943 6944 6945 // Struct management 6946 NVGpickPath* nvg__allocPickPath (NVGpickScene* ps) { 6947 NVGpickPath* pp = ps.freePaths; 6948 if (pp !is null) { 6949 ps.freePaths = pp.next; 6950 } else { 6951 pp = cast(NVGpickPath*)malloc(NVGpickPath.sizeof); 6952 } 6953 memset(pp, 0, NVGpickPath.sizeof); 6954 return pp; 6955 } 6956 6957 // Put a pick path and any sub paths (back) to the free lists. 6958 void nvg__freePickPath (NVGpickScene* ps, NVGpickPath* pp) { 6959 // Add all sub paths to the sub path free list. 6960 // Finds the end of the path sub paths, links that to the current 6961 // sub path free list head and replaces the head ptr with the 6962 // head path sub path entry. 6963 NVGpickSubPath* psp = null; 6964 for (psp = pp.subPaths; psp !is null && psp.next !is null; psp = psp.next) {} 6965 6966 if (psp) { 6967 psp.next = ps.freeSubPaths; 6968 ps.freeSubPaths = pp.subPaths; 6969 } 6970 pp.subPaths = null; 6971 6972 // Add the path to the path freelist 6973 pp.next = ps.freePaths; 6974 ps.freePaths = pp; 6975 if (pp.next is null) ps.lastPath = pp; 6976 } 6977 6978 NVGpickSubPath* nvg__allocPickSubPath (NVGpickScene* ps) { 6979 NVGpickSubPath* psp = ps.freeSubPaths; 6980 if (psp !is null) { 6981 ps.freeSubPaths = psp.next; 6982 } else { 6983 psp = cast(NVGpickSubPath*)malloc(NVGpickSubPath.sizeof); 6984 if (psp is null) return null; 6985 } 6986 memset(psp, 0, NVGpickSubPath.sizeof); 6987 return psp; 6988 } 6989 6990 void nvg__returnPickSubPath (NVGpickScene* ps, NVGpickSubPath* psp) { 6991 psp.next = ps.freeSubPaths; 6992 ps.freeSubPaths = psp; 6993 } 6994 6995 NVGpickScene* nvg__allocPickScene () { 6996 NVGpickScene* ps = cast(NVGpickScene*)malloc(NVGpickScene.sizeof); 6997 if (ps is null) return null; 6998 memset(ps, 0, NVGpickScene.sizeof); 6999 ps.nlevels = 5; 7000 return ps; 7001 } 7002 7003 void nvg__deletePickScene (NVGpickScene* ps) { 7004 NVGpickPath* pp; 7005 NVGpickSubPath* psp; 7006 7007 // Add all paths (and thus sub paths) to the free list(s). 7008 while (ps.paths !is null) { 7009 pp = ps.paths.next; 7010 nvg__freePickPath(ps, ps.paths); 7011 ps.paths = pp; 7012 } 7013 7014 // Delete all paths 7015 while (ps.freePaths !is null) { 7016 pp = ps.freePaths; 7017 ps.freePaths = pp.next; 7018 while (pp.subPaths !is null) { 7019 psp = pp.subPaths; 7020 pp.subPaths = psp.next; 7021 free(psp); 7022 } 7023 free(pp); 7024 } 7025 7026 // Delete all sub paths 7027 while (ps.freeSubPaths !is null) { 7028 psp = ps.freeSubPaths.next; 7029 free(ps.freeSubPaths); 7030 ps.freeSubPaths = psp; 7031 } 7032 7033 ps.npoints = 0; 7034 ps.nsegments = 0; 7035 7036 if (ps.levels !is null) { 7037 free(ps.levels[0]); 7038 free(ps.levels); 7039 } 7040 7041 if (ps.picked !is null) free(ps.picked); 7042 if (ps.points !is null) free(ps.points); 7043 if (ps.segments !is null) free(ps.segments); 7044 7045 free(ps); 7046 } 7047 7048 NVGpickScene* nvg__pickSceneGet (NVGContext ctx) { 7049 if (ctx.pickScene is null) ctx.pickScene = nvg__allocPickScene(); 7050 return ctx.pickScene; 7051 } 7052 7053 7054 // Applies Casteljau's algorithm to a cubic bezier for a given parameter t 7055 // points is 4 points (8 floats) 7056 // lvl1 is 3 points (6 floats) 7057 // lvl2 is 2 points (4 floats) 7058 // lvl3 is 1 point (2 floats) 7059 void nvg__casteljau (const(float)* points, float t, float* lvl1, float* lvl2, float* lvl3) { 7060 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0; 7061 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1; 7062 7063 // Level 1 7064 lvl1[x0] = (points[x1]-points[x0])*t+points[x0]; 7065 lvl1[y0] = (points[y1]-points[y0])*t+points[y0]; 7066 7067 lvl1[x1] = (points[x2]-points[x1])*t+points[x1]; 7068 lvl1[y1] = (points[y2]-points[y1])*t+points[y1]; 7069 7070 lvl1[x2] = (points[x3]-points[x2])*t+points[x2]; 7071 lvl1[y2] = (points[y3]-points[y2])*t+points[y2]; 7072 7073 // Level 2 7074 lvl2[x0] = (lvl1[x1]-lvl1[x0])*t+lvl1[x0]; 7075 lvl2[y0] = (lvl1[y1]-lvl1[y0])*t+lvl1[y0]; 7076 7077 lvl2[x1] = (lvl1[x2]-lvl1[x1])*t+lvl1[x1]; 7078 lvl2[y1] = (lvl1[y2]-lvl1[y1])*t+lvl1[y1]; 7079 7080 // Level 3 7081 lvl3[x0] = (lvl2[x1]-lvl2[x0])*t+lvl2[x0]; 7082 lvl3[y0] = (lvl2[y1]-lvl2[y0])*t+lvl2[y0]; 7083 } 7084 7085 // Calculates a point on a bezier at point t. 7086 void nvg__bezierEval (const(float)* points, float t, ref float[2] tpoint) { 7087 immutable float omt = 1-t; 7088 immutable float omt3 = omt*omt*omt; 7089 immutable float omt2 = omt*omt; 7090 immutable float t3 = t*t*t; 7091 immutable float t2 = t*t; 7092 7093 tpoint.ptr[0] = 7094 points[0]*omt3+ 7095 points[2]*3.0f*omt2*t+ 7096 points[4]*3.0f*omt*t2+ 7097 points[6]*t3; 7098 7099 tpoint.ptr[1] = 7100 points[1]*omt3+ 7101 points[3]*3.0f*omt2*t+ 7102 points[5]*3.0f*omt*t2+ 7103 points[7]*t3; 7104 } 7105 7106 // Splits a cubic bezier curve into two parts at point t. 7107 void nvg__splitBezier (const(float)* points, float t, float* pointsA, float* pointsB) { 7108 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0; 7109 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1; 7110 7111 float[6] lvl1 = void; 7112 float[4] lvl2 = void; 7113 float[2] lvl3 = void; 7114 7115 nvg__casteljau(points, t, lvl1.ptr, lvl2.ptr, lvl3.ptr); 7116 7117 // First half 7118 pointsA[x0] = points[x0]; 7119 pointsA[y0] = points[y0]; 7120 7121 pointsA[x1] = lvl1.ptr[x0]; 7122 pointsA[y1] = lvl1.ptr[y0]; 7123 7124 pointsA[x2] = lvl2.ptr[x0]; 7125 pointsA[y2] = lvl2.ptr[y0]; 7126 7127 pointsA[x3] = lvl3.ptr[x0]; 7128 pointsA[y3] = lvl3.ptr[y0]; 7129 7130 // Second half 7131 pointsB[x0] = lvl3.ptr[x0]; 7132 pointsB[y0] = lvl3.ptr[y0]; 7133 7134 pointsB[x1] = lvl2.ptr[x1]; 7135 pointsB[y1] = lvl2.ptr[y1]; 7136 7137 pointsB[x2] = lvl1.ptr[x2]; 7138 pointsB[y2] = lvl1.ptr[y2]; 7139 7140 pointsB[x3] = points[x3]; 7141 pointsB[y3] = points[y3]; 7142 } 7143 7144 // Calculates the inflection points in coordinate coord (X = 0, Y = 1) of a cubic bezier. 7145 // Appends any found inflection points to the array inflections and increments *ninflections. 7146 // So finds the parameters where dx/dt or dy/dt is 0 7147 void nvg__bezierInflections (const(float)* points, int coord, int* ninflections, float* inflections) { 7148 immutable float v0 = points[0*2+coord], v1 = points[1*2+coord], v2 = points[2*2+coord], v3 = points[3*2+coord]; 7149 float[2] t = void; 7150 int nvalid = *ninflections; 7151 7152 immutable float a = 3.0f*( -v0+3.0f*v1-3.0f*v2+v3 ); 7153 immutable float b = 6.0f*( v0-2.0f*v1+v2 ); 7154 immutable float c = 3.0f*( v1-v0 ); 7155 7156 float d = b*b-4.0f*a*c; 7157 if (nvg__absf(d-0.0f) < NVGPickEPS) { 7158 // Zero or one root 7159 t.ptr[0] = -b/2.0f*a; 7160 if (t.ptr[0] > NVGPickEPS && t.ptr[0] < (1.0f-NVGPickEPS)) { 7161 inflections[nvalid] = t.ptr[0]; 7162 ++nvalid; 7163 } 7164 } else if (d > NVGPickEPS) { 7165 // zero, one or two roots 7166 d = nvg__sqrtf(d); 7167 7168 t.ptr[0] = (-b+d)/(2.0f*a); 7169 t.ptr[1] = (-b-d)/(2.0f*a); 7170 7171 for (int i = 0; i < 2; ++i) { 7172 if (t.ptr[i] > NVGPickEPS && t.ptr[i] < (1.0f-NVGPickEPS)) { 7173 inflections[nvalid] = t.ptr[i]; 7174 ++nvalid; 7175 } 7176 } 7177 } else { 7178 // zero roots 7179 } 7180 7181 *ninflections = nvalid; 7182 } 7183 7184 // Sort a small number of floats in ascending order (0 < n < 6) 7185 void nvg__smallsort (float* values, int n) { 7186 bool bSwapped = true; 7187 for (int j = 0; j < n-1 && bSwapped; ++j) { 7188 bSwapped = false; 7189 for (int i = 0; i < n-1; ++i) { 7190 if (values[i] > values[i+1]) { 7191 auto tmp = values[i]; 7192 values[i] = values[i+1]; 7193 values[i+1] = tmp; 7194 } 7195 } 7196 } 7197 } 7198 7199 // Calculates the bounding rect of a given cubic bezier curve. 7200 void nvg__bezierBounds (const(float)* points, ref float[4] bounds) { 7201 float[4] inflections = void; 7202 int ninflections = 0; 7203 float[2] tpoint = void; 7204 7205 nvg__initBounds(bounds); 7206 7207 // Include start and end points in bounds 7208 nvg__expandBounds(bounds, &points[0], 1); 7209 nvg__expandBounds(bounds, &points[6], 1); 7210 7211 // Calculate dx==0 and dy==0 inflection points and add them to the bounds 7212 7213 nvg__bezierInflections(points, 0, &ninflections, inflections.ptr); 7214 nvg__bezierInflections(points, 1, &ninflections, inflections.ptr); 7215 7216 foreach (immutable int i; 0..ninflections) { 7217 nvg__bezierEval(points, inflections[i], tpoint); 7218 nvg__expandBounds(bounds, tpoint.ptr, 1); 7219 } 7220 } 7221 7222 // Checks to see if a line originating from x,y along the +ve x axis 7223 // intersects the given line (points[0],points[1]) -> (points[2], points[3]). 7224 // Returns `true` on intersection. 7225 // Horizontal lines are never hit. 7226 bool nvg__intersectLine (const(float)* points, float x, float y) { 7227 immutable float x1 = points[0]; 7228 immutable float y1 = points[1]; 7229 immutable float x2 = points[2]; 7230 immutable float y2 = points[3]; 7231 immutable float d = y2-y1; 7232 if (d > NVGPickEPS || d < -NVGPickEPS) { 7233 immutable float s = (x2-x1)/d; 7234 immutable float lineX = x1+(y-y1)*s; 7235 return (lineX > x); 7236 } else { 7237 return false; 7238 } 7239 } 7240 7241 // Checks to see if a line originating from x,y along the +ve x axis intersects the given bezier. 7242 // It is assumed that the line originates from within the bounding box of 7243 // the bezier and that the curve has no dy=0 inflection points. 7244 // Returns the number of intersections found (which is either 1 or 0). 7245 int nvg__intersectBezier (const(float)* points, float x, float y) { 7246 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0]; 7247 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1]; 7248 7249 if (y0 == y1 && y1 == y2 && y2 == y3) return 0; 7250 7251 // Initial t guess 7252 float t = void; 7253 if (y3 != y0) t = (y-y0)/(y3-y0); 7254 else if (x3 != x0) t = (x-x0)/(x3-x0); 7255 else t = 0.5f; 7256 7257 // A few Newton iterations 7258 for (int iter = 0; iter < 6; ++iter) { 7259 immutable float omt = 1-t; 7260 immutable float omt2 = omt*omt; 7261 immutable float t2 = t*t; 7262 immutable float omt3 = omt2*omt; 7263 immutable float t3 = t2*t; 7264 7265 immutable float ty = y0*omt3 + 7266 y1*3.0f*omt2*t + 7267 y2*3.0f*omt*t2 + 7268 y3*t3; 7269 7270 // Newton iteration 7271 immutable float dty = 3.0f*omt2*(y1-y0) + 7272 6.0f*omt*t*(y2-y1) + 7273 3.0f*t2*(y3-y2); 7274 7275 // dty will never == 0 since: 7276 // Either omt, omt2 are zero OR t2 is zero 7277 // y0 != y1 != y2 != y3 (checked above) 7278 t = t-(ty-y)/dty; 7279 } 7280 7281 { 7282 immutable float omt = 1-t; 7283 immutable float omt2 = omt*omt; 7284 immutable float t2 = t*t; 7285 immutable float omt3 = omt2*omt; 7286 immutable float t3 = t2*t; 7287 7288 immutable float tx = 7289 x0*omt3+ 7290 x1*3.0f*omt2*t+ 7291 x2*3.0f*omt*t2+ 7292 x3*t3; 7293 7294 return (tx > x ? 1 : 0); 7295 } 7296 } 7297 7298 // Finds the closest point on a line to a given point 7299 void nvg__closestLine (const(float)* points, float x, float y, float* closest, float* ot) { 7300 immutable float x1 = points[0]; 7301 immutable float y1 = points[1]; 7302 immutable float x2 = points[2]; 7303 immutable float y2 = points[3]; 7304 immutable float pqx = x2-x1; 7305 immutable float pqz = y2-y1; 7306 immutable float dx = x-x1; 7307 immutable float dz = y-y1; 7308 immutable float d = pqx*pqx+pqz*pqz; 7309 float t = pqx*dx+pqz*dz; 7310 if (d > 0) t /= d; 7311 if (t < 0) t = 0; else if (t > 1) t = 1; 7312 closest[0] = x1+t*pqx; 7313 closest[1] = y1+t*pqz; 7314 *ot = t; 7315 } 7316 7317 // Finds the closest point on a curve for a given point (x,y). 7318 // Assumes that the curve has no dx==0 or dy==0 inflection points. 7319 void nvg__closestBezier (const(float)* points, float x, float y, float* closest, float *ot) { 7320 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0]; 7321 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1]; 7322 7323 // This assumes that the curve has no dy=0 inflection points. 7324 7325 // Initial t guess 7326 float t = 0.5f; 7327 7328 // A few Newton iterations 7329 for (int iter = 0; iter < 6; ++iter) { 7330 immutable float omt = 1-t; 7331 immutable float omt2 = omt*omt; 7332 immutable float t2 = t*t; 7333 immutable float omt3 = omt2*omt; 7334 immutable float t3 = t2*t; 7335 7336 immutable float ty = 7337 y0*omt3+ 7338 y1*3.0f*omt2*t+ 7339 y2*3.0f*omt*t2+ 7340 y3*t3; 7341 7342 immutable float tx = 7343 x0*omt3+ 7344 x1*3.0f*omt2*t+ 7345 x2*3.0f*omt*t2+ 7346 x3*t3; 7347 7348 // Newton iteration 7349 immutable float dty = 7350 3.0f*omt2*(y1-y0)+ 7351 6.0f*omt*t*(y2-y1)+ 7352 3.0f*t2*(y3-y2); 7353 7354 immutable float ddty = 7355 6.0f*omt*(y2-2.0f*y1+y0)+ 7356 6.0f*t*(y3-2.0f*y2+y1); 7357 7358 immutable float dtx = 7359 3.0f*omt2*(x1-x0)+ 7360 6.0f*omt*t*(x2-x1)+ 7361 3.0f*t2*(x3-x2); 7362 7363 immutable float ddtx = 7364 6.0f*omt*(x2-2.0f*x1+x0)+ 7365 6.0f*t*(x3-2.0f*x2+x1); 7366 7367 immutable float errorx = tx-x; 7368 immutable float errory = ty-y; 7369 7370 immutable float n = errorx*dtx+errory*dty; 7371 if (n == 0) break; 7372 7373 immutable float d = dtx*dtx+dty*dty+errorx*ddtx+errory*ddty; 7374 if (d != 0) t = t-n/d; else break; 7375 } 7376 7377 t = nvg__max(0, nvg__min(1.0, t)); 7378 *ot = t; 7379 { 7380 immutable float omt = 1-t; 7381 immutable float omt2 = omt*omt; 7382 immutable float t2 = t*t; 7383 immutable float omt3 = omt2*omt; 7384 immutable float t3 = t2*t; 7385 7386 immutable float ty = 7387 y0*omt3+ 7388 y1*3.0f*omt2*t+ 7389 y2*3.0f*omt*t2+ 7390 y3*t3; 7391 7392 immutable float tx = 7393 x0*omt3+ 7394 x1*3.0f*omt2*t+ 7395 x2*3.0f*omt*t2+ 7396 x3*t3; 7397 7398 closest[0] = tx; 7399 closest[1] = ty; 7400 } 7401 } 7402 7403 // Returns: 7404 // 1 If (x,y) is contained by the stroke of the path 7405 // 0 If (x,y) is not contained by the path. 7406 int nvg__pickSubPathStroke (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, float strokeWidth, int lineCap, int lineJoin) { 7407 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0; 7408 if (psp.firstSegment == -1) return 0; 7409 7410 float[2] closest = void; 7411 float[2] d = void; 7412 float t = void; 7413 7414 // trace a line from x,y out along the positive x axis and count the number of intersections 7415 int nsegments = psp.nsegments; 7416 const(NVGsegment)* seg = ps.segments+psp.firstSegment; 7417 const(NVGsegment)* prevseg = (psp.closed ? &ps.segments[psp.firstSegment+nsegments-1] : null); 7418 immutable float strokeWidthSqd = strokeWidth*strokeWidth; 7419 7420 for (int s = 0; s < nsegments; ++s, prevseg = seg, ++seg) { 7421 if (nvg__pointInBounds(x, y, seg.bounds)) { 7422 // Line potentially hits stroke. 7423 switch (seg.type) { 7424 case Command.LineTo: 7425 nvg__closestLine(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t); 7426 break; 7427 case Command.BezierTo: 7428 nvg__closestBezier(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t); 7429 break; 7430 default: 7431 continue; 7432 } 7433 7434 d.ptr[0] = x-closest.ptr[0]; 7435 d.ptr[1] = y-closest.ptr[1]; 7436 7437 if ((t >= NVGPickEPS && t <= 1.0f-NVGPickEPS) || 7438 (seg.flags&(NVGSegmentFlags.Corner|NVGSegmentFlags.Cap|NVGSegmentFlags.Endcap)) == 0 || 7439 (lineJoin == NVGLineCap.Round)) 7440 { 7441 // Closest point is in the middle of the line/curve, at a rounded join/cap 7442 // or at a smooth join 7443 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7444 if (distSqd < strokeWidthSqd) return 1; 7445 } else if ((t > 1.0f-NVGPickEPS && (seg.flags&NVGSegmentFlags.Endcap)) || 7446 (t < NVGPickEPS && (seg.flags&NVGSegmentFlags.Cap))) { 7447 switch (lineCap) { 7448 case NVGLineCap.Butt: 7449 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7450 immutable float dirD = (t < NVGPickEPS ? 7451 -(d.ptr[0]*seg.startDir.ptr[0]+d.ptr[1]*seg.startDir.ptr[1]) : 7452 d.ptr[0]*seg.endDir.ptr[0]+d.ptr[1]*seg.endDir.ptr[1]); 7453 if (dirD < -NVGPickEPS && distSqd < strokeWidthSqd) return 1; 7454 break; 7455 case NVGLineCap.Square: 7456 if (nvg__absf(d.ptr[0]) < strokeWidth && nvg__absf(d.ptr[1]) < strokeWidth) return 1; 7457 break; 7458 case NVGLineCap.Round: 7459 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7460 if (distSqd < strokeWidthSqd) return 1; 7461 break; 7462 default: 7463 break; 7464 } 7465 } else if (seg.flags&NVGSegmentFlags.Corner) { 7466 // Closest point is at a corner 7467 const(NVGsegment)* seg0, seg1; 7468 7469 if (t < NVGPickEPS) { 7470 seg0 = prevseg; 7471 seg1 = seg; 7472 } else { 7473 seg0 = seg; 7474 seg1 = (s == nsegments-1 ? &ps.segments[psp.firstSegment] : seg+1); 7475 } 7476 7477 if (!(seg1.flags&NVGSegmentFlags.Bevel)) { 7478 immutable float prevNDist = -seg0.endDir.ptr[1]*d.ptr[0]+seg0.endDir.ptr[0]*d.ptr[1]; 7479 immutable float curNDist = seg1.startDir.ptr[1]*d.ptr[0]-seg1.startDir.ptr[0]*d.ptr[1]; 7480 if (nvg__absf(prevNDist) < strokeWidth && nvg__absf(curNDist) < strokeWidth) return 1; 7481 } else { 7482 d.ptr[0] -= -seg1.startDir.ptr[1]*strokeWidth; 7483 d.ptr[1] -= +seg1.startDir.ptr[0]*strokeWidth; 7484 if (seg1.miterDir.ptr[0]*d.ptr[0]+seg1.miterDir.ptr[1]*d.ptr[1] < 0) return 1; 7485 } 7486 } 7487 } 7488 } 7489 7490 return 0; 7491 } 7492 7493 // Returns: 7494 // 1 If (x,y) is contained by the path and the path is solid. 7495 // -1 If (x,y) is contained by the path and the path is a hole. 7496 // 0 If (x,y) is not contained by the path. 7497 int nvg__pickSubPath (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, bool evenOddMode) { 7498 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0; 7499 if (psp.firstSegment == -1) return 0; 7500 7501 const(NVGsegment)* seg = &ps.segments[psp.firstSegment]; 7502 int nsegments = psp.nsegments; 7503 int nintersections = 0; 7504 7505 // trace a line from x,y out along the positive x axis and count the number of intersections 7506 for (int s = 0; s < nsegments; ++s, ++seg) { 7507 if ((seg.bounds.ptr[1]-NVGPickEPS) < y && 7508 (seg.bounds.ptr[3]-NVGPickEPS) > y && 7509 seg.bounds.ptr[2] > x) 7510 { 7511 // Line hits the box. 7512 switch (seg.type) { 7513 case Command.LineTo: 7514 if (seg.bounds.ptr[0] > x) { 7515 // line originates outside the box 7516 ++nintersections; 7517 } else { 7518 // line originates inside the box 7519 nintersections += nvg__intersectLine(&ps.points[seg.firstPoint*2], x, y); 7520 } 7521 break; 7522 case Command.BezierTo: 7523 if (seg.bounds.ptr[0] > x) { 7524 // line originates outside the box 7525 ++nintersections; 7526 } else { 7527 // line originates inside the box 7528 nintersections += nvg__intersectBezier(&ps.points[seg.firstPoint*2], x, y); 7529 } 7530 break; 7531 default: 7532 break; 7533 } 7534 } 7535 } 7536 7537 if (evenOddMode) { 7538 return nintersections; 7539 } else { 7540 return (nintersections&1 ? (psp.winding == NVGSolidity.Solid ? 1 : -1) : 0); 7541 } 7542 } 7543 7544 bool nvg__pickPath (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) { 7545 int pickCount = 0; 7546 const(NVGpickSubPath)* psp = pp.subPaths; 7547 while (psp !is null) { 7548 pickCount += nvg__pickSubPath(ps, psp, x, y, pp.evenOddMode); 7549 psp = psp.next; 7550 } 7551 return ((pp.evenOddMode ? pickCount&1 : pickCount) != 0); 7552 } 7553 7554 bool nvg__pickPathStroke (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) { 7555 const(NVGpickSubPath)* psp = pp.subPaths; 7556 while (psp !is null) { 7557 if (nvg__pickSubPathStroke(ps, psp, x, y, pp.strokeWidth, pp.lineCap, pp.lineJoin)) return true; 7558 psp = psp.next; 7559 } 7560 return false; 7561 } 7562 7563 bool nvg__pickPathTestBounds (NVGContext ctx, const NVGpickScene* ps, const NVGpickPath* pp, float x, float y) { 7564 if (nvg__pointInBounds(x, y, pp.bounds)) { 7565 //{ import core.stdc.stdio; printf(" (0): in bounds!\n"); } 7566 if (pp.flags&NVGPathFlags.Scissor) { 7567 const(float)* scissor = &ps.points[pp.scissor*2]; 7568 // untransform scissor translation 7569 float stx = void, sty = void; 7570 ctx.gpuUntransformPoint(&stx, &sty, scissor[4], scissor[5]); 7571 immutable float rx = x-stx; 7572 immutable float ry = y-sty; 7573 //{ 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]); } 7574 if (nvg__absf((scissor[0]*rx)+(scissor[1]*ry)) > scissor[6] || 7575 nvg__absf((scissor[2]*rx)+(scissor[3]*ry)) > scissor[7]) 7576 { 7577 //{ import core.stdc.stdio; printf(" (1): scissor reject!\n"); } 7578 return false; 7579 } 7580 } 7581 return true; 7582 } 7583 return false; 7584 } 7585 7586 int nvg__countBitsUsed (uint v) pure { 7587 pragma(inline, true); 7588 import core.bitop : bsr; 7589 return (v != 0 ? bsr(v)+1 : 0); 7590 } 7591 7592 void nvg__pickSceneInsert (NVGpickScene* ps, NVGpickPath* pp) { 7593 if (ps is null || pp is null) return; 7594 7595 int[4] cellbounds; 7596 int base = ps.nlevels-1; 7597 int level; 7598 int levelwidth; 7599 int levelshift; 7600 int levelx; 7601 int levely; 7602 NVGpickPath** cell = null; 7603 7604 // Bit tricks for inserting into an implicit quadtree. 7605 7606 // Calc bounds of path in cells at the lowest level 7607 cellbounds.ptr[0] = cast(int)(pp.bounds.ptr[0]/ps.xdim); 7608 cellbounds.ptr[1] = cast(int)(pp.bounds.ptr[1]/ps.ydim); 7609 cellbounds.ptr[2] = cast(int)(pp.bounds.ptr[2]/ps.xdim); 7610 cellbounds.ptr[3] = cast(int)(pp.bounds.ptr[3]/ps.ydim); 7611 7612 // Find which bits differ between the min/max x/y coords 7613 cellbounds.ptr[0] ^= cellbounds.ptr[2]; 7614 cellbounds.ptr[1] ^= cellbounds.ptr[3]; 7615 7616 // Use the number of bits used (countBitsUsed(x) == sizeof(int) * 8 - clz(x); 7617 // to calculate the level to insert at (the level at which the bounds fit in a single cell) 7618 level = nvg__min(base-nvg__countBitsUsed(cellbounds.ptr[0]), base-nvg__countBitsUsed(cellbounds.ptr[1])); 7619 if (level < 0) level = 0; 7620 //{ 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]); } 7621 //level = 0; 7622 7623 // Find the correct cell in the chosen level, clamping to the edges. 7624 levelwidth = 1<<level; 7625 levelshift = (ps.nlevels-level)-1; 7626 levelx = nvg__clamp(cellbounds.ptr[2]>>levelshift, 0, levelwidth-1); 7627 levely = nvg__clamp(cellbounds.ptr[3]>>levelshift, 0, levelwidth-1); 7628 7629 // Insert the path into the linked list at that cell. 7630 cell = &ps.levels[level][levely*levelwidth+levelx]; 7631 7632 pp.cellnext = *cell; 7633 *cell = pp; 7634 7635 if (ps.paths is null) ps.lastPath = pp; 7636 pp.next = ps.paths; 7637 ps.paths = pp; 7638 7639 // Store the order (depth) of the path for picking ops. 7640 pp.order = cast(short)ps.npaths; 7641 ++ps.npaths; 7642 } 7643 7644 void nvg__pickBeginFrame (NVGContext ctx, int width, int height) { 7645 NVGpickScene* ps = nvg__pickSceneGet(ctx); 7646 7647 //NVG_PICK_DEBUG_NEWFRAME(); 7648 7649 // Return all paths & sub paths from last frame to the free list 7650 while (ps.paths !is null) { 7651 NVGpickPath* pp = ps.paths.next; 7652 nvg__freePickPath(ps, ps.paths); 7653 ps.paths = pp; 7654 } 7655 7656 ps.paths = null; 7657 ps.npaths = 0; 7658 7659 // Store the screen metrics for the quadtree 7660 ps.width = width; 7661 ps.height = height; 7662 7663 immutable float lowestSubDiv = cast(float)(1<<(ps.nlevels-1)); 7664 ps.xdim = cast(float)width/lowestSubDiv; 7665 ps.ydim = cast(float)height/lowestSubDiv; 7666 7667 // Allocate the quadtree if required. 7668 if (ps.levels is null) { 7669 int ncells = 1; 7670 7671 ps.levels = cast(NVGpickPath***)malloc((NVGpickPath**).sizeof*ps.nlevels); 7672 for (int l = 0; l < ps.nlevels; ++l) { 7673 int leveldim = 1<<l; 7674 ncells += leveldim*leveldim; 7675 } 7676 7677 ps.levels[0] = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ncells); 7678 7679 int cell = 1; 7680 for (int l = 1; l < ps.nlevels; ++l) { 7681 ps.levels[l] = &ps.levels[0][cell]; 7682 int leveldim = 1<<l; 7683 cell += leveldim*leveldim; 7684 } 7685 7686 ps.ncells = ncells; 7687 } 7688 memset(ps.levels[0], 0, ps.ncells*(NVGpickPath*).sizeof); 7689 7690 // Allocate temporary storage for nvgHitTestAll results if required. 7691 if (ps.picked is null) { 7692 ps.cpicked = 16; 7693 ps.picked = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ps.cpicked); 7694 } 7695 7696 ps.npoints = 0; 7697 ps.nsegments = 0; 7698 } 7699 } // nothrow @trusted @nogc 7700 7701 7702 /// Return outline of the current path. Returned outline is not flattened. 7703 /// Group: paths 7704 public NVGPathOutline getCurrPathOutline (NVGContext ctx) nothrow @trusted @nogc { 7705 if (ctx is null || !ctx.contextAlive || ctx.ncommands == 0) return NVGPathOutline.init; 7706 7707 auto res = NVGPathOutline.createNew(); 7708 7709 const(float)[] acommands = ctx.commands[0..ctx.ncommands]; 7710 int ncommands = cast(int)acommands.length; 7711 const(float)* commands = acommands.ptr; 7712 7713 float cx = 0, cy = 0; 7714 float[2] start = void; 7715 float[4] totalBounds = [float.max, float.max, -float.max, -float.max]; 7716 float[8] bcp = void; // bezier curve points; used to calculate bounds 7717 7718 void addToBounds (in float x, in float y) nothrow @trusted @nogc { 7719 totalBounds.ptr[0] = nvg__min(totalBounds.ptr[0], x); 7720 totalBounds.ptr[1] = nvg__min(totalBounds.ptr[1], y); 7721 totalBounds.ptr[2] = nvg__max(totalBounds.ptr[2], x); 7722 totalBounds.ptr[3] = nvg__max(totalBounds.ptr[3], y); 7723 } 7724 7725 bool hasPoints = false; 7726 7727 void closeIt () nothrow @trusted @nogc { 7728 if (!hasPoints) return; 7729 if (cx != start.ptr[0] || cy != start.ptr[1]) { 7730 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo); 7731 res.ds.putArgs(start[]); 7732 cx = start.ptr[0]; 7733 cy = start.ptr[1]; 7734 addToBounds(cx, cy); 7735 } 7736 } 7737 7738 int i = 0; 7739 while (i < ncommands) { 7740 int cmd = cast(int)commands[i++]; 7741 switch (cmd) { 7742 case Command.MoveTo: // one coordinate pair 7743 const(float)* tfxy = commands+i; 7744 i += 2; 7745 // add command 7746 res.ds.putCommand(NVGPathOutline.Command.Kind.MoveTo); 7747 res.ds.putArgs(tfxy[0..2]); 7748 // new starting point 7749 start.ptr[0..2] = tfxy[0..2]; 7750 cx = tfxy[0]; 7751 cy = tfxy[0]; 7752 addToBounds(cx, cy); 7753 hasPoints = true; 7754 break; 7755 case Command.LineTo: // one coordinate pair 7756 const(float)* tfxy = commands+i; 7757 i += 2; 7758 // add command 7759 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo); 7760 res.ds.putArgs(tfxy[0..2]); 7761 cx = tfxy[0]; 7762 cy = tfxy[0]; 7763 addToBounds(cx, cy); 7764 hasPoints = true; 7765 break; 7766 case Command.BezierTo: // three coordinate pairs 7767 const(float)* tfxy = commands+i; 7768 i += 3*2; 7769 // add command 7770 res.ds.putCommand(NVGPathOutline.Command.Kind.BezierTo); 7771 res.ds.putArgs(tfxy[0..6]); 7772 // bounds 7773 bcp.ptr[0] = cx; 7774 bcp.ptr[1] = cy; 7775 bcp.ptr[2..8] = tfxy[0..6]; 7776 nvg__bezierBounds(bcp.ptr, totalBounds); 7777 cx = tfxy[4]; 7778 cy = tfxy[5]; 7779 hasPoints = true; 7780 break; 7781 case Command.Close: 7782 closeIt(); 7783 hasPoints = false; 7784 break; 7785 case Command.Winding: 7786 //psp.winding = cast(short)cast(int)commands[i]; 7787 i += 1; 7788 break; 7789 default: 7790 break; 7791 } 7792 } 7793 7794 res.ds.bounds[] = totalBounds[]; 7795 return res; 7796 } 7797 7798 7799 // ////////////////////////////////////////////////////////////////////////// // 7800 // Text 7801 7802 /** Creates font by loading it from the disk from specified file name. 7803 * Returns handle to the font or FONS_INVALID (aka -1) on error. 7804 * Use "fontname:noaa" as [name] to turn off antialiasing (if font driver supports that). 7805 * 7806 * On POSIX systems it is possible to use fontconfig font names too. 7807 * `:noaa` in font path is still allowed, but it must be the last option. 7808 * 7809 * Group: text_api 7810 */ 7811 public int createFont (NVGContext ctx, const(char)[] name, const(char)[] path) nothrow @trusted { 7812 return ctx.fs.addFont(name, path, ctx.params.fontAA); 7813 } 7814 7815 /** Creates font by loading it from the specified memory chunk. 7816 * Returns handle to the font or FONS_INVALID (aka -1) on error. 7817 * Won't free data on error. 7818 * 7819 * Group: text_api 7820 */ 7821 public int createFontMem (NVGContext ctx, const(char)[] name, ubyte* data, int ndata, bool freeData) nothrow @trusted @nogc { 7822 return ctx.fs.addFontMem(name, data, ndata, freeData, ctx.params.fontAA); 7823 } 7824 7825 /// Add fonts from another context. 7826 /// This is more effective than reloading fonts, 'cause font data will be shared. 7827 /// Group: text_api 7828 public void addFontsFrom (NVGContext ctx, NVGContext source) nothrow @trusted @nogc { 7829 if (ctx is null || source is null) return; 7830 ctx.fs.addFontsFrom(source.fs); 7831 } 7832 7833 /// Finds a loaded font of specified name, and returns handle to it, or FONS_INVALID (aka -1) if the font is not found. 7834 /// Group: text_api 7835 public int findFont (NVGContext ctx, const(char)[] name) nothrow @trusted @nogc { 7836 pragma(inline, true); 7837 return (name.length == 0 ? FONS_INVALID : ctx.fs.getFontByName(name)); 7838 } 7839 7840 /// Sets the font size of current text style. 7841 /// Group: text_api 7842 public void fontSize (NVGContext ctx, float size) nothrow @trusted @nogc { 7843 pragma(inline, true); 7844 nvg__getState(ctx).fontSize = size; 7845 } 7846 7847 /// Gets the font size of current text style. 7848 /// Group: text_api 7849 public float fontSize (NVGContext ctx) nothrow @trusted @nogc { 7850 pragma(inline, true); 7851 return nvg__getState(ctx).fontSize; 7852 } 7853 7854 /// Sets the blur of current text style. 7855 /// Group: text_api 7856 public void fontBlur (NVGContext ctx, float blur) nothrow @trusted @nogc { 7857 pragma(inline, true); 7858 nvg__getState(ctx).fontBlur = blur; 7859 } 7860 7861 /// Gets the blur of current text style. 7862 /// Group: text_api 7863 public float fontBlur (NVGContext ctx) nothrow @trusted @nogc { 7864 pragma(inline, true); 7865 return nvg__getState(ctx).fontBlur; 7866 } 7867 7868 /// Sets the letter spacing of current text style. 7869 /// Group: text_api 7870 public void textLetterSpacing (NVGContext ctx, float spacing) nothrow @trusted @nogc { 7871 pragma(inline, true); 7872 nvg__getState(ctx).letterSpacing = spacing; 7873 } 7874 7875 /// Gets the letter spacing of current text style. 7876 /// Group: text_api 7877 public float textLetterSpacing (NVGContext ctx) nothrow @trusted @nogc { 7878 pragma(inline, true); 7879 return nvg__getState(ctx).letterSpacing; 7880 } 7881 7882 /// Sets the proportional line height of current text style. The line height is specified as multiple of font size. 7883 /// Group: text_api 7884 public void textLineHeight (NVGContext ctx, float lineHeight) nothrow @trusted @nogc { 7885 pragma(inline, true); 7886 nvg__getState(ctx).lineHeight = lineHeight; 7887 } 7888 7889 /// Gets the proportional line height of current text style. The line height is specified as multiple of font size. 7890 /// Group: text_api 7891 public float textLineHeight (NVGContext ctx) nothrow @trusted @nogc { 7892 pragma(inline, true); 7893 return nvg__getState(ctx).lineHeight; 7894 } 7895 7896 /// Sets the text align of current text style, see [NVGTextAlign] for options. 7897 /// Group: text_api 7898 public void textAlign (NVGContext ctx, NVGTextAlign talign) nothrow @trusted @nogc { 7899 pragma(inline, true); 7900 nvg__getState(ctx).textAlign = talign; 7901 } 7902 7903 /// Ditto. 7904 public void textAlign (NVGContext ctx, NVGTextAlign.H h) nothrow @trusted @nogc { 7905 pragma(inline, true); 7906 nvg__getState(ctx).textAlign.horizontal = h; 7907 } 7908 7909 /// Ditto. 7910 public void textAlign (NVGContext ctx, NVGTextAlign.V v) nothrow @trusted @nogc { 7911 pragma(inline, true); 7912 nvg__getState(ctx).textAlign.vertical = v; 7913 } 7914 7915 /// Ditto. 7916 public void textAlign (NVGContext ctx, NVGTextAlign.H h, NVGTextAlign.V v) nothrow @trusted @nogc { 7917 pragma(inline, true); 7918 nvg__getState(ctx).textAlign.reset(h, v); 7919 } 7920 7921 /// Ditto. 7922 public void textAlign (NVGContext ctx, NVGTextAlign.V v, NVGTextAlign.H h) nothrow @trusted @nogc { 7923 pragma(inline, true); 7924 nvg__getState(ctx).textAlign.reset(h, v); 7925 } 7926 7927 /// Gets the text align of current text style, see [NVGTextAlign] for options. 7928 /// Group: text_api 7929 public NVGTextAlign textAlign (NVGContext ctx) nothrow @trusted @nogc { 7930 pragma(inline, true); 7931 return nvg__getState(ctx).textAlign; 7932 } 7933 7934 /// Sets the font face based on specified id of current text style. 7935 /// Group: text_api 7936 public void fontFaceId (NVGContext ctx, int font) nothrow @trusted @nogc { 7937 pragma(inline, true); 7938 nvg__getState(ctx).fontId = font; 7939 } 7940 7941 /// Gets the font face based on specified id of current text style. 7942 /// Group: text_api 7943 public int fontFaceId (NVGContext ctx) nothrow @trusted @nogc { 7944 pragma(inline, true); 7945 return nvg__getState(ctx).fontId; 7946 } 7947 7948 /** Sets the font face based on specified name of current text style. 7949 * 7950 * The underlying implementation is using O(1) data structure to lookup 7951 * font names, so you probably should use this function instead of [fontFaceId] 7952 * to make your code more robust and less error-prone. 7953 * 7954 * Group: text_api 7955 */ 7956 public void fontFace (NVGContext ctx, const(char)[] font) nothrow @trusted @nogc { 7957 pragma(inline, true); 7958 nvg__getState(ctx).fontId = ctx.fs.getFontByName(font); 7959 } 7960 7961 static if (is(typeof(&fons__nvg__toPath))) { 7962 public enum NanoVegaHasCharToPath = true; /// 7963 } else { 7964 public enum NanoVegaHasCharToPath = false; /// 7965 } 7966 7967 /// Adds glyph outlines to the current path. Vertical 0 is baseline. 7968 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead. 7969 /// Returns `false` if there is no such glyph, or current font is not scalable. 7970 /// Group: text_api 7971 public bool charToPath (NVGContext ctx, dchar dch, float[] bounds=null) nothrow @trusted @nogc { 7972 NVGstate* state = nvg__getState(ctx); 7973 ctx.fs.fontId = state.fontId; 7974 return ctx.fs.toPath(ctx, dch, bounds); 7975 } 7976 7977 static if (is(typeof(&fons__nvg__bounds))) { 7978 public enum NanoVegaHasCharPathBounds = true; /// 7979 } else { 7980 public enum NanoVegaHasCharPathBounds = false; /// 7981 } 7982 7983 /// Returns bounds of the glyph outlines. Vertical 0 is baseline. 7984 /// The glyph is not scaled in any way. 7985 /// Returns `false` if there is no such glyph, or current font is not scalable. 7986 /// Group: text_api 7987 public bool charPathBounds (NVGContext ctx, dchar dch, float[] bounds) nothrow @trusted @nogc { 7988 NVGstate* state = nvg__getState(ctx); 7989 ctx.fs.fontId = state.fontId; 7990 return ctx.fs.getPathBounds(dch, bounds); 7991 } 7992 7993 /** [charOutline] will return [NVGPathOutline]. 7994 7995 some usage samples: 7996 7997 --- 7998 float[4] bounds = void; 7999 8000 nvg.scale(0.5, 0.5); 8001 nvg.translate(500, 800); 8002 nvg.evenOddFill; 8003 8004 nvg.newPath(); 8005 nvg.charToPath('&', bounds[]); 8006 conwriteln(bounds[]); 8007 nvg.fillPaint(nvg.linearGradient(0, 0, 600, 600, NVGColor("#f70"), NVGColor("#ff0"))); 8008 nvg.strokeColor(NVGColor("#0f0")); 8009 nvg.strokeWidth = 3; 8010 nvg.fill(); 8011 nvg.stroke(); 8012 // glyph bounds 8013 nvg.newPath(); 8014 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); 8015 nvg.strokeColor(NVGColor("#00f")); 8016 nvg.stroke(); 8017 8018 nvg.newPath(); 8019 nvg.charToPath('g', bounds[]); 8020 conwriteln(bounds[]); 8021 nvg.fill(); 8022 nvg.strokeColor(NVGColor("#0f0")); 8023 nvg.stroke(); 8024 // glyph bounds 8025 nvg.newPath(); 8026 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); 8027 nvg.strokeColor(NVGColor("#00f")); 8028 nvg.stroke(); 8029 8030 nvg.newPath(); 8031 nvg.moveTo(0, 0); 8032 nvg.lineTo(600, 0); 8033 nvg.strokeColor(NVGColor("#0ff")); 8034 nvg.stroke(); 8035 8036 if (auto ol = nvg.charOutline('Q')) { 8037 scope(exit) ol.kill(); 8038 nvg.newPath(); 8039 conwriteln("==== length: ", ol.length, " ===="); 8040 foreach (const ref cmd; ol.commands) { 8041 //conwriteln(" ", cmd.code, ": ", cmd.args[]); 8042 assert(cmd.valid); 8043 final switch (cmd.code) { 8044 case cmd.Kind.MoveTo: nvg.moveTo(cmd.args[0], cmd.args[1]); break; 8045 case cmd.Kind.LineTo: nvg.lineTo(cmd.args[0], cmd.args[1]); break; 8046 case cmd.Kind.QuadTo: nvg.quadTo(cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]); break; 8047 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; 8048 } 8049 } 8050 nvg.strokeColor(NVGColor("#f00")); 8051 nvg.stroke(); 8052 } 8053 --- 8054 8055 Group: text_api 8056 */ 8057 public struct NVGPathOutline { 8058 private nothrow @trusted @nogc: 8059 struct DataStore { 8060 uint rc; // refcount 8061 ubyte* data; 8062 uint used; 8063 uint size; 8064 uint ccount; // number of commands 8065 float[4] bounds = 0; /// outline bounds 8066 nothrow @trusted @nogc: 8067 void putBytes (const(void)[] b) { 8068 if (b.length == 0) return; 8069 if (b.length >= int.max/8) assert(0, "NanoVega: out of memory"); 8070 if (int.max/8-used < b.length) assert(0, "NanoVega: out of memory"); 8071 if (used+cast(uint)b.length > size) { 8072 import core.stdc.stdlib : realloc; 8073 uint newsz = size; 8074 while (newsz < used+cast(uint)b.length) newsz = (newsz == 0 ? 1024 : newsz < 32768 ? newsz*2 : newsz+8192); 8075 assert(used+cast(uint)b.length <= newsz); 8076 data = cast(ubyte*)realloc(data, newsz); 8077 if (data is null) assert(0, "NanoVega: out of memory"); 8078 size = newsz; 8079 } 8080 import core.stdc.string : memcpy; 8081 memcpy(data+used, b.ptr, b.length); 8082 used += cast(uint)b.length; 8083 } 8084 void putCommand (ubyte cmd) { pragma(inline, true); ++ccount; putBytes((&cmd)[0..1]); } 8085 void putArgs (const(float)[] f...) { pragma(inline, true); putBytes(f[]); } 8086 } 8087 8088 static void incRef (DataStore* ds) { 8089 pragma(inline, true); 8090 if (ds !is null) { 8091 ++ds.rc; 8092 //{ import core.stdc.stdio; printf("ods(%p): incref: newrc=%u\n", ds, ds.rc); } 8093 } 8094 } 8095 8096 static void decRef (DataStore* ds) { 8097 version(aliced) pragma(inline, true); 8098 if (ds !is null) { 8099 //{ import core.stdc.stdio; printf("ods(%p): decref: newrc=%u\n", ds, ds.rc-1); } 8100 if (--ds.rc == 0) { 8101 import core.stdc.stdlib : free; 8102 import core.stdc.string : memset; 8103 if (ds.data !is null) free(ds.data); 8104 memset(ds, 0, DataStore.sizeof); // just in case 8105 free(ds); 8106 //{ import core.stdc.stdio; printf(" ods(%p): killed.\n"); } 8107 } 8108 } 8109 } 8110 8111 private: 8112 static NVGPathOutline createNew () { 8113 import core.stdc.stdlib : malloc; 8114 import core.stdc.string : memset; 8115 auto ds = cast(DataStore*)malloc(DataStore.sizeof); 8116 if (ds is null) assert(0, "NanoVega: out of memory"); 8117 memset(ds, 0, DataStore.sizeof); 8118 ds.rc = 1; 8119 NVGPathOutline res; 8120 res.dsaddr = cast(usize)ds; 8121 return res; 8122 } 8123 8124 private: 8125 usize dsaddr; // fool GC 8126 8127 @property inout(DataStore)* ds () inout pure { pragma(inline, true); return cast(DataStore*)dsaddr; } 8128 8129 public: 8130 /// commands 8131 static struct Command { 8132 /// 8133 enum Kind : ubyte { 8134 MoveTo, /// 8135 LineTo, /// 8136 QuadTo, /// 8137 BezierTo, /// 8138 End, /// no more commands (this command is not `valid`!) 8139 8140 } 8141 Kind code; /// 8142 const(float)[] args; /// 8143 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (code >= Kind.min && code < Kind.End && args.length >= 2); } /// 8144 8145 static uint arglen (Kind code) pure nothrow @safe @nogc { 8146 pragma(inline, true); 8147 return 8148 code == Kind.MoveTo || code == Kind.LineTo ? 2 : 8149 code == Kind.QuadTo ? 4 : 8150 code == Kind.BezierTo ? 6 : 8151 0; 8152 } 8153 8154 /// perform NanoVega command with stored data. 8155 void perform (NVGContext ctx) const nothrow @trusted @nogc { 8156 if (ctx is null) return; 8157 final switch (code) { 8158 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(args.ptr[0..2]); break; 8159 case Kind.LineTo: if (args.length > 1) ctx.lineTo(args.ptr[0..2]); break; 8160 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(args.ptr[0..4]); break; 8161 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(args.ptr[0..6]); break; 8162 case Kind.End: break; 8163 } 8164 } 8165 8166 /// perform NanoVega command with stored data, transforming points with [xform] transformation matrix. 8167 void perform() (NVGContext ctx, const scope auto ref NVGMatrix xform) const nothrow @trusted @nogc { 8168 if (ctx is null || !valid) return; 8169 float[6] pts = void; 8170 pts[0..args.length] = args[]; 8171 foreach (immutable pidx; 0..args.length/2) xform.point(pts.ptr[pidx*2+0], pts.ptr[pidx*2+1]); 8172 final switch (code) { 8173 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(pts.ptr[0..2]); break; 8174 case Kind.LineTo: if (args.length > 1) ctx.lineTo(pts.ptr[0..2]); break; 8175 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(pts.ptr[0..4]); break; 8176 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(pts.ptr[0..6]); break; 8177 case Kind.End: break; 8178 } 8179 } 8180 } 8181 8182 public: 8183 /// Create new path with quadratic bezier (first command is MoveTo, second command is QuadTo). 8184 static NVGPathOutline createNewQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) { 8185 auto res = createNew(); 8186 res.ds.putCommand(Command.Kind.MoveTo); 8187 res.ds.putArgs(x0, y0); 8188 res.ds.putCommand(Command.Kind.QuadTo); 8189 res.ds.putArgs(cx, cy, x, y); 8190 return res; 8191 } 8192 8193 /// Create new path with cubic bezier (first command is MoveTo, second command is BezierTo). 8194 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) { 8195 auto res = createNew(); 8196 res.ds.putCommand(Command.Kind.MoveTo); 8197 res.ds.putArgs(x1, y1); 8198 res.ds.putCommand(Command.Kind.BezierTo); 8199 res.ds.putArgs(x2, y2, x3, y3, x4, y4); 8200 return res; 8201 } 8202 8203 public: 8204 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); } 8205 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); } 8206 8207 void opAssign() (const scope auto ref NVGPathOutline a) { 8208 incRef(cast(DataStore*)a.dsaddr); 8209 decRef(cast(DataStore*)dsaddr); 8210 dsaddr = a.dsaddr; 8211 } 8212 8213 /// Clear storage. 8214 void clear () { 8215 pragma(inline, true); 8216 decRef(ds); 8217 dsaddr = 0; 8218 } 8219 8220 /// Is this outline empty? 8221 @property empty () const pure { pragma(inline, true); return (dsaddr == 0 || ds.ccount == 0); } 8222 8223 /// Returns number of commands in outline. 8224 @property int length () const pure { pragma(inline, true); return (dsaddr ? ds.ccount : 0); } 8225 8226 /// Returns "flattened" path. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8227 NVGPathOutline flatten () const { pragma(inline, true); return flattenInternal(null); } 8228 8229 /// Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8230 NVGPathOutline flatten() (const scope auto ref NVGMatrix mt) const { pragma(inline, true); return flattenInternal(&mt); } 8231 8232 // Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8233 private NVGPathOutline flattenInternal (scope NVGMatrix* tfm) const { 8234 import core.stdc.string : memset; 8235 8236 NVGPathOutline res; 8237 if (dsaddr == 0 || ds.ccount == 0) { res = this; return res; } // nothing to do 8238 8239 // check if we need to flatten the path 8240 if (tfm is null) { 8241 bool dowork = false; 8242 foreach (const ref cs; commands) { 8243 if (cs.code != Command.Kind.MoveTo && cs.code != Command.Kind.LineTo) { 8244 dowork = true; 8245 break; 8246 } 8247 } 8248 if (!dowork) { res = this; return res; } // nothing to do 8249 } 8250 8251 NVGcontextinternal ctx; 8252 memset(&ctx, 0, ctx.sizeof); 8253 ctx.cache = nvg__allocPathCache(); 8254 scope(exit) { 8255 import core.stdc.stdlib : free; 8256 nvg__deletePathCache(ctx.cache); 8257 } 8258 8259 ctx.tessTol = 0.25f; 8260 ctx.angleTol = 0; // 0 -- angle tolerance for McSeem Bezier rasterizer 8261 ctx.cuspLimit = 0; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps) 8262 ctx.distTol = 0.01f; 8263 ctx.tesselatortype = NVGTesselation.DeCasteljau; 8264 8265 nvg__addPath(&ctx); // we need this for `nvg__addPoint()` 8266 8267 // has some curves or transformations, convert path 8268 res = createNew(); 8269 float[8] args = void; 8270 8271 res.ds.bounds = [float.max, float.max, -float.max, -float.max]; 8272 8273 float lastX = float.max, lastY = float.max; 8274 bool lastWasMove = false; 8275 8276 void addPoint (float x, float y, Command.Kind cmd=Command.Kind.LineTo) nothrow @trusted @nogc { 8277 if (tfm !is null) tfm.point(x, y); 8278 bool isMove = (cmd == Command.Kind.MoveTo); 8279 if (isMove) { 8280 // moveto 8281 if (lastWasMove && nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return; 8282 } else { 8283 // lineto 8284 if (nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return; 8285 } 8286 lastWasMove = isMove; 8287 lastX = x; 8288 lastY = y; 8289 res.ds.putCommand(cmd); 8290 res.ds.putArgs(x, y); 8291 res.ds.bounds.ptr[0] = nvg__min(res.ds.bounds.ptr[0], x); 8292 res.ds.bounds.ptr[1] = nvg__min(res.ds.bounds.ptr[1], y); 8293 res.ds.bounds.ptr[2] = nvg__max(res.ds.bounds.ptr[2], x); 8294 res.ds.bounds.ptr[3] = nvg__max(res.ds.bounds.ptr[3], y); 8295 } 8296 8297 // sorry for this pasta 8298 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 { 8299 ctx.cache.npoints = 0; 8300 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) { 8301 nvg__tesselateBezier(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner); 8302 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) { 8303 nvg__tesselateBezierMcSeem(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner); 8304 } else { 8305 nvg__tesselateBezierAFD(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, PointFlag.Corner); 8306 } 8307 // add generated points 8308 foreach (const ref pt; ctx.cache.points[0..ctx.cache.npoints]) addPoint(pt.x, pt.y); 8309 } 8310 8311 void flattenQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) { 8312 flattenBezier( 8313 x0, y0, 8314 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 8315 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 8316 x, y, 8317 0, 8318 ); 8319 } 8320 8321 float cx = 0, cy = 0; 8322 foreach (const ref cs; commands) { 8323 switch (cs.code) { 8324 case Command.Kind.LineTo: 8325 case Command.Kind.MoveTo: 8326 addPoint(cs.args[0], cs.args[1], cs.code); 8327 cx = cs.args[0]; 8328 cy = cs.args[1]; 8329 break; 8330 case Command.Kind.QuadTo: 8331 flattenQuad(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3]); 8332 cx = cs.args[2]; 8333 cy = cs.args[3]; 8334 break; 8335 case Command.Kind.BezierTo: 8336 flattenBezier(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3], cs.args[4], cs.args[5], 0); 8337 cx = cs.args[4]; 8338 cy = cs.args[5]; 8339 break; 8340 default: 8341 break; 8342 } 8343 } 8344 8345 return res; 8346 } 8347 8348 /// Returns forward range with all glyph commands. 8349 auto commands () const nothrow @trusted @nogc { 8350 static struct Range { 8351 private nothrow @trusted @nogc: 8352 usize dsaddr; 8353 uint cpos; // current position in data 8354 uint cleft; // number of commands left 8355 @property const(ubyte)* data () inout pure { pragma(inline, true); return (dsaddr ? (cast(DataStore*)dsaddr).data : null); } 8356 public: 8357 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); } 8358 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); } 8359 void opAssign() (const scope auto ref Range a) { 8360 incRef(cast(DataStore*)a.dsaddr); 8361 decRef(cast(DataStore*)dsaddr); 8362 dsaddr = a.dsaddr; 8363 cpos = a.cpos; 8364 cleft = a.cleft; 8365 } 8366 float[4] bounds () const pure { float[4] res = 0; pragma(inline, true); if (dsaddr) res[] = (cast(DataStore*)dsaddr).bounds[]; return res; } /// outline bounds 8367 @property bool empty () const pure { pragma(inline, true); return (cleft == 0); } 8368 @property int length () const pure { pragma(inline, true); return cleft; } 8369 @property Range save () const { pragma(inline, true); Range res = this; return res; } 8370 @property Command front () const { 8371 Command res = void; 8372 if (cleft > 0) { 8373 res.code = cast(Command.Kind)data[cpos]; 8374 switch (res.code) { 8375 case Command.Kind.MoveTo: 8376 case Command.Kind.LineTo: 8377 res.args = (cast(const(float*))(data+cpos+1))[0..1*2]; 8378 break; 8379 case Command.Kind.QuadTo: 8380 res.args = (cast(const(float*))(data+cpos+1))[0..2*2]; 8381 break; 8382 case Command.Kind.BezierTo: 8383 res.args = (cast(const(float*))(data+cpos+1))[0..3*2]; 8384 break; 8385 default: 8386 res.code = Command.Kind.End; 8387 res.args = null; 8388 break; 8389 } 8390 } else { 8391 res.code = Command.Kind.End; 8392 res.args = null; 8393 } 8394 return res; 8395 } 8396 void popFront () { 8397 if (cleft <= 1) { cleft = 0; return; } // don't waste time skipping last command 8398 --cleft; 8399 switch (data[cpos]) { 8400 case Command.Kind.MoveTo: 8401 case Command.Kind.LineTo: 8402 cpos += 1+1*2*cast(uint)float.sizeof; 8403 break; 8404 case Command.Kind.QuadTo: 8405 cpos += 1+2*2*cast(uint)float.sizeof; 8406 break; 8407 case Command.Kind.BezierTo: 8408 cpos += 1+3*2*cast(uint)float.sizeof; 8409 break; 8410 default: 8411 cleft = 0; 8412 break; 8413 } 8414 } 8415 } 8416 if (dsaddr) { 8417 incRef(cast(DataStore*)dsaddr); // range anchors it 8418 return Range(dsaddr, 0, ds.ccount); 8419 } else { 8420 return Range.init; 8421 } 8422 } 8423 } 8424 8425 public alias NVGGlyphOutline = NVGPathOutline; /// For backwards compatibility. 8426 8427 /// Destroy glyph outiline and free allocated memory. 8428 /// Group: text_api 8429 public void kill (ref NVGPathOutline ol) nothrow @trusted @nogc { 8430 pragma(inline, true); 8431 ol.clear(); 8432 } 8433 8434 static if (is(typeof(&fons__nvg__toOutline))) { 8435 public enum NanoVegaHasCharOutline = true; /// 8436 } else { 8437 public enum NanoVegaHasCharOutline = false; /// 8438 } 8439 8440 /// Returns glyph outlines as array of commands. Vertical 0 is baseline. 8441 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead. 8442 /// Returns `null` if there is no such glyph, or current font is not scalable. 8443 /// Group: text_api 8444 public NVGPathOutline charOutline (NVGContext ctx, dchar dch) nothrow @trusted @nogc { 8445 import core.stdc.stdlib : malloc; 8446 import core.stdc.string : memcpy; 8447 NVGstate* state = nvg__getState(ctx); 8448 ctx.fs.fontId = state.fontId; 8449 auto oline = NVGPathOutline.createNew(); 8450 if (!ctx.fs.toOutline(dch, oline.ds)) oline.clear(); 8451 return oline; 8452 } 8453 8454 8455 float nvg__quantize (float a, float d) pure nothrow @safe @nogc { 8456 pragma(inline, true); 8457 return (cast(int)(a/d+0.5f))*d; 8458 } 8459 8460 float nvg__getFontScale (NVGstate* state) /*pure*/ nothrow @safe @nogc { 8461 pragma(inline, true); 8462 return nvg__min(nvg__quantize(nvg__getAverageScale(state.xform), 0.01f), 4.0f); 8463 } 8464 8465 void nvg__flushTextTexture (NVGContext ctx) nothrow @trusted @nogc { 8466 int[4] dirty = void; 8467 if (ctx.fs.validateTexture(dirty.ptr)) { 8468 auto fontImage = &ctx.fontImages[ctx.fontImageIdx]; 8469 // Update texture 8470 if (fontImage.valid) { 8471 int iw, ih; 8472 const(ubyte)* data = ctx.fs.getTextureData(&iw, &ih); 8473 int x = dirty[0]; 8474 int y = dirty[1]; 8475 int w = dirty[2]-dirty[0]; 8476 int h = dirty[3]-dirty[1]; 8477 ctx.params.renderUpdateTexture(ctx.params.userPtr, fontImage.id, x, y, w, h, data); 8478 } 8479 } 8480 } 8481 8482 bool nvg__allocTextAtlas (NVGContext ctx) nothrow @trusted @nogc { 8483 int iw, ih; 8484 nvg__flushTextTexture(ctx); 8485 if (ctx.fontImageIdx >= NVG_MAX_FONTIMAGES-1) return false; 8486 // if next fontImage already have a texture 8487 if (ctx.fontImages[ctx.fontImageIdx+1].valid) { 8488 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx+1], iw, ih); 8489 } else { 8490 // calculate the new font image size and create it 8491 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx], iw, ih); 8492 if (iw > ih) ih *= 2; else iw *= 2; 8493 if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE; 8494 ctx.fontImages[ctx.fontImageIdx+1].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, iw, ih, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); 8495 if (ctx.fontImages[ctx.fontImageIdx+1].id > 0) { 8496 ctx.fontImages[ctx.fontImageIdx+1].ctx = ctx; 8497 ctx.nvg__imageIncRef(ctx.fontImages[ctx.fontImageIdx+1].id, false); // don't increment driver refcount 8498 } 8499 } 8500 ++ctx.fontImageIdx; 8501 ctx.fs.resetAtlas(iw, ih); 8502 return true; 8503 } 8504 8505 void nvg__renderText (NVGContext ctx, NVGVertex* verts, int nverts) nothrow @trusted @nogc { 8506 NVGstate* state = nvg__getState(ctx); 8507 NVGPaint paint = state.fill; 8508 8509 // Render triangles. 8510 paint.image = ctx.fontImages[ctx.fontImageIdx]; 8511 8512 // Apply global alpha 8513 paint.innerColor.a *= state.alpha; 8514 paint.middleColor.a *= state.alpha; 8515 paint.outerColor.a *= state.alpha; 8516 8517 ctx.params.renderTriangles(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &paint, &state.scissor, verts, nverts); 8518 8519 ++ctx.drawCallCount; 8520 ctx.textTriCount += nverts/3; 8521 } 8522 8523 /// Draws text string at specified location. Returns next x position. 8524 /// Group: text_api 8525 public float text(T) (NVGContext ctx, float x, float y, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 8526 NVGstate* state = nvg__getState(ctx); 8527 FONSTextIter!T iter, prevIter; 8528 FONSQuad q; 8529 NVGVertex* verts; 8530 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8531 float invscale = 1.0f/scale; 8532 int cverts = 0; 8533 int nverts = 0; 8534 8535 if (state.fontId == FONS_INVALID) return x; 8536 if (str.length == 0) return x; 8537 8538 ctx.fs.size = state.fontSize*scale; 8539 ctx.fs.spacing = state.letterSpacing*scale; 8540 ctx.fs.blur = state.fontBlur*scale; 8541 ctx.fs.textAlign = state.textAlign; 8542 ctx.fs.fontId = state.fontId; 8543 8544 cverts = nvg__max(2, cast(int)(str.length))*6; // conservative estimate 8545 verts = nvg__allocTempVerts(ctx, cverts); 8546 if (verts is null) return x; 8547 8548 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Required)) return x; 8549 prevIter = iter; 8550 while (iter.next(q)) { 8551 float[4*2] c = void; 8552 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8553 if (nverts != 0) { 8554 // TODO: add back-end bit to do this just once per frame 8555 nvg__flushTextTexture(ctx); 8556 nvg__renderText(ctx, verts, nverts); 8557 nverts = 0; 8558 } 8559 if (!nvg__allocTextAtlas(ctx)) break; // no memory :( 8560 iter = prevIter; 8561 iter.next(q); // try again 8562 if (iter.prevGlyphIndex < 0) { 8563 // still can not find glyph, try replacement 8564 iter = prevIter; 8565 if (!iter.getDummyChar(q)) break; 8566 } 8567 } 8568 prevIter = iter; 8569 // transform corners 8570 state.xform.point(&c[0], &c[1], q.x0*invscale, q.y0*invscale); 8571 state.xform.point(&c[2], &c[3], q.x1*invscale, q.y0*invscale); 8572 state.xform.point(&c[4], &c[5], q.x1*invscale, q.y1*invscale); 8573 state.xform.point(&c[6], &c[7], q.x0*invscale, q.y1*invscale); 8574 // create triangles 8575 if (nverts+6 <= cverts) { 8576 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts; 8577 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts; 8578 nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); ++nverts; 8579 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts; 8580 nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); ++nverts; 8581 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts; 8582 } 8583 } 8584 8585 // TODO: add back-end bit to do this just once per frame 8586 if (nverts > 0) { 8587 nvg__flushTextTexture(ctx); 8588 nvg__renderText(ctx, verts, nverts); 8589 } 8590 8591 return iter.nextx/scale; 8592 } 8593 8594 /** Draws multi-line text string at specified location wrapped at the specified width. 8595 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8596 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8597 * 8598 * Group: text_api 8599 */ 8600 public void textBox(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 8601 NVGstate* state = nvg__getState(ctx); 8602 if (state.fontId == FONS_INVALID) return; 8603 8604 NVGTextRow!T[2] rows; 8605 auto oldAlign = state.textAlign; 8606 scope(exit) state.textAlign = oldAlign; 8607 auto halign = state.textAlign.horizontal; 8608 float lineh = 0; 8609 8610 ctx.textMetrics(null, null, &lineh); 8611 state.textAlign.horizontal = NVGTextAlign.H.Left; 8612 for (;;) { 8613 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]); 8614 //{ import core.stdc.stdio : printf; printf("slen=%u; rlen=%u; bw=%f\n", cast(uint)str.length, cast(uint)rres.length, cast(double)breakRowWidth); } 8615 if (rres.length == 0) break; 8616 foreach (ref row; rres) { 8617 final switch (halign) { 8618 case NVGTextAlign.H.Left: ctx.text(x, y, row.row); break; 8619 case NVGTextAlign.H.Center: ctx.text(x+breakRowWidth*0.5f-row.width*0.5f, y, row.row); break; 8620 case NVGTextAlign.H.Right: ctx.text(x+breakRowWidth-row.width, y, row.row); break; 8621 } 8622 y += lineh*state.lineHeight; 8623 } 8624 str = rres[$-1].rest; 8625 } 8626 } 8627 8628 private template isGoodPositionDelegate(DG) { 8629 private DG dg; 8630 static if (is(typeof({ NVGGlyphPosition pos; bool res = dg(pos); })) || 8631 is(typeof({ NVGGlyphPosition pos; dg(pos); }))) 8632 enum isGoodPositionDelegate = true; 8633 else 8634 enum isGoodPositionDelegate = false; 8635 } 8636 8637 /** Calculates the glyph x positions of the specified text. 8638 * Measured values are returned in local coordinate space. 8639 * 8640 * Group: text_api 8641 */ 8642 public NVGGlyphPosition[] textGlyphPositions(T) (NVGContext ctx, float x, float y, const(T)[] str, NVGGlyphPosition[] positions) nothrow @trusted @nogc 8643 if (isAnyCharType!T) 8644 { 8645 if (str.length == 0 || positions.length == 0) return positions[0..0]; 8646 usize posnum; 8647 auto len = ctx.textGlyphPositions(x, y, str, (const scope ref NVGGlyphPosition pos) { 8648 positions.ptr[posnum++] = pos; 8649 return (posnum < positions.length); 8650 }); 8651 return positions[0..len]; 8652 } 8653 8654 /// Ditto. 8655 public int textGlyphPositions(T, DG) (NVGContext ctx, float x, float y, const(T)[] str, scope DG dg) 8656 if (isAnyCharType!T && isGoodPositionDelegate!DG) 8657 { 8658 import std.traits : ReturnType; 8659 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true; 8660 8661 NVGstate* state = nvg__getState(ctx); 8662 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8663 float invscale = 1.0f/scale; 8664 FONSTextIter!T iter, prevIter; 8665 FONSQuad q; 8666 int npos = 0; 8667 8668 if (str.length == 0) return 0; 8669 8670 ctx.fs.size = state.fontSize*scale; 8671 ctx.fs.spacing = state.letterSpacing*scale; 8672 ctx.fs.blur = state.fontBlur*scale; 8673 ctx.fs.textAlign = state.textAlign; 8674 ctx.fs.fontId = state.fontId; 8675 8676 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Optional)) return npos; 8677 prevIter = iter; 8678 while (iter.next(q)) { 8679 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8680 if (!nvg__allocTextAtlas(ctx)) break; // no memory 8681 iter = prevIter; 8682 iter.next(q); // try again 8683 if (iter.prevGlyphIndex < 0) { 8684 // still can not find glyph, try replacement 8685 iter = prevIter; 8686 if (!iter.getDummyChar(q)) break; 8687 } 8688 } 8689 prevIter = iter; 8690 NVGGlyphPosition position = void; //WARNING! 8691 position.strpos = cast(usize)(iter.stringp-str.ptr); 8692 position.x = iter.x*invscale; 8693 position.minx = nvg__min(iter.x, q.x0)*invscale; 8694 position.maxx = nvg__max(iter.nextx, q.x1)*invscale; 8695 ++npos; 8696 static if (RetBool) { if (!dg(position)) return npos; } else dg(position); 8697 } 8698 8699 return npos; 8700 } 8701 8702 private template isGoodRowDelegate(CT, DG) { 8703 private DG dg; 8704 static if (is(typeof({ NVGTextRow!CT row; bool res = dg(row); })) || 8705 is(typeof({ NVGTextRow!CT row; dg(row); }))) 8706 enum isGoodRowDelegate = true; 8707 else 8708 enum isGoodRowDelegate = false; 8709 } 8710 8711 /** Breaks the specified text into lines. 8712 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8713 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8714 * 8715 * Group: text_api 8716 */ 8717 public NVGTextRow!T[] textBreakLines(T) (NVGContext ctx, const(T)[] str, float breakRowWidth, NVGTextRow!T[] rows) nothrow @trusted @nogc 8718 if (isAnyCharType!T) 8719 { 8720 if (rows.length == 0) return rows; 8721 if (rows.length > int.max-1) rows = rows[0..int.max-1]; 8722 int nrow = 0; 8723 auto count = ctx.textBreakLines(str, breakRowWidth, (const scope ref NVGTextRow!T row) { 8724 rows[nrow++] = row; 8725 return (nrow < rows.length); 8726 }); 8727 return rows[0..count]; 8728 } 8729 8730 /** Breaks the specified text into lines. 8731 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8732 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8733 * Returns number of rows. 8734 * 8735 * Group: text_api 8736 */ 8737 public int textBreakLines(T, DG) (NVGContext ctx, const(T)[] str, float breakRowWidth, scope DG dg) 8738 if (isAnyCharType!T && isGoodRowDelegate!(T, DG)) 8739 { 8740 import std.traits : ReturnType; 8741 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true; 8742 8743 enum NVGcodepointType : int { 8744 Space, 8745 NewLine, 8746 Char, 8747 } 8748 8749 NVGstate* state = nvg__getState(ctx); 8750 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8751 float invscale = 1.0f/scale; 8752 FONSTextIter!T iter, prevIter; 8753 FONSQuad q; 8754 int nrows = 0; 8755 float rowStartX = 0; 8756 float rowWidth = 0; 8757 float rowMinX = 0; 8758 float rowMaxX = 0; 8759 int rowStart = 0; 8760 int rowEnd = 0; 8761 int wordStart = 0; 8762 float wordStartX = 0; 8763 float wordMinX = 0; 8764 int breakEnd = 0; 8765 float breakWidth = 0; 8766 float breakMaxX = 0; 8767 int type = NVGcodepointType.Space, ptype = NVGcodepointType.Space; 8768 uint pcodepoint = 0; 8769 8770 if (state.fontId == FONS_INVALID) return 0; 8771 if (str.length == 0 || dg is null) return 0; 8772 8773 ctx.fs.size = state.fontSize*scale; 8774 ctx.fs.spacing = state.letterSpacing*scale; 8775 ctx.fs.blur = state.fontBlur*scale; 8776 ctx.fs.textAlign = state.textAlign; 8777 ctx.fs.fontId = state.fontId; 8778 8779 breakRowWidth *= scale; 8780 8781 enum Phase { 8782 Normal, // searching for breaking point 8783 SkipBlanks, // skip leading blanks 8784 } 8785 Phase phase = Phase.SkipBlanks; // don't skip blanks on first line 8786 8787 if (!iter.setup(ctx.fs, 0, 0, str, FONSBitmapFlag.Optional)) return 0; 8788 prevIter = iter; 8789 while (iter.next(q)) { 8790 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8791 if (!nvg__allocTextAtlas(ctx)) break; // no memory 8792 iter = prevIter; 8793 iter.next(q); // try again 8794 if (iter.prevGlyphIndex < 0) { 8795 // still can not find glyph, try replacement 8796 iter = prevIter; 8797 if (!iter.getDummyChar(q)) break; 8798 } 8799 } 8800 prevIter = iter; 8801 switch (iter.codepoint) { 8802 case 9: // \t 8803 case 11: // \v 8804 case 12: // \f 8805 case 32: // space 8806 case 0x00a0: // NBSP 8807 type = NVGcodepointType.Space; 8808 break; 8809 case 10: // \n 8810 type = (pcodepoint == 13 ? NVGcodepointType.Space : NVGcodepointType.NewLine); 8811 break; 8812 case 13: // \r 8813 type = (pcodepoint == 10 ? NVGcodepointType.Space : NVGcodepointType.NewLine); 8814 break; 8815 case 0x0085: // NEL 8816 case 0x2028: // Line Separator 8817 case 0x2029: // Paragraph Separator 8818 type = NVGcodepointType.NewLine; 8819 break; 8820 default: 8821 type = NVGcodepointType.Char; 8822 break; 8823 } 8824 if (phase == Phase.SkipBlanks) { 8825 // fix row start 8826 rowStart = cast(int)(iter.stringp-str.ptr); 8827 rowEnd = rowStart; 8828 rowStartX = iter.x; 8829 rowWidth = iter.nextx-rowStartX; // q.x1-rowStartX; 8830 rowMinX = q.x0-rowStartX; 8831 rowMaxX = q.x1-rowStartX; 8832 wordStart = rowStart; 8833 wordStartX = iter.x; 8834 wordMinX = q.x0-rowStartX; 8835 breakEnd = rowStart; 8836 breakWidth = 0.0; 8837 breakMaxX = 0.0; 8838 if (type == NVGcodepointType.Space) continue; 8839 phase = Phase.Normal; 8840 } 8841 8842 if (type == NVGcodepointType.NewLine) { 8843 // always handle new lines 8844 NVGTextRow!T row; 8845 row.string = str; 8846 row.start = rowStart; 8847 row.end = rowEnd; 8848 row.width = rowWidth*invscale; 8849 row.minx = rowMinX*invscale; 8850 row.maxx = rowMaxX*invscale; 8851 ++nrows; 8852 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8853 phase = Phase.SkipBlanks; 8854 } else { 8855 float nextWidth = iter.nextx-rowStartX; 8856 // track last non-white space character 8857 if (type == NVGcodepointType.Char) { 8858 rowEnd = cast(int)(iter.nextp-str.ptr); 8859 rowWidth = iter.nextx-rowStartX; 8860 rowMaxX = q.x1-rowStartX; 8861 } 8862 // track last end of a word 8863 if (ptype == NVGcodepointType.Char && type == NVGcodepointType.Space) { 8864 breakEnd = cast(int)(iter.stringp-str.ptr); 8865 breakWidth = rowWidth; 8866 breakMaxX = rowMaxX; 8867 } 8868 // track last beginning of a word 8869 if (ptype == NVGcodepointType.Space && type == NVGcodepointType.Char) { 8870 wordStart = cast(int)(iter.stringp-str.ptr); 8871 wordStartX = iter.x; 8872 wordMinX = q.x0-rowStartX; 8873 } 8874 // break to new line when a character is beyond break width 8875 if (type == NVGcodepointType.Char && nextWidth > breakRowWidth) { 8876 // the run length is too long, need to break to new line 8877 NVGTextRow!T row; 8878 row.string = str; 8879 if (breakEnd == rowStart) { 8880 // the current word is longer than the row length, just break it from here 8881 row.start = rowStart; 8882 row.end = cast(int)(iter.stringp-str.ptr); 8883 row.width = rowWidth*invscale; 8884 row.minx = rowMinX*invscale; 8885 row.maxx = rowMaxX*invscale; 8886 ++nrows; 8887 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8888 rowStartX = iter.x; 8889 rowStart = cast(int)(iter.stringp-str.ptr); 8890 rowEnd = cast(int)(iter.nextp-str.ptr); 8891 rowWidth = iter.nextx-rowStartX; 8892 rowMinX = q.x0-rowStartX; 8893 rowMaxX = q.x1-rowStartX; 8894 wordStart = rowStart; 8895 wordStartX = iter.x; 8896 wordMinX = q.x0-rowStartX; 8897 } else { 8898 // break the line from the end of the last word, and start new line from the beginning of the new 8899 //{ import core.stdc.stdio : printf; printf("rowStart=%u; rowEnd=%u; breakEnd=%u; len=%u\n", rowStart, rowEnd, breakEnd, cast(uint)str.length); } 8900 row.start = rowStart; 8901 row.end = breakEnd; 8902 row.width = breakWidth*invscale; 8903 row.minx = rowMinX*invscale; 8904 row.maxx = breakMaxX*invscale; 8905 ++nrows; 8906 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8907 rowStartX = wordStartX; 8908 rowStart = wordStart; 8909 rowEnd = cast(int)(iter.nextp-str.ptr); 8910 rowWidth = iter.nextx-rowStartX; 8911 rowMinX = wordMinX; 8912 rowMaxX = q.x1-rowStartX; 8913 // no change to the word start 8914 } 8915 // set null break point 8916 breakEnd = rowStart; 8917 breakWidth = 0.0; 8918 breakMaxX = 0.0; 8919 } 8920 } 8921 8922 pcodepoint = iter.codepoint; 8923 ptype = type; 8924 } 8925 8926 // break the line from the end of the last word, and start new line from the beginning of the new 8927 if (phase != Phase.SkipBlanks && rowStart < str.length) { 8928 //{ import core.stdc.stdio : printf; printf(" rowStart=%u; len=%u\n", rowStart, cast(uint)str.length); } 8929 NVGTextRow!T row; 8930 row.string = str; 8931 row.start = rowStart; 8932 row.end = cast(int)str.length; 8933 row.width = rowWidth*invscale; 8934 row.minx = rowMinX*invscale; 8935 row.maxx = rowMaxX*invscale; 8936 ++nrows; 8937 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8938 } 8939 8940 return nrows; 8941 } 8942 8943 /** Returns iterator which you can use to calculate text bounds and advancement. 8944 * This is usable when you need to do some text layouting with wrapping, to avoid 8945 * guesswork ("will advancement for this space stay the same?"), and Schlemiel's 8946 * algorithm. Note that you can copy the returned struct to save iterator state. 8947 * 8948 * You can check if iterator is valid with [valid] property, put new chars with 8949 * [put] method, get current advance with [advance] property, and current 8950 * bounds with `getBounds(ref float[4] bounds)` method. 8951 * 8952 * $(WARNING Don't change font parameters while iterating! Or use [restoreFont] method.) 8953 * 8954 * Group: text_api 8955 */ 8956 public struct TextBoundsIterator { 8957 private: 8958 NVGContext ctx; 8959 FONSTextBoundsIterator fsiter; // fontstash iterator 8960 float scale, invscale, xscaled, yscaled; 8961 // font settings 8962 float fsSize, fsSpacing, fsBlur; 8963 int fsFontId; 8964 NVGTextAlign fsAlign; 8965 8966 public: 8967 /// Setups iteration. Takes current font parameters from the given NanoVega context. 8968 this (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { reset(actx, ax, ay); } 8969 8970 /// Resets iteration. Takes current font parameters from the given NanoVega context. 8971 void reset (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { 8972 fsiter = fsiter.init; 8973 this = this.init; 8974 if (actx is null) return; 8975 NVGstate* state = nvg__getState(actx); 8976 if (state is null) return; 8977 if (state.fontId == FONS_INVALID) { ctx = null; return; } 8978 8979 ctx = actx; 8980 scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8981 invscale = 1.0f/scale; 8982 8983 fsSize = state.fontSize*scale; 8984 fsSpacing = state.letterSpacing*scale; 8985 fsBlur = state.fontBlur*scale; 8986 fsAlign = state.textAlign; 8987 fsFontId = state.fontId; 8988 restoreFont(); 8989 8990 xscaled = ax*scale; 8991 yscaled = ay*scale; 8992 fsiter.reset(ctx.fs, xscaled, yscaled); 8993 } 8994 8995 /// Restart iteration. Will not restore font. 8996 void restart () nothrow @trusted @nogc { 8997 if (ctx !is null) fsiter.reset(ctx.fs, xscaled, yscaled); 8998 } 8999 9000 /// Restore font settings for the context. 9001 void restoreFont () nothrow @trusted @nogc { 9002 if (ctx !is null) { 9003 ctx.fs.size = fsSize; 9004 ctx.fs.spacing = fsSpacing; 9005 ctx.fs.blur = fsBlur; 9006 ctx.fs.textAlign = fsAlign; 9007 ctx.fs.fontId = fsFontId; 9008 } 9009 } 9010 9011 /// Is this iterator valid? 9012 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null); } 9013 9014 /// Add chars. 9015 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { pragma(inline, true); if (ctx !is null) fsiter.put(str[]); } 9016 9017 /// Returns current advance 9018 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null ? fsiter.advance*invscale : 0); } 9019 9020 /// Returns current text bounds. 9021 void getBounds (ref float[4] bounds) nothrow @trusted @nogc { 9022 if (ctx !is null) { 9023 fsiter.getBounds(bounds); 9024 ctx.fs.getLineBounds(yscaled, &bounds[1], &bounds[3]); 9025 bounds[0] *= invscale; 9026 bounds[1] *= invscale; 9027 bounds[2] *= invscale; 9028 bounds[3] *= invscale; 9029 } else { 9030 bounds[] = 0; 9031 } 9032 } 9033 9034 /// Returns current horizontal text bounds. 9035 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc { 9036 if (ctx !is null) { 9037 fsiter.getHBounds(xmin, xmax); 9038 xmin *= invscale; 9039 xmax *= invscale; 9040 } 9041 } 9042 9043 /// Returns current vertical text bounds. 9044 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc { 9045 if (ctx !is null) { 9046 //fsiter.getVBounds(ymin, ymax); 9047 ctx.fs.getLineBounds(yscaled, &ymin, &ymax); 9048 ymin *= invscale; 9049 ymax *= invscale; 9050 } 9051 } 9052 } 9053 9054 /// Returns font line height (without line spacing), measured in local coordinate space. 9055 /// Group: text_api 9056 public float textFontHeight (NVGContext ctx) nothrow @trusted @nogc { 9057 float res = void; 9058 ctx.textMetrics(null, null, &res); 9059 return res; 9060 } 9061 9062 /// Returns font ascender (positive), measured in local coordinate space. 9063 /// Group: text_api 9064 public float textFontAscender (NVGContext ctx) nothrow @trusted @nogc { 9065 float res = void; 9066 ctx.textMetrics(&res, null, null); 9067 return res; 9068 } 9069 9070 /// Returns font descender (negative), measured in local coordinate space. 9071 /// Group: text_api 9072 public float textFontDescender (NVGContext ctx) nothrow @trusted @nogc { 9073 float res = void; 9074 ctx.textMetrics(null, &res, null); 9075 return res; 9076 } 9077 9078 /** Measures the specified text string. Returns horizontal and vertical sizes of the measured text. 9079 * Measured values are returned in local coordinate space. 9080 * 9081 * Group: text_api 9082 */ 9083 public void textExtents(T) (NVGContext ctx, const(T)[] str, float *w, float *h) nothrow @trusted @nogc if (isAnyCharType!T) { 9084 float[4] bnd = void; 9085 ctx.textBounds(0, 0, str, bnd[]); 9086 if (!ctx.fs.getFontAA(nvg__getState(ctx).fontId)) { 9087 if (w !is null) *w = nvg__lrintf(bnd.ptr[2]-bnd.ptr[0]); 9088 if (h !is null) *h = nvg__lrintf(bnd.ptr[3]-bnd.ptr[1]); 9089 } else { 9090 if (w !is null) *w = bnd.ptr[2]-bnd.ptr[0]; 9091 if (h !is null) *h = bnd.ptr[3]-bnd.ptr[1]; 9092 } 9093 } 9094 9095 /** Measures the specified text string. Returns horizontal size of the measured text. 9096 * Measured values are returned in local coordinate space. 9097 * 9098 * Group: text_api 9099 */ 9100 public float textWidth(T) (NVGContext ctx, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 9101 float w = void; 9102 ctx.textExtents(str, &w, null); 9103 return w; 9104 } 9105 9106 /** Measures the specified text string. Parameter bounds should be a float[4], 9107 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax] 9108 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn). 9109 * Measured values are returned in local coordinate space. 9110 * 9111 * Group: text_api 9112 */ 9113 public float textBounds(T) (NVGContext ctx, float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc 9114 if (isAnyCharType!T) 9115 { 9116 NVGstate* state = nvg__getState(ctx); 9117 9118 if (state.fontId == FONS_INVALID) { 9119 bounds[] = 0; 9120 return 0; 9121 } 9122 9123 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9124 ctx.fs.size = state.fontSize*scale; 9125 ctx.fs.spacing = state.letterSpacing*scale; 9126 ctx.fs.blur = state.fontBlur*scale; 9127 ctx.fs.textAlign = state.textAlign; 9128 ctx.fs.fontId = state.fontId; 9129 9130 float[4] b = void; 9131 immutable float width = ctx.fs.getTextBounds(x*scale, y*scale, str, b[]); 9132 immutable float invscale = 1.0f/scale; 9133 if (bounds.length) { 9134 // use line bounds for height 9135 ctx.fs.getLineBounds(y*scale, b.ptr+1, b.ptr+3); 9136 if (bounds.length > 0) bounds.ptr[0] = b.ptr[0]*invscale; 9137 if (bounds.length > 1) bounds.ptr[1] = b.ptr[1]*invscale; 9138 if (bounds.length > 2) bounds.ptr[2] = b.ptr[2]*invscale; 9139 if (bounds.length > 3) bounds.ptr[3] = b.ptr[3]*invscale; 9140 } 9141 return width*invscale; 9142 } 9143 9144 /// Ditto. 9145 public void textBoxBounds(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str, float[] bounds) if (isAnyCharType!T) { 9146 NVGstate* state = nvg__getState(ctx); 9147 NVGTextRow!T[2] rows; 9148 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9149 float invscale = 1.0f/scale; 9150 float lineh = 0, rminy = 0, rmaxy = 0; 9151 float minx, miny, maxx, maxy; 9152 9153 if (state.fontId == FONS_INVALID) { 9154 bounds[] = 0; 9155 return; 9156 } 9157 9158 auto oldAlign = state.textAlign; 9159 scope(exit) state.textAlign = oldAlign; 9160 auto halign = state.textAlign.horizontal; 9161 9162 ctx.textMetrics(null, null, &lineh); 9163 state.textAlign.horizontal = NVGTextAlign.H.Left; 9164 9165 minx = maxx = x; 9166 miny = maxy = y; 9167 9168 ctx.fs.size = state.fontSize*scale; 9169 ctx.fs.spacing = state.letterSpacing*scale; 9170 ctx.fs.blur = state.fontBlur*scale; 9171 ctx.fs.textAlign = state.textAlign; 9172 ctx.fs.fontId = state.fontId; 9173 ctx.fs.getLineBounds(0, &rminy, &rmaxy); 9174 rminy *= invscale; 9175 rmaxy *= invscale; 9176 9177 for (;;) { 9178 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]); 9179 if (rres.length == 0) break; 9180 foreach (ref row; rres) { 9181 float rminx, rmaxx, dx = 0; 9182 // horizontal bounds 9183 final switch (halign) { 9184 case NVGTextAlign.H.Left: dx = 0; break; 9185 case NVGTextAlign.H.Center: dx = breakRowWidth*0.5f-row.width*0.5f; break; 9186 case NVGTextAlign.H.Right: dx = breakRowWidth-row.width; break; 9187 } 9188 rminx = x+row.minx+dx; 9189 rmaxx = x+row.maxx+dx; 9190 minx = nvg__min(minx, rminx); 9191 maxx = nvg__max(maxx, rmaxx); 9192 // vertical bounds 9193 miny = nvg__min(miny, y+rminy); 9194 maxy = nvg__max(maxy, y+rmaxy); 9195 y += lineh*state.lineHeight; 9196 } 9197 str = rres[$-1].rest; 9198 } 9199 9200 if (bounds.length) { 9201 if (bounds.length > 0) bounds.ptr[0] = minx; 9202 if (bounds.length > 1) bounds.ptr[1] = miny; 9203 if (bounds.length > 2) bounds.ptr[2] = maxx; 9204 if (bounds.length > 3) bounds.ptr[3] = maxy; 9205 } 9206 } 9207 9208 /// Returns the vertical metrics based on the current text style. Measured values are returned in local coordinate space. 9209 /// Group: text_api 9210 public void textMetrics (NVGContext ctx, float* ascender, float* descender, float* lineh) nothrow @trusted @nogc { 9211 NVGstate* state = nvg__getState(ctx); 9212 9213 if (state.fontId == FONS_INVALID) { 9214 if (ascender !is null) *ascender *= 0; 9215 if (descender !is null) *descender *= 0; 9216 if (lineh !is null) *lineh *= 0; 9217 return; 9218 } 9219 9220 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9221 immutable float invscale = 1.0f/scale; 9222 9223 ctx.fs.size = state.fontSize*scale; 9224 ctx.fs.spacing = state.letterSpacing*scale; 9225 ctx.fs.blur = state.fontBlur*scale; 9226 ctx.fs.textAlign = state.textAlign; 9227 ctx.fs.fontId = state.fontId; 9228 9229 ctx.fs.getVertMetrics(ascender, descender, lineh); 9230 if (ascender !is null) *ascender *= invscale; 9231 if (descender !is null) *descender *= invscale; 9232 if (lineh !is null) *lineh *= invscale; 9233 } 9234 9235 9236 // ////////////////////////////////////////////////////////////////////////// // 9237 // fontstash 9238 // ////////////////////////////////////////////////////////////////////////// // 9239 import core.stdc.stdlib : malloc, realloc, free; 9240 import core.stdc.string : memset, memcpy, strncpy, strcmp, strlen; 9241 import core.stdc.stdio : FILE, fopen, fclose, fseek, ftell, fread, SEEK_END, SEEK_SET; 9242 9243 public: 9244 // welcome to version hell! 9245 version(nanovg_force_stb_ttf) { 9246 } else { 9247 version(nanovg_force_detect) {} else version(nanovg_use_freetype) { version = nanovg_use_freetype_ii; } 9248 } 9249 version(nanovg_ignore_iv_stb_ttf) enum nanovg_ignore_iv_stb_ttf = true; else enum nanovg_ignore_iv_stb_ttf = false; 9250 //version(nanovg_ignore_mono); 9251 9252 version(nanovg_force_stb_ttf) { 9253 private enum NanoVegaForceFreeType = false; 9254 } else { 9255 version (nanovg_builtin_freetype_bindings) { 9256 version(Posix) { 9257 private enum NanoVegaForceFreeType = true; 9258 } else { 9259 private enum NanoVegaForceFreeType = false; 9260 } 9261 } else { 9262 version(Posix) { 9263 private enum NanoVegaForceFreeType = true; 9264 } else { 9265 private enum NanoVegaForceFreeType = false; 9266 } 9267 } 9268 } 9269 9270 version(nanovg_use_freetype_ii) { 9271 enum NanoVegaIsUsingSTBTTF = false; 9272 //pragma(msg, "iv.freetype: forced"); 9273 } else { 9274 static if (NanoVegaForceFreeType) { 9275 enum NanoVegaIsUsingSTBTTF = false; 9276 } else { 9277 static if (!nanovg_ignore_iv_stb_ttf && __traits(compiles, { import iv.stb.ttf; })) { 9278 import iv.stb.ttf; 9279 enum NanoVegaIsUsingSTBTTF = true; 9280 version(nanovg_report_stb_ttf) pragma(msg, "iv.stb.ttf"); 9281 } else static if (__traits(compiles, { import arsd.ttf; })) { 9282 import arsd.ttf; 9283 enum NanoVegaIsUsingSTBTTF = true; 9284 version(nanovg_report_stb_ttf) pragma(msg, "arsd.ttf"); 9285 } else static if (__traits(compiles, { import stb_truetype; })) { 9286 import stb_truetype; 9287 enum NanoVegaIsUsingSTBTTF = true; 9288 version(nanovg_report_stb_ttf) pragma(msg, "stb_truetype"); 9289 } else static if (__traits(compiles, { import iv.freetype; })) { 9290 version (nanovg_builtin_freetype_bindings) { 9291 enum NanoVegaIsUsingSTBTTF = false; 9292 version = nanovg_builtin_freetype_bindings; 9293 } else { 9294 import iv.freetype; 9295 enum NanoVegaIsUsingSTBTTF = false; 9296 } 9297 version(nanovg_report_stb_ttf) pragma(msg, "freetype"); 9298 } else { 9299 static assert(0, "no stb_ttf/iv.freetype found!"); 9300 } 9301 } 9302 } 9303 9304 9305 // ////////////////////////////////////////////////////////////////////////// // 9306 //version = nanovg_ft_mono; 9307 9308 /// Invald font id. 9309 /// Group: font_stash 9310 public enum FONS_INVALID = -1; 9311 9312 public enum FONSBitmapFlag : uint { 9313 Required = 0, 9314 Optional = 1, 9315 } 9316 9317 public enum FONSError : int { 9318 NoError = 0, 9319 AtlasFull = 1, // Font atlas is full. 9320 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. 9321 StatesOverflow = 3, // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES. 9322 StatesUnderflow = 4, // Trying to pop too many states fonsPopState(). 9323 } 9324 9325 /// Initial parameters for new FontStash. 9326 /// Group: font_stash 9327 public struct FONSParams { 9328 enum Flag : uint { 9329 ZeroTopLeft = 0U, // default 9330 ZeroBottomLeft = 1U, 9331 } 9332 int width, height; 9333 Flag flags = Flag.ZeroTopLeft; 9334 void* userPtr; 9335 bool function (void* uptr, int width, int height) nothrow @trusted @nogc renderCreate; 9336 int function (void* uptr, int width, int height) nothrow @trusted @nogc renderResize; 9337 void function (void* uptr, int* rect, const(ubyte)* data) nothrow @trusted @nogc renderUpdate; 9338 void function (void* uptr) nothrow @trusted @nogc renderDelete; 9339 @property bool isZeroTopLeft () const pure nothrow @trusted @nogc { pragma(inline, true); return ((flags&Flag.ZeroBottomLeft) == 0); } 9340 } 9341 9342 //TODO: document this 9343 public struct FONSQuad { 9344 float x0=0, y0=0, s0=0, t0=0; 9345 float x1=0, y1=0, s1=0, t1=0; 9346 } 9347 9348 //TODO: document this 9349 public struct FONSTextIter(CT) if (isAnyCharType!CT) { 9350 alias CharType = CT; 9351 float x=0, y=0, nextx=0, nexty=0, scale=0, spacing=0; 9352 uint codepoint; 9353 short isize, iblur; 9354 FONSContext stash; 9355 FONSfont* font; 9356 int prevGlyphIndex; 9357 const(CT)* s; // string 9358 const(CT)* n; // next 9359 const(CT)* e; // end 9360 FONSBitmapFlag bitmapOption; 9361 static if (is(CT == char)) { 9362 uint utf8state; 9363 } 9364 9365 this (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { setup(astash, ax, ay, astr, abitmapOption); } 9366 ~this () nothrow @trusted @nogc { pragma(inline, true); static if (is(CT == char)) utf8state = 0; s = n = e = null; } 9367 9368 @property const(CT)* stringp () const pure nothrow @trusted @nogc { pragma(inline, true); return s; } 9369 @property const(CT)* nextp () const pure nothrow @trusted @nogc { pragma(inline, true); return n; } 9370 @property const(CT)* endp () const pure nothrow @trusted @nogc { pragma(inline, true); return e; } 9371 9372 bool setup (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { 9373 import core.stdc.string : memset; 9374 9375 memset(&this, 0, this.sizeof); 9376 if (astash is null) return false; 9377 9378 FONSstate* state = astash.getState; 9379 9380 if (state.font < 0 || state.font >= astash.nfonts) return false; 9381 font = astash.fonts[state.font]; 9382 if (font is null || font.fdata is null) return false; 9383 9384 isize = cast(short)(state.size*10.0f); 9385 iblur = cast(short)state.blur; 9386 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 9387 9388 // align horizontally 9389 if (state.talign.left) { 9390 // empty 9391 } else if (state.talign.right) { 9392 immutable float width = astash.getTextBounds(ax, ay, astr, null); 9393 ax -= width; 9394 } else if (state.talign.center) { 9395 immutable float width = astash.getTextBounds(ax, ay, astr, null); 9396 ax -= width*0.5f; 9397 } 9398 9399 // align vertically 9400 ay += astash.getVertAlign(font, state.talign, isize); 9401 9402 x = nextx = ax; 9403 y = nexty = ay; 9404 spacing = state.spacing; 9405 9406 if (astr.ptr is null) { 9407 static if (is(CharType == char)) astr = ""; 9408 else static if (is(CharType == wchar)) astr = ""w; 9409 else static if (is(CharType == dchar)) astr = ""d; 9410 else static assert(0, "wtf?!"); 9411 } 9412 s = astr.ptr; 9413 n = astr.ptr; 9414 e = astr.ptr+astr.length; 9415 9416 codepoint = 0; 9417 prevGlyphIndex = -1; 9418 bitmapOption = abitmapOption; 9419 stash = astash; 9420 9421 return true; 9422 } 9423 9424 bool getDummyChar (ref FONSQuad quad) nothrow @trusted @nogc { 9425 if (stash is null || font is null) return false; 9426 // get glyph and quad 9427 x = nextx; 9428 y = nexty; 9429 FONSglyph* glyph = stash.getGlyph(font, 0xFFFD, isize, iblur, bitmapOption); 9430 if (glyph !is null) { 9431 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9432 prevGlyphIndex = glyph.index; 9433 return true; 9434 } else { 9435 prevGlyphIndex = -1; 9436 return false; 9437 } 9438 } 9439 9440 bool next (ref FONSQuad quad) nothrow @trusted @nogc { 9441 if (stash is null || font is null) return false; 9442 FONSglyph* glyph = null; 9443 static if (is(CharType == char)) { 9444 const(char)* str = this.n; 9445 this.s = this.n; 9446 if (str is this.e) return false; 9447 const(char)* e = this.e; 9448 for (; str !is e; ++str) { 9449 /*if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;*/ 9450 mixin(DecUtfMixin!("this.utf8state", "this.codepoint", "*cast(const(ubyte)*)str")); 9451 if (utf8state) continue; 9452 ++str; // 'cause we'll break anyway 9453 // get glyph and quad 9454 x = nextx; 9455 y = nexty; 9456 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption); 9457 if (glyph !is null) { 9458 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9459 prevGlyphIndex = glyph.index; 9460 } else { 9461 prevGlyphIndex = -1; 9462 } 9463 break; 9464 } 9465 this.n = str; 9466 } else { 9467 const(CharType)* str = this.n; 9468 this.s = this.n; 9469 if (str is this.e) return false; 9470 codepoint = cast(uint)(*str++); 9471 if (codepoint > dchar.max) codepoint = 0xFFFD; 9472 // get glyph and quad 9473 x = nextx; 9474 y = nexty; 9475 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption); 9476 if (glyph !is null) { 9477 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9478 prevGlyphIndex = glyph.index; 9479 } else { 9480 prevGlyphIndex = -1; 9481 } 9482 this.n = str; 9483 } 9484 return true; 9485 } 9486 } 9487 9488 9489 // ////////////////////////////////////////////////////////////////////////// // 9490 //static if (!HasAST) version = nanovg_use_freetype_ii_x; 9491 9492 /*version(nanovg_use_freetype_ii_x)*/ static if (!NanoVegaIsUsingSTBTTF) { 9493 version(nanovg_builtin_freetype_bindings) { 9494 pragma(lib, "freetype"); 9495 private extern(C) nothrow @trusted @nogc { 9496 private import core.stdc.config : c_long, c_ulong; 9497 alias FT_Pos = c_long; 9498 // config/ftconfig.h 9499 alias FT_Int16 = short; 9500 alias FT_UInt16 = ushort; 9501 alias FT_Int32 = int; 9502 alias FT_UInt32 = uint; 9503 alias FT_Fast = int; 9504 alias FT_UFast = uint; 9505 alias FT_Int64 = long; 9506 alias FT_Uint64 = ulong; 9507 // fttypes.h 9508 alias FT_Bool = ubyte; 9509 alias FT_FWord = short; 9510 alias FT_UFWord = ushort; 9511 alias FT_Char = char; 9512 alias FT_Byte = ubyte; 9513 alias FT_Bytes = FT_Byte*; 9514 alias FT_Tag = FT_UInt32; 9515 alias FT_String = char; 9516 alias FT_Short = short; 9517 alias FT_UShort = ushort; 9518 alias FT_Int = int; 9519 alias FT_UInt = uint; 9520 alias FT_Long = c_long; 9521 alias FT_ULong = c_ulong; 9522 alias FT_F2Dot14 = short; 9523 alias FT_F26Dot6 = c_long; 9524 alias FT_Fixed = c_long; 9525 alias FT_Error = int; 9526 alias FT_Pointer = void*; 9527 alias FT_Offset = usize; 9528 alias FT_PtrDist = ptrdiff_t; 9529 9530 struct FT_UnitVector { 9531 FT_F2Dot14 x; 9532 FT_F2Dot14 y; 9533 } 9534 9535 struct FT_Matrix { 9536 FT_Fixed xx, xy; 9537 FT_Fixed yx, yy; 9538 } 9539 9540 struct FT_Data { 9541 const(FT_Byte)* pointer; 9542 FT_Int length; 9543 } 9544 alias FT_Face = FT_FaceRec*; 9545 struct FT_FaceRec { 9546 FT_Long num_faces; 9547 FT_Long face_index; 9548 FT_Long face_flags; 9549 FT_Long style_flags; 9550 FT_Long num_glyphs; 9551 FT_String* family_name; 9552 FT_String* style_name; 9553 FT_Int num_fixed_sizes; 9554 FT_Bitmap_Size* available_sizes; 9555 FT_Int num_charmaps; 9556 FT_CharMap* charmaps; 9557 FT_Generic generic; 9558 FT_BBox bbox; 9559 FT_UShort units_per_EM; 9560 FT_Short ascender; 9561 FT_Short descender; 9562 FT_Short height; 9563 FT_Short max_advance_width; 9564 FT_Short max_advance_height; 9565 FT_Short underline_position; 9566 FT_Short underline_thickness; 9567 FT_GlyphSlot glyph; 9568 FT_Size size; 9569 FT_CharMap charmap; 9570 FT_Driver driver; 9571 FT_Memory memory; 9572 FT_Stream stream; 9573 FT_ListRec sizes_list; 9574 FT_Generic autohint; 9575 void* extensions; 9576 FT_Face_Internal internal; 9577 } 9578 struct FT_Bitmap_Size { 9579 FT_Short height; 9580 FT_Short width; 9581 FT_Pos size; 9582 FT_Pos x_ppem; 9583 FT_Pos y_ppem; 9584 } 9585 alias FT_CharMap = FT_CharMapRec*; 9586 struct FT_CharMapRec { 9587 FT_Face face; 9588 FT_Encoding encoding; 9589 FT_UShort platform_id; 9590 FT_UShort encoding_id; 9591 } 9592 extern(C) nothrow @nogc { alias FT_Generic_Finalizer = void function (void* object); } 9593 struct FT_Generic { 9594 void* data; 9595 FT_Generic_Finalizer finalizer; 9596 } 9597 struct FT_Vector { 9598 FT_Pos x; 9599 FT_Pos y; 9600 } 9601 struct FT_BBox { 9602 FT_Pos xMin, yMin; 9603 FT_Pos xMax, yMax; 9604 } 9605 alias FT_Pixel_Mode = int; 9606 enum { 9607 FT_PIXEL_MODE_NONE = 0, 9608 FT_PIXEL_MODE_MONO, 9609 FT_PIXEL_MODE_GRAY, 9610 FT_PIXEL_MODE_GRAY2, 9611 FT_PIXEL_MODE_GRAY4, 9612 FT_PIXEL_MODE_LCD, 9613 FT_PIXEL_MODE_LCD_V, 9614 FT_PIXEL_MODE_MAX 9615 } 9616 struct FT_Bitmap { 9617 uint rows; 9618 uint width; 9619 int pitch; 9620 ubyte* buffer; 9621 ushort num_grays; 9622 ubyte pixel_mode; 9623 ubyte palette_mode; 9624 void* palette; 9625 } 9626 struct FT_Outline { 9627 short n_contours; 9628 short n_points; 9629 FT_Vector* points; 9630 byte* tags; 9631 short* contours; 9632 int flags; 9633 } 9634 alias FT_GlyphSlot = FT_GlyphSlotRec*; 9635 struct FT_GlyphSlotRec { 9636 FT_Library library; 9637 FT_Face face; 9638 FT_GlyphSlot next; 9639 FT_UInt reserved; 9640 FT_Generic generic; 9641 FT_Glyph_Metrics metrics; 9642 FT_Fixed linearHoriAdvance; 9643 FT_Fixed linearVertAdvance; 9644 FT_Vector advance; 9645 FT_Glyph_Format format; 9646 FT_Bitmap bitmap; 9647 FT_Int bitmap_left; 9648 FT_Int bitmap_top; 9649 FT_Outline outline; 9650 FT_UInt num_subglyphs; 9651 FT_SubGlyph subglyphs; 9652 void* control_data; 9653 c_long control_len; 9654 FT_Pos lsb_delta; 9655 FT_Pos rsb_delta; 9656 void* other; 9657 FT_Slot_Internal internal; 9658 } 9659 alias FT_Size = FT_SizeRec*; 9660 struct FT_SizeRec { 9661 FT_Face face; 9662 FT_Generic generic; 9663 FT_Size_Metrics metrics; 9664 FT_Size_Internal internal; 9665 } 9666 alias FT_Encoding = FT_Tag; 9667 alias FT_Face_Internal = void*; 9668 alias FT_Driver = void*; 9669 alias FT_Memory = void*; 9670 alias FT_Stream = void*; 9671 alias FT_Library = void*; 9672 alias FT_SubGlyph = void*; 9673 alias FT_Slot_Internal = void*; 9674 alias FT_Size_Internal = void*; 9675 alias FT_ListNode = FT_ListNodeRec*; 9676 alias FT_List = FT_ListRec*; 9677 struct FT_ListNodeRec { 9678 FT_ListNode prev; 9679 FT_ListNode next; 9680 void* data; 9681 } 9682 struct FT_ListRec { 9683 FT_ListNode head; 9684 FT_ListNode tail; 9685 } 9686 struct FT_Glyph_Metrics { 9687 FT_Pos width; 9688 FT_Pos height; 9689 FT_Pos horiBearingX; 9690 FT_Pos horiBearingY; 9691 FT_Pos horiAdvance; 9692 FT_Pos vertBearingX; 9693 FT_Pos vertBearingY; 9694 FT_Pos vertAdvance; 9695 } 9696 alias FT_Glyph_Format = FT_Tag; 9697 FT_Tag FT_MAKE_TAG (char x1, char x2, char x3, char x4) pure nothrow @safe @nogc { 9698 pragma(inline, true); 9699 return cast(FT_UInt32)((x1<<24)|(x2<<16)|(x3<<8)|x4); 9700 } 9701 enum : FT_Tag { 9702 FT_GLYPH_FORMAT_NONE = 0, 9703 FT_GLYPH_FORMAT_COMPOSITE = FT_MAKE_TAG('c','o','m','p'), 9704 FT_GLYPH_FORMAT_BITMAP = FT_MAKE_TAG('b','i','t','s'), 9705 FT_GLYPH_FORMAT_OUTLINE = FT_MAKE_TAG('o','u','t','l'), 9706 FT_GLYPH_FORMAT_PLOTTER = FT_MAKE_TAG('p','l','o','t'), 9707 } 9708 struct FT_Size_Metrics { 9709 FT_UShort x_ppem; 9710 FT_UShort y_ppem; 9711 9712 FT_Fixed x_scale; 9713 FT_Fixed y_scale; 9714 9715 FT_Pos ascender; 9716 FT_Pos descender; 9717 FT_Pos height; 9718 FT_Pos max_advance; 9719 } 9720 enum FT_LOAD_DEFAULT = 0x0U; 9721 enum FT_LOAD_NO_SCALE = 1U<<0; 9722 enum FT_LOAD_NO_HINTING = 1U<<1; 9723 enum FT_LOAD_RENDER = 1U<<2; 9724 enum FT_LOAD_NO_BITMAP = 1U<<3; 9725 enum FT_LOAD_VERTICAL_LAYOUT = 1U<<4; 9726 enum FT_LOAD_FORCE_AUTOHINT = 1U<<5; 9727 enum FT_LOAD_CROP_BITMAP = 1U<<6; 9728 enum FT_LOAD_PEDANTIC = 1U<<7; 9729 enum FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1U<<9; 9730 enum FT_LOAD_NO_RECURSE = 1U<<10; 9731 enum FT_LOAD_IGNORE_TRANSFORM = 1U<<11; 9732 enum FT_LOAD_MONOCHROME = 1U<<12; 9733 enum FT_LOAD_LINEAR_DESIGN = 1U<<13; 9734 enum FT_LOAD_NO_AUTOHINT = 1U<<15; 9735 enum FT_LOAD_COLOR = 1U<<20; 9736 enum FT_LOAD_COMPUTE_METRICS = 1U<<21; 9737 enum FT_FACE_FLAG_KERNING = 1U<<6; 9738 alias FT_Kerning_Mode = int; 9739 enum /*FT_Kerning_Mode*/ { 9740 FT_KERNING_DEFAULT = 0, 9741 FT_KERNING_UNFITTED, 9742 FT_KERNING_UNSCALED 9743 } 9744 extern(C) nothrow @nogc { 9745 alias FT_Outline_MoveToFunc = int function (const(FT_Vector)*, void*); 9746 alias FT_Outline_LineToFunc = int function (const(FT_Vector)*, void*); 9747 alias FT_Outline_ConicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, void*); 9748 alias FT_Outline_CubicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, const(FT_Vector)*, void*); 9749 } 9750 struct FT_Outline_Funcs { 9751 FT_Outline_MoveToFunc move_to; 9752 FT_Outline_LineToFunc line_to; 9753 FT_Outline_ConicToFunc conic_to; 9754 FT_Outline_CubicToFunc cubic_to; 9755 int shift; 9756 FT_Pos delta; 9757 } 9758 9759 FT_Error FT_Init_FreeType (FT_Library*); 9760 FT_Error FT_New_Memory_Face (FT_Library, const(FT_Byte)*, FT_Long, FT_Long, FT_Face*); 9761 FT_UInt FT_Get_Char_Index (FT_Face, FT_ULong); 9762 FT_Error FT_Set_Pixel_Sizes (FT_Face, FT_UInt, FT_UInt); 9763 FT_Error FT_Load_Glyph (FT_Face, FT_UInt, FT_Int32); 9764 FT_Error FT_Get_Advance (FT_Face, FT_UInt, FT_Int32, FT_Fixed*); 9765 FT_Error FT_Get_Kerning (FT_Face, FT_UInt, FT_UInt, FT_UInt, FT_Vector*); 9766 void FT_Outline_Get_CBox (const(FT_Outline)*, FT_BBox*); 9767 FT_Error FT_Outline_Decompose (FT_Outline*, const(FT_Outline_Funcs)*, void*); 9768 } 9769 } else version(bindbc) { 9770 import bindbc.freetype; 9771 alias FT_KERNING_DEFAULT = FT_Kerning_Mode.FT_KERNING_DEFAULT; 9772 alias FT_KERNING_UNFITTED = FT_Kerning_Mode.FT_KERNING_UNFITTED; 9773 alias FT_KERNING_UNSCALED = FT_Kerning_Mode.FT_KERNING_UNSCALED; 9774 } else { 9775 import iv.freetype; 9776 } 9777 9778 struct FONSttFontImpl { 9779 FT_Face font; 9780 bool mono; // no aa? 9781 } 9782 9783 __gshared FT_Library ftLibrary; 9784 9785 int fons__tt_init (FONSContext context) nothrow @trusted @nogc { 9786 FT_Error ftError; 9787 //FONS_NOTUSED(context); 9788 ftError = FT_Init_FreeType(&ftLibrary); 9789 return (ftError == 0); 9790 } 9791 9792 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc { 9793 font.mono = v; 9794 } 9795 9796 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc { 9797 return font.mono; 9798 } 9799 9800 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc { 9801 FT_Error ftError; 9802 //font.font.userdata = stash; 9803 ftError = FT_New_Memory_Face(ftLibrary, cast(const(FT_Byte)*)data, dataSize, 0, &font.font); 9804 return ftError == 0; 9805 } 9806 9807 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc { 9808 *ascent = font.font.ascender; 9809 *descent = font.font.descender; 9810 *lineGap = font.font.height-(*ascent - *descent); 9811 } 9812 9813 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc { 9814 return size/(font.font.ascender-font.font.descender); 9815 } 9816 9817 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc { 9818 return FT_Get_Char_Index(font.font, codepoint); 9819 } 9820 9821 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 { 9822 FT_Error ftError; 9823 FT_GlyphSlot ftGlyph; 9824 //version(nanovg_ignore_mono) enum exflags = 0; 9825 //else version(nanovg_ft_mono) enum exflags = FT_LOAD_MONOCHROME; else enum exflags = 0; 9826 uint exflags = (font.mono ? FT_LOAD_MONOCHROME : 0); 9827 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))); 9828 if (ftError) return 0; 9829 ftError = FT_Load_Glyph(font.font, glyph, FT_LOAD_RENDER|/*FT_LOAD_NO_AUTOHINT|*/exflags); 9830 if (ftError) return 0; 9831 ftError = FT_Get_Advance(font.font, glyph, FT_LOAD_NO_SCALE|/*FT_LOAD_NO_AUTOHINT|*/exflags, cast(FT_Fixed*)advance); 9832 if (ftError) return 0; 9833 ftGlyph = font.font.glyph; 9834 *lsb = cast(int)ftGlyph.metrics.horiBearingX; 9835 *x0 = ftGlyph.bitmap_left; 9836 *x1 = *x0+ftGlyph.bitmap.width; 9837 *y0 = -ftGlyph.bitmap_top; 9838 *y1 = *y0+ftGlyph.bitmap.rows; 9839 return 1; 9840 } 9841 9842 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc { 9843 FT_GlyphSlot ftGlyph = font.font.glyph; 9844 //FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap 9845 //version(nanovg_ignore_mono) enum RenderAA = true; 9846 //else version(nanovg_ft_mono) enum RenderAA = false; 9847 //else enum RenderAA = true; 9848 if (font.mono) { 9849 auto src = ftGlyph.bitmap.buffer; 9850 auto dst = output; 9851 auto spt = ftGlyph.bitmap.pitch; 9852 if (spt < 0) spt = -spt; 9853 foreach (int y; 0..ftGlyph.bitmap.rows) { 9854 ubyte count = 0, b = 0; 9855 auto s = src; 9856 auto d = dst; 9857 foreach (int x; 0..ftGlyph.bitmap.width) { 9858 if (count-- == 0) { count = 7; b = *s++; } else b <<= 1; 9859 *d++ = (b&0x80 ? 255 : 0); 9860 } 9861 src += spt; 9862 dst += outStride; 9863 } 9864 } else { 9865 auto src = ftGlyph.bitmap.buffer; 9866 auto dst = output; 9867 auto spt = ftGlyph.bitmap.pitch; 9868 if (spt < 0) spt = -spt; 9869 foreach (int y; 0..ftGlyph.bitmap.rows) { 9870 import core.stdc.string : memcpy; 9871 //dst[0..ftGlyph.bitmap.width] = src[0..ftGlyph.bitmap.width]; 9872 memcpy(dst, src, ftGlyph.bitmap.width); 9873 src += spt; 9874 dst += outStride; 9875 } 9876 } 9877 } 9878 9879 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc { 9880 FT_Vector ftKerning; 9881 version(none) { 9882 // fitted kerning 9883 FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning); 9884 //{ import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d\n", glyph1, glyph2, ftKerning.x, ftKerning.y); } 9885 return cast(int)ftKerning.x; // round up and convert to integer 9886 } else { 9887 // unfitted kerning 9888 //FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNFITTED, &ftKerning); 9889 if (glyph1 <= 0 || glyph2 <= 0 || (font.font.face_flags&FT_FACE_FLAG_KERNING) == 0) return 0; 9890 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; 9891 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning)) return 0; 9892 version(none) { 9893 if (ftKerning.x) { 9894 //{ import core.stdc.stdio : printf; printf("has kerning: %u\n", cast(uint)(font.font.face_flags&FT_FACE_FLAG_KERNING)); } 9895 { import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d (size=%g)\n", glyph1, glyph2, ftKerning.x, ftKerning.y, cast(double)size); } 9896 } 9897 } 9898 version(none) { 9899 FT_Vector kk; 9900 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNSCALED, &kk)) assert(0, "wtf?!"); 9901 auto kadvfrac = FT_MulFix(kk.x, font.font.size.metrics.x_scale); // 1/64 of pixel 9902 //return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6); 9903 //assert(ftKerning.x == kadvfrac); 9904 if (ftKerning.x || kadvfrac) { 9905 { 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); } 9906 } 9907 //return cast(int)(kadvfrac+(kadvfrac < 0 ? -31 : 32)>>6); // round up and convert to integer 9908 return kadvfrac/64.0f; 9909 } 9910 //return cast(int)(ftKerning.x+(ftKerning.x < 0 ? -31 : 32)>>6); // round up and convert to integer 9911 return ftKerning.x/64.0f; 9912 } 9913 } 9914 9915 extern(C) nothrow @trusted @nogc { 9916 static struct OutlinerData { 9917 @disable this (this); 9918 void opAssign() (const scope auto ref OutlinerData a) { static assert(0, "no copies!"); } 9919 NVGContext vg; 9920 NVGPathOutline.DataStore* ol; 9921 FT_BBox outlineBBox; 9922 nothrow @trusted @nogc: 9923 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; } 9924 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 9925 } 9926 9927 int fons__nvg__moveto_cb (const(FT_Vector)* to, void* user) { 9928 auto odata = cast(OutlinerData*)user; 9929 if (odata.vg !is null) odata.vg.moveTo(odata.transx(to.x), odata.transy(to.y)); 9930 if (odata.ol !is null) { 9931 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo); 9932 odata.ol.putArgs(odata.transx(to.x)); 9933 odata.ol.putArgs(odata.transy(to.y)); 9934 } 9935 return 0; 9936 } 9937 9938 int fons__nvg__lineto_cb (const(FT_Vector)* to, void* user) { 9939 auto odata = cast(OutlinerData*)user; 9940 if (odata.vg !is null) odata.vg.lineTo(odata.transx(to.x), odata.transy(to.y)); 9941 if (odata.ol !is null) { 9942 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo); 9943 odata.ol.putArgs(odata.transx(to.x)); 9944 odata.ol.putArgs(odata.transy(to.y)); 9945 } 9946 return 0; 9947 } 9948 9949 int fons__nvg__quadto_cb (const(FT_Vector)* c1, const(FT_Vector)* to, void* user) { 9950 auto odata = cast(OutlinerData*)user; 9951 if (odata.vg !is null) odata.vg.quadTo(odata.transx(c1.x), odata.transy(c1.y), odata.transx(to.x), odata.transy(to.y)); 9952 if (odata.ol !is null) { 9953 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo); 9954 odata.ol.putArgs(odata.transx(c1.x)); 9955 odata.ol.putArgs(odata.transy(c1.y)); 9956 odata.ol.putArgs(odata.transx(to.x)); 9957 odata.ol.putArgs(odata.transy(to.y)); 9958 } 9959 return 0; 9960 } 9961 9962 int fons__nvg__cubicto_cb (const(FT_Vector)* c1, const(FT_Vector)* c2, const(FT_Vector)* to, void* user) { 9963 auto odata = cast(OutlinerData*)user; 9964 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)); 9965 if (odata.ol !is null) { 9966 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo); 9967 odata.ol.putArgs(odata.transx(c1.x)); 9968 odata.ol.putArgs(odata.transy(c1.y)); 9969 odata.ol.putArgs(odata.transx(c2.x)); 9970 odata.ol.putArgs(odata.transy(c2.y)); 9971 odata.ol.putArgs(odata.transx(to.x)); 9972 odata.ol.putArgs(odata.transy(to.y)); 9973 } 9974 return 0; 9975 } 9976 } 9977 9978 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc { 9979 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 9980 9981 FT_Outline_Funcs funcs; 9982 funcs.move_to = &fons__nvg__moveto_cb; 9983 funcs.line_to = &fons__nvg__lineto_cb; 9984 funcs.conic_to = &fons__nvg__quadto_cb; 9985 funcs.cubic_to = &fons__nvg__cubicto_cb; 9986 9987 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 9988 if (err) { bounds[] = 0; return false; } 9989 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; } 9990 9991 FT_Outline outline = font.font.glyph.outline; 9992 9993 OutlinerData odata; 9994 odata.vg = vg; 9995 FT_Outline_Get_CBox(&outline, &odata.outlineBBox); 9996 9997 err = FT_Outline_Decompose(&outline, &funcs, &odata); 9998 if (err) { bounds[] = 0; return false; } 9999 if (bounds.length > 0) bounds.ptr[0] = odata.outlineBBox.xMin; 10000 if (bounds.length > 1) bounds.ptr[1] = -odata.outlineBBox.yMax; 10001 if (bounds.length > 2) bounds.ptr[2] = odata.outlineBBox.xMax; 10002 if (bounds.length > 3) bounds.ptr[3] = -odata.outlineBBox.yMin; 10003 return true; 10004 } 10005 10006 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 10007 FT_Outline_Funcs funcs; 10008 funcs.move_to = &fons__nvg__moveto_cb; 10009 funcs.line_to = &fons__nvg__lineto_cb; 10010 funcs.conic_to = &fons__nvg__quadto_cb; 10011 funcs.cubic_to = &fons__nvg__cubicto_cb; 10012 10013 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 10014 if (err) return false; 10015 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) return false; 10016 10017 FT_Outline outline = font.font.glyph.outline; 10018 10019 OutlinerData odata; 10020 odata.ol = ol; 10021 FT_Outline_Get_CBox(&outline, &odata.outlineBBox); 10022 10023 err = FT_Outline_Decompose(&outline, &funcs, &odata); 10024 if (err) return false; 10025 ol.bounds.ptr[0] = odata.outlineBBox.xMin; 10026 ol.bounds.ptr[1] = -odata.outlineBBox.yMax; 10027 ol.bounds.ptr[2] = odata.outlineBBox.xMax; 10028 ol.bounds.ptr[3] = -odata.outlineBBox.yMin; 10029 return true; 10030 } 10031 10032 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc { 10033 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10034 10035 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 10036 if (err) return false; 10037 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; } 10038 10039 FT_Outline outline = font.font.glyph.outline; 10040 FT_BBox outlineBBox; 10041 FT_Outline_Get_CBox(&outline, &outlineBBox); 10042 if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin; 10043 if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax; 10044 if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax; 10045 if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin; 10046 return true; 10047 } 10048 10049 10050 } else { 10051 // ////////////////////////////////////////////////////////////////////////// // 10052 // sorry 10053 import std.traits : isFunctionPointer, isDelegate; 10054 private auto assumeNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) { 10055 import std.traits; 10056 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_; 10057 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 10058 } 10059 10060 private auto forceNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) { 10061 try { 10062 return assumeNoThrowNoGC(t)(); 10063 } catch (Exception e) { 10064 assert(0, "OOPS!"); 10065 } 10066 } 10067 10068 struct FONSttFontImpl { 10069 stbtt_fontinfo font; 10070 bool mono; // no aa? 10071 } 10072 10073 int fons__tt_init (FONSContext context) nothrow @trusted @nogc { 10074 return 1; 10075 } 10076 10077 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc { 10078 font.mono = v; 10079 } 10080 10081 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc { 10082 return font.mono; 10083 } 10084 10085 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc { 10086 int stbError; 10087 font.font.userdata = context; 10088 forceNoThrowNoGC({ stbError = stbtt_InitFont(&font.font, data, 0); }); 10089 return stbError; 10090 } 10091 10092 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc { 10093 forceNoThrowNoGC({ stbtt_GetFontVMetrics(&font.font, ascent, descent, lineGap); }); 10094 } 10095 10096 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc { 10097 float res = void; 10098 forceNoThrowNoGC({ res = stbtt_ScaleForPixelHeight(&font.font, size); }); 10099 return res; 10100 } 10101 10102 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc { 10103 int res; 10104 forceNoThrowNoGC({ res = stbtt_FindGlyphIndex(&font.font, codepoint); }); 10105 return res; 10106 } 10107 10108 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 { 10109 forceNoThrowNoGC({ stbtt_GetGlyphHMetrics(&font.font, glyph, advance, lsb); }); 10110 forceNoThrowNoGC({ stbtt_GetGlyphBitmapBox(&font.font, glyph, scale, scale, x0, y0, x1, y1); }); 10111 return 1; 10112 } 10113 10114 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc { 10115 forceNoThrowNoGC({ stbtt_MakeGlyphBitmap(&font.font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); }); 10116 } 10117 10118 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc { 10119 // FUnits -> pixels: pointSize * resolution / (72 points per inch * units_per_em) 10120 // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#converting 10121 float res = void; 10122 forceNoThrowNoGC({ 10123 res = stbtt_GetGlyphKernAdvance(&font.font, glyph1, glyph2); 10124 res *= stbtt_ScaleForPixelHeight(&font.font, size); 10125 }); 10126 /* 10127 if (res != 0) { 10128 { 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)); } 10129 } 10130 */ 10131 //k8: dunno if this is right; i guess it isn't but... 10132 return res; 10133 } 10134 10135 // old arsd.ttf sux! ;-) 10136 static if (is(typeof(STBTT_vcubic))) { 10137 10138 static struct OutlinerData { 10139 @disable this (this); 10140 void opAssign() (const scope auto ref OutlinerData a) { static assert(0, "no copies!"); } 10141 NVGPathOutline.DataStore* ol; 10142 nothrow @trusted @nogc: 10143 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; } 10144 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 10145 } 10146 10147 10148 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc { 10149 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10150 10151 bool okflag = false; 10152 10153 forceNoThrowNoGC({ 10154 int x0, y0, x1, y1; 10155 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10156 bounds[] = 0; 10157 return; 10158 } 10159 10160 if (bounds.length > 0) bounds.ptr[0] = x0; 10161 if (bounds.length > 1) bounds.ptr[1] = -y1; 10162 if (bounds.length > 2) bounds.ptr[2] = x1; 10163 if (bounds.length > 3) bounds.ptr[3] = -y0; 10164 10165 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 10166 10167 stbtt_vertex* verts = null; 10168 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); } 10169 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts); 10170 if (vcount < 1) return; 10171 10172 foreach (const ref vt; verts[0..vcount]) { 10173 switch (vt.type) { 10174 case STBTT_vmove: vg.moveTo(vt.x, transy(vt.y)); break; 10175 case STBTT_vline: vg.lineTo(vt.x, transy(vt.y)); break; 10176 case STBTT_vcurve: vg.quadTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy)); break; 10177 case STBTT_vcubic: vg.bezierTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy), vt.cx1, transy(vt.cy1)); break; 10178 default: 10179 } 10180 } 10181 10182 okflag = true; 10183 }); 10184 10185 return okflag; 10186 } 10187 10188 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 10189 bool okflag = false; 10190 10191 forceNoThrowNoGC({ 10192 int x0, y0, x1, y1; 10193 10194 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10195 ol.bounds[] = 0; 10196 return; 10197 } 10198 10199 ol.bounds.ptr[0] = x0; 10200 ol.bounds.ptr[1] = -y1; 10201 ol.bounds.ptr[2] = x1; 10202 ol.bounds.ptr[3] = -y0; 10203 10204 stbtt_vertex* verts = null; 10205 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); } 10206 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts); 10207 if (vcount < 1) return; 10208 10209 OutlinerData odata; 10210 odata.ol = ol; 10211 10212 foreach (const ref vt; verts[0..vcount]) { 10213 switch (vt.type) { 10214 case STBTT_vmove: 10215 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo); 10216 odata.ol.putArgs(odata.transx(vt.x)); 10217 odata.ol.putArgs(odata.transy(vt.y)); 10218 break; 10219 case STBTT_vline: 10220 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo); 10221 odata.ol.putArgs(odata.transx(vt.x)); 10222 odata.ol.putArgs(odata.transy(vt.y)); 10223 break; 10224 case STBTT_vcurve: 10225 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo); 10226 odata.ol.putArgs(odata.transx(vt.x)); 10227 odata.ol.putArgs(odata.transy(vt.y)); 10228 odata.ol.putArgs(odata.transx(vt.cx)); 10229 odata.ol.putArgs(odata.transy(vt.cy)); 10230 break; 10231 case STBTT_vcubic: 10232 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo); 10233 odata.ol.putArgs(odata.transx(vt.x)); 10234 odata.ol.putArgs(odata.transy(vt.y)); 10235 odata.ol.putArgs(odata.transx(vt.cx)); 10236 odata.ol.putArgs(odata.transy(vt.cy)); 10237 odata.ol.putArgs(odata.transx(vt.cx1)); 10238 odata.ol.putArgs(odata.transy(vt.cy1)); 10239 break; 10240 default: 10241 } 10242 } 10243 10244 okflag = true; 10245 }); 10246 10247 return okflag; 10248 } 10249 10250 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc { 10251 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10252 10253 bool okflag = false; 10254 10255 forceNoThrowNoGC({ 10256 int x0, y0, x1, y1; 10257 if (stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10258 if (bounds.length > 0) bounds.ptr[0] = x0; 10259 if (bounds.length > 1) bounds.ptr[1] = -y1; 10260 if (bounds.length > 2) bounds.ptr[2] = x1; 10261 if (bounds.length > 3) bounds.ptr[3] = -y0; 10262 okflag = true; 10263 } else { 10264 bounds[] = 0; 10265 } 10266 }); 10267 10268 return okflag; 10269 } 10270 10271 } // check for old stb_ttf 10272 10273 10274 } // version 10275 10276 10277 // ////////////////////////////////////////////////////////////////////////// // 10278 private: 10279 enum FONS_SCRATCH_BUF_SIZE = 64000; 10280 enum FONS_HASH_LUT_SIZE = 256; 10281 enum FONS_INIT_FONTS = 4; 10282 enum FONS_INIT_GLYPHS = 256; 10283 enum FONS_INIT_ATLAS_NODES = 256; 10284 enum FONS_VERTEX_COUNT = 1024; 10285 enum FONS_MAX_STATES = 20; 10286 enum FONS_MAX_FALLBACKS = 20; 10287 10288 10289 struct FONSglyph { 10290 uint codepoint; 10291 int index; 10292 int next; 10293 short size, blur; 10294 short x0, y0, x1, y1; 10295 short xadv, xoff, yoff; 10296 } 10297 10298 // refcounted 10299 struct FONSfontData { 10300 ubyte* data; 10301 int dataSize; 10302 bool freeData; 10303 int rc; 10304 10305 @disable this (this); // no copies 10306 void opAssign() (const scope auto ref FONSfontData a) { static assert(0, "no copies!"); } 10307 } 10308 10309 // won't set rc to 1 10310 FONSfontData* fons__createFontData (ubyte* adata, int asize, bool afree) nothrow @trusted @nogc { 10311 import core.stdc.stdlib : malloc; 10312 assert(adata !is null); 10313 assert(asize > 0); 10314 auto res = cast(FONSfontData*)malloc(FONSfontData.sizeof); 10315 if (res is null) assert(0, "FONS: out of memory"); 10316 res.data = adata; 10317 res.dataSize = asize; 10318 res.freeData = afree; 10319 res.rc = 0; 10320 return res; 10321 } 10322 10323 void incref (FONSfontData* fd) pure nothrow @trusted @nogc { 10324 pragma(inline, true); 10325 if (fd !is null) ++fd.rc; 10326 } 10327 10328 void decref (ref FONSfontData* fd) nothrow @trusted @nogc { 10329 if (fd !is null) { 10330 if (--fd.rc == 0) { 10331 import core.stdc.stdlib : free; 10332 if (fd.freeData && fd.data !is null) { 10333 free(fd.data); 10334 fd.data = null; 10335 } 10336 free(fd); 10337 fd = null; 10338 } 10339 } 10340 } 10341 10342 // as creating and destroying fonts is a rare operation, malloc some data 10343 struct FONSfont { 10344 FONSttFontImpl font; 10345 char* name; // malloced, strz, always lowercase 10346 uint namelen; 10347 uint namehash; 10348 char* path; // malloced, strz 10349 FONSfontData* fdata; 10350 float ascender; 10351 float descender; 10352 float lineh; 10353 FONSglyph* glyphs; 10354 int cglyphs; 10355 int nglyphs; 10356 int[FONS_HASH_LUT_SIZE] lut; 10357 int[FONS_MAX_FALLBACKS] fallbacks; 10358 int nfallbacks; 10359 10360 @disable this (this); 10361 void opAssign() (const scope auto ref FONSfont a) { static assert(0, "no copies"); } 10362 10363 static uint djbhash (const(void)[] s) pure nothrow @safe @nogc { 10364 uint hash = 5381; 10365 foreach (ubyte b; cast(const(ubyte)[])s) { 10366 if (b >= 'A' && b <= 'Z') b += 32; // poor man's tolower 10367 hash = ((hash<<5)+hash)+b; 10368 } 10369 return hash; 10370 } 10371 10372 // except glyphs 10373 void freeMemory () nothrow @trusted @nogc { 10374 import core.stdc.stdlib : free; 10375 if (name !is null) { free(name); name = null; } 10376 namelen = namehash = 0; 10377 if (path !is null) { free(path); path = null; } 10378 fdata.decref(); 10379 } 10380 10381 // this also calcs name hash 10382 void setName (const(char)[] aname) nothrow @trusted @nogc { 10383 //{ import core.stdc.stdio; printf("setname: [%.*s]\n", cast(uint)aname.length, aname.ptr); } 10384 import core.stdc.stdlib : realloc; 10385 if (aname.length > int.max/32) assert(0, "FONS: invalid font name"); 10386 namelen = cast(uint)aname.length; 10387 name = cast(char*)realloc(name, namelen+1); 10388 if (name is null) assert(0, "FONS: out of memory"); 10389 if (aname.length) name[0..aname.length] = aname[]; 10390 name[namelen] = 0; 10391 // lowercase it 10392 foreach (ref char ch; name[0..namelen]) if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower 10393 namehash = djbhash(name[0..namelen]); 10394 //{ import core.stdc.stdio; printf(" [%s] [%.*s] [0x%08x]\n", name, namelen, name, namehash); } 10395 } 10396 10397 void setPath (const(char)[] apath) nothrow @trusted @nogc { 10398 import core.stdc.stdlib : realloc; 10399 if (apath.length > int.max/32) assert(0, "FONS: invalid font path"); 10400 path = cast(char*)realloc(path, apath.length+1); 10401 if (path is null) assert(0, "FONS: out of memory"); 10402 if (apath.length) path[0..apath.length] = apath[]; 10403 path[apath.length] = 0; 10404 } 10405 10406 // this won't check hash 10407 bool nameEqu (const(char)[] aname) const pure nothrow @trusted @nogc { 10408 //{ import core.stdc.stdio; printf("nameEqu: aname=[%.*s]; namelen=%u; aslen=%u\n", cast(uint)aname.length, aname.ptr, namelen, cast(uint)aname.length); } 10409 if (namelen != aname.length) return false; 10410 const(char)* ns = name; 10411 // name part 10412 foreach (char ch; aname) { 10413 if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower 10414 if (ch != *ns++) return false; 10415 } 10416 // done (length was checked earlier) 10417 return true; 10418 } 10419 10420 void clear () nothrow @trusted @nogc { 10421 import core.stdc.stdlib : free; 10422 import core.stdc.string : memset; 10423 if (glyphs !is null) free(glyphs); 10424 freeMemory(); 10425 memset(&this, 0, this.sizeof); 10426 } 10427 10428 FONSglyph* allocGlyph () nothrow @trusted @nogc { 10429 if (nglyphs+1 > cglyphs) { 10430 import core.stdc.stdlib : realloc; 10431 cglyphs = (cglyphs == 0 ? 8 : cglyphs*2); 10432 glyphs = cast(FONSglyph*)realloc(glyphs, FONSglyph.sizeof*cglyphs); 10433 if (glyphs is null) assert(0, "FontStash: out of memory"); 10434 } 10435 ++nglyphs; 10436 return &glyphs[nglyphs-1]; 10437 } 10438 } 10439 10440 void kill (ref FONSfont* font) nothrow @trusted @nogc { 10441 if (font !is null) { 10442 import core.stdc.stdlib : free; 10443 font.clear(); 10444 free(font); 10445 font = null; 10446 } 10447 } 10448 10449 10450 // ////////////////////////////////////////////////////////////////////////// // 10451 struct FONSstate { 10452 int font; 10453 NVGTextAlign talign; 10454 float size = 0; 10455 float blur = 0; 10456 float spacing = 0; 10457 } 10458 10459 10460 // ////////////////////////////////////////////////////////////////////////// // 10461 // atlas based on Skyline Bin Packer by Jukka Jylänki 10462 alias FONSAtlas = FONSatlasInternal*; 10463 10464 struct FONSatlasInternal { 10465 static struct Node { 10466 short x, y, width; 10467 } 10468 10469 int width, height; 10470 Node* nodes; 10471 int nnodes; 10472 int cnodes; 10473 10474 @disable this (this); 10475 void opAssign() (const scope auto ref FONSatlasInternal a) { static assert(0, "no copies"); } 10476 10477 nothrow @trusted @nogc: 10478 static FONSAtlas create (int w, int h, int nnodes) { 10479 import core.stdc.stdlib : malloc; 10480 import core.stdc.string : memset; 10481 10482 FONSAtlas atlas = cast(FONSAtlas)malloc(FONSatlasInternal.sizeof); 10483 if (atlas is null) assert(0, "FontStash: out of memory"); 10484 memset(atlas, 0, FONSatlasInternal.sizeof); 10485 10486 atlas.width = w; 10487 atlas.height = h; 10488 10489 // allocate space for skyline nodes 10490 atlas.nodes = cast(Node*)malloc(Node.sizeof*nnodes); 10491 if (atlas.nodes is null) assert(0, "FontStash: out of memory"); 10492 memset(atlas.nodes, 0, Node.sizeof*nnodes); 10493 atlas.nnodes = 0; 10494 atlas.cnodes = nnodes; 10495 10496 // init root node 10497 atlas.nodes[0].x = 0; 10498 atlas.nodes[0].y = 0; 10499 atlas.nodes[0].width = cast(short)w; 10500 ++atlas.nnodes; 10501 10502 return atlas; 10503 } 10504 10505 void clear () { 10506 import core.stdc.stdlib : free; 10507 import core.stdc.string : memset; 10508 10509 if (nodes !is null) free(nodes); 10510 memset(&this, 0, this.sizeof); 10511 } 10512 10513 void insertNode (int idx, int x, int y, int w) { 10514 if (nnodes+1 > cnodes) { 10515 import core.stdc.stdlib : realloc; 10516 cnodes = (cnodes == 0 ? 8 : cnodes*2); 10517 nodes = cast(Node*)realloc(nodes, Node.sizeof*cnodes); 10518 if (nodes is null) assert(0, "FontStash: out of memory"); 10519 } 10520 for (int i = nnodes; i > idx; --i) nodes[i] = nodes[i-1]; 10521 nodes[idx].x = cast(short)x; 10522 nodes[idx].y = cast(short)y; 10523 nodes[idx].width = cast(short)w; 10524 ++nnodes; 10525 } 10526 10527 void removeNode (int idx) { 10528 if (nnodes == 0) return; 10529 foreach (immutable int i; idx+1..nnodes) nodes[i-1] = nodes[i]; 10530 --nnodes; 10531 } 10532 10533 // insert node for empty space 10534 void expand (int w, int h) { 10535 if (w > width) insertNode(nnodes, width, 0, w-width); 10536 width = w; 10537 height = h; 10538 } 10539 10540 void reset (int w, int h) { 10541 width = w; 10542 height = h; 10543 nnodes = 0; 10544 // init root node 10545 nodes[0].x = 0; 10546 nodes[0].y = 0; 10547 nodes[0].width = cast(short)w; 10548 ++nnodes; 10549 } 10550 10551 void addSkylineLevel (int idx, int x, int y, int w, int h) { 10552 insertNode(idx, x, y+h, w); 10553 10554 // delete skyline segments that fall under the shadow of the new segment 10555 for (int i = idx+1; i < nnodes; ++i) { 10556 if (nodes[i].x < nodes[i-1].x+nodes[i-1].width) { 10557 int shrink = nodes[i-1].x+nodes[i-1].width-nodes[i].x; 10558 nodes[i].x += cast(short)shrink; 10559 nodes[i].width -= cast(short)shrink; 10560 if (nodes[i].width <= 0) { 10561 removeNode(i); 10562 --i; 10563 } else { 10564 break; 10565 } 10566 } else { 10567 break; 10568 } 10569 } 10570 10571 // Merge same height skyline segments that are next to each other 10572 for (int i = 0; i < nnodes-1; ++i) { 10573 if (nodes[i].y == nodes[i+1].y) { 10574 nodes[i].width += nodes[i+1].width; 10575 removeNode(i+1); 10576 --i; 10577 } 10578 } 10579 } 10580 10581 // checks if there is enough space at the location of skyline span 'i', 10582 // and return the max height of all skyline spans under that at that location, 10583 // (think tetris block being dropped at that position); or -1 if no space found 10584 int rectFits (int i, int w, int h) { 10585 int x = nodes[i].x; 10586 int y = nodes[i].y; 10587 if (x+w > width) return -1; 10588 int spaceLeft = w; 10589 while (spaceLeft > 0) { 10590 if (i == nnodes) return -1; 10591 y = nvg__max(y, nodes[i].y); 10592 if (y+h > height) return -1; 10593 spaceLeft -= nodes[i].width; 10594 ++i; 10595 } 10596 return y; 10597 } 10598 10599 bool addRect (int rw, int rh, int* rx, int* ry) { 10600 int besth = height, bestw = width, besti = -1; 10601 int bestx = -1, besty = -1; 10602 10603 // Bottom left fit heuristic. 10604 for (int i = 0; i < nnodes; ++i) { 10605 int y = rectFits(i, rw, rh); 10606 if (y != -1) { 10607 if (y+rh < besth || (y+rh == besth && nodes[i].width < bestw)) { 10608 besti = i; 10609 bestw = nodes[i].width; 10610 besth = y+rh; 10611 bestx = nodes[i].x; 10612 besty = y; 10613 } 10614 } 10615 } 10616 10617 if (besti == -1) return false; 10618 10619 // perform the actual packing 10620 addSkylineLevel(besti, bestx, besty, rw, rh); 10621 10622 *rx = bestx; 10623 *ry = besty; 10624 10625 return true; 10626 } 10627 } 10628 10629 void kill (ref FONSAtlas atlas) nothrow @trusted @nogc { 10630 if (atlas !is null) { 10631 import core.stdc.stdlib : free; 10632 atlas.clear(); 10633 free(atlas); 10634 atlas = null; 10635 } 10636 } 10637 10638 10639 // ////////////////////////////////////////////////////////////////////////// // 10640 /// FontStash context (internal definition). Don't use it derectly, it was made public only to generate documentation. 10641 /// Group: font_stash 10642 public struct FONScontextInternal { 10643 private: 10644 FONSParams params; 10645 float itw, ith; 10646 ubyte* texData; 10647 int[4] dirtyRect; 10648 FONSfont** fonts; // actually, a simple hash table; can't grow yet 10649 int cfonts; // allocated 10650 int nfonts; // used (so we can track hash table stats) 10651 int* hashidx; // [hsize] items; holds indicies in [fonts] array 10652 int hused, hsize;// used items and total items in [hashidx] 10653 FONSAtlas atlas; 10654 ubyte* scratch; 10655 int nscratch; 10656 FONSstate[FONS_MAX_STATES] states; 10657 int nstates; 10658 10659 void delegate (FONSError error, int val) nothrow @trusted @nogc handleError; 10660 10661 @disable this (this); 10662 void opAssign() (const scope auto ref FONScontextInternal ctx) { static assert(0, "FONS copying is not allowed"); } 10663 10664 private: 10665 static bool strequci (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 10666 if (s0.length != s1.length) return false; 10667 const(char)* sp0 = s0.ptr; 10668 const(char)* sp1 = s1.ptr; 10669 foreach (immutable _; 0..s0.length) { 10670 char c0 = *sp0++; 10671 char c1 = *sp1++; 10672 if (c0 != c1) { 10673 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man tolower 10674 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man tolower 10675 if (c0 != c1) return false; 10676 } 10677 } 10678 return true; 10679 } 10680 10681 inout(FONSstate)* getState () inout pure nothrow @trusted @nogc return { 10682 pragma(inline, true); 10683 return cast(inout)(&states[(nstates > 0 ? nstates-1 : 0)]); 10684 } 10685 10686 // simple linear probing; returns [FONS_INVALID] if not found 10687 int findNameInHash (const(char)[] name) const pure nothrow @trusted @nogc { 10688 if (nfonts == 0) return FONS_INVALID; 10689 auto nhash = FONSfont.djbhash(name); 10690 //{ import core.stdc.stdio; printf("findinhash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10691 auto res = nhash%hsize; 10692 // hash will never be 100% full, so this loop is safe 10693 for (;;) { 10694 int idx = hashidx[res]; 10695 if (idx == -1) break; 10696 auto font = fonts[idx]; 10697 if (font is null) assert(0, "FONS internal error"); 10698 if (font.namehash == nhash && font.nameEqu(name)) return idx; 10699 //{ import core.stdc.stdio; printf("findinhash chained: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10700 res = (res+1)%hsize; 10701 } 10702 return FONS_INVALID; 10703 } 10704 10705 // should be called $(B before) freeing `fonts[fidx]` 10706 void removeIndexFromHash (int fidx) nothrow @trusted @nogc { 10707 if (fidx < 0 || fidx >= nfonts) assert(0, "FONS internal error"); 10708 if (fonts[fidx] is null) assert(0, "FONS internal error"); 10709 if (hused != nfonts) assert(0, "FONS internal error"); 10710 auto nhash = fonts[fidx].namehash; 10711 auto res = nhash%hsize; 10712 // hash will never be 100% full, so this loop is safe 10713 for (;;) { 10714 int idx = hashidx[res]; 10715 if (idx == -1) assert(0, "FONS INTERNAL ERROR"); 10716 if (idx == fidx) { 10717 // i found her! copy rest here 10718 int nidx = (res+1)%hsize; 10719 for (;;) { 10720 if ((hashidx[res] = hashidx[nidx]) == -1) break; // so it will copy `-1` too 10721 res = nidx; 10722 nidx = (nidx+1)%hsize; 10723 } 10724 return; 10725 } 10726 res = (res+1)%hsize; 10727 } 10728 } 10729 10730 // add font with the given index to hash 10731 // prerequisite: font should not exists in hash 10732 void addIndexToHash (int idx) nothrow @trusted @nogc { 10733 if (idx < 0 || idx >= nfonts) assert(0, "FONS internal error"); 10734 if (fonts[idx] is null) assert(0, "FONS internal error"); 10735 import core.stdc.stdlib : realloc; 10736 auto nhash = fonts[idx].namehash; 10737 //{ import core.stdc.stdio; printf("addtohash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10738 // allocate new hash table if there was none 10739 if (hsize == 0) { 10740 enum InitSize = 256; 10741 auto newlist = cast(int*)realloc(null, InitSize*hashidx[0].sizeof); 10742 if (newlist is null) assert(0, "FONS: out of memory"); 10743 newlist[0..InitSize] = -1; 10744 hsize = InitSize; 10745 hused = 0; 10746 hashidx = newlist; 10747 } 10748 int res = cast(int)(nhash%hsize); 10749 // need to rehash? we want our hash table 50% full at max 10750 if (hashidx[res] != -1 && hused >= hsize/2) { 10751 uint nsz = hsize*2; 10752 if (nsz > 1024*1024) assert(0, "FONS: out of memory for fonts"); 10753 auto newlist = cast(int*)realloc(fonts, nsz*hashidx[0].sizeof); 10754 if (newlist is null) assert(0, "FONS: out of memory"); 10755 newlist[0..nsz] = -1; 10756 hused = 0; 10757 // rehash 10758 foreach (immutable fidx, FONSfont* ff; fonts[0..nfonts]) { 10759 if (ff is null) continue; 10760 // find slot for this font (guaranteed to have one) 10761 uint newslot = ff.namehash%nsz; 10762 while (newlist[newslot] != -1) newslot = (newslot+1)%nsz; 10763 newlist[newslot] = cast(int)fidx; 10764 ++hused; 10765 } 10766 hsize = nsz; 10767 hashidx = newlist; 10768 // we added everything, including [idx], so nothing more to do here 10769 } else { 10770 // find slot (guaranteed to have one) 10771 while (hashidx[res] != -1) res = (res+1)%hsize; 10772 // i found her! 10773 hashidx[res] = idx; 10774 ++hused; 10775 } 10776 } 10777 10778 void addWhiteRect (int w, int h) nothrow @trusted @nogc { 10779 int gx, gy; 10780 ubyte* dst; 10781 10782 if (!atlas.addRect(w, h, &gx, &gy)) return; 10783 10784 // Rasterize 10785 dst = &texData[gx+gy*params.width]; 10786 foreach (int y; 0..h) { 10787 foreach (int x; 0..w) { 10788 dst[x] = 0xff; 10789 } 10790 dst += params.width; 10791 } 10792 10793 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], gx); 10794 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], gy); 10795 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], gx+w); 10796 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], gy+h); 10797 } 10798 10799 // returns fid, not hash slot 10800 int allocFontAt (int atidx) nothrow @trusted @nogc { 10801 if (atidx >= 0 && atidx >= nfonts) assert(0, "internal NanoVega fontstash error"); 10802 10803 if (atidx < 0) { 10804 if (nfonts >= cfonts) { 10805 import core.stdc.stdlib : realloc; 10806 import core.stdc.string : memset; 10807 assert(nfonts == cfonts); 10808 int newsz = cfonts+64; 10809 if (newsz > 65535) assert(0, "FONS: too many fonts"); 10810 auto newlist = cast(FONSfont**)realloc(fonts, newsz*(FONSfont*).sizeof); 10811 if (newlist is null) assert(0, "FONS: out of memory"); 10812 memset(newlist+cfonts, 0, (newsz-cfonts)*(FONSfont*).sizeof); 10813 fonts = newlist; 10814 cfonts = newsz; 10815 } 10816 assert(nfonts < cfonts); 10817 } 10818 10819 FONSfont* font = cast(FONSfont*)malloc(FONSfont.sizeof); 10820 if (font is null) assert(0, "FONS: out of memory"); 10821 memset(font, 0, FONSfont.sizeof); 10822 10823 font.glyphs = cast(FONSglyph*)malloc(FONSglyph.sizeof*FONS_INIT_GLYPHS); 10824 if (font.glyphs is null) assert(0, "FONS: out of memory"); 10825 font.cglyphs = FONS_INIT_GLYPHS; 10826 font.nglyphs = 0; 10827 10828 if (atidx < 0) { 10829 fonts[nfonts] = font; 10830 return nfonts++; 10831 } else { 10832 fonts[atidx] = font; 10833 return atidx; 10834 } 10835 } 10836 10837 // 0: ooops 10838 int findGlyphForCP (FONSfont *font, dchar dch, FONSfont** renderfont) nothrow @trusted @nogc { 10839 if (renderfont !is null) *renderfont = font; 10840 if (font is null || font.fdata is null) return 0; 10841 auto g = fons__tt_getGlyphIndex(&font.font, cast(uint)dch); 10842 // try to find the glyph in fallback fonts 10843 if (g == 0) { 10844 foreach (immutable i; 0..font.nfallbacks) { 10845 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]]; 10846 if (fallbackFont !is null) { 10847 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, cast(uint)dch); 10848 if (fallbackIndex != 0) { 10849 if (renderfont !is null) *renderfont = fallbackFont; 10850 return g; 10851 } 10852 } 10853 } 10854 // no char, try to find replacement one 10855 if (dch != 0xFFFD) { 10856 g = fons__tt_getGlyphIndex(&font.font, 0xFFFD); 10857 if (g == 0) { 10858 foreach (immutable i; 0..font.nfallbacks) { 10859 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]]; 10860 if (fallbackFont !is null) { 10861 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, 0xFFFD); 10862 if (fallbackIndex != 0) { 10863 if (renderfont !is null) *renderfont = fallbackFont; 10864 return g; 10865 } 10866 } 10867 } 10868 } 10869 } 10870 } 10871 return g; 10872 } 10873 10874 void clear () nothrow @trusted @nogc { 10875 import core.stdc.stdlib : free; 10876 10877 if (params.renderDelete !is null) params.renderDelete(params.userPtr); 10878 foreach (immutable int i; 0..nfonts) fonts[i].kill(); 10879 10880 if (atlas !is null) atlas.kill(); 10881 if (fonts !is null) free(fonts); 10882 if (texData !is null) free(texData); 10883 if (scratch !is null) free(scratch); 10884 if (hashidx !is null) free(hashidx); 10885 } 10886 10887 // add font from another fontstash 10888 int addCookedFont (FONSfont* font) nothrow @trusted @nogc { 10889 if (font is null || font.fdata is null) return FONS_INVALID; 10890 font.fdata.incref(); 10891 auto res = addFontWithData(font.name[0..font.namelen], font.fdata, !font.font.mono); 10892 if (res == FONS_INVALID) font.fdata.decref(); // oops 10893 return res; 10894 } 10895 10896 // fdata refcount must be already increased; it won't be changed 10897 int addFontWithData (const(char)[] name, FONSfontData* fdata, bool defAA) nothrow @trusted @nogc { 10898 int i, ascent, descent, fh, lineGap; 10899 10900 if (name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID; 10901 if (name.length > 32767) return FONS_INVALID; 10902 if (fdata is null) return FONS_INVALID; 10903 10904 // find a font with the given name 10905 int newidx; 10906 FONSfont* oldfont = null; 10907 int oldidx = findNameInHash(name); 10908 if (oldidx != FONS_INVALID) { 10909 // replacement font 10910 oldfont = fonts[oldidx]; 10911 newidx = oldidx; 10912 } else { 10913 // new font, allocate new bucket 10914 newidx = -1; 10915 } 10916 10917 newidx = allocFontAt(newidx); 10918 FONSfont* font = fonts[newidx]; 10919 font.setName(name); 10920 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; // init hash lookup 10921 font.fdata = fdata; // set the font data (don't change reference count) 10922 fons__tt_setMono(&this, &font.font, !defAA); 10923 10924 // init font 10925 nscratch = 0; 10926 if (!fons__tt_loadFont(&this, &font.font, fdata.data, fdata.dataSize)) { 10927 // we promised to not free data on error, so just clear the data store (it will be freed by the caller) 10928 font.fdata = null; 10929 font.kill(); 10930 if (oldidx != FONS_INVALID) { 10931 assert(oldidx == newidx); 10932 fonts[oldidx] = oldfont; 10933 } else { 10934 assert(newidx == nfonts-1); 10935 fonts[newidx] = null; 10936 --nfonts; 10937 } 10938 return FONS_INVALID; 10939 } else { 10940 // free old font data, if any 10941 if (oldfont !is null) oldfont.kill(); 10942 } 10943 10944 // add font to name hash 10945 if (oldidx == FONS_INVALID) addIndexToHash(newidx); 10946 10947 // store normalized line height 10948 // the real line height is got by multiplying the lineh by font size 10949 fons__tt_getFontVMetrics(&font.font, &ascent, &descent, &lineGap); 10950 fh = ascent-descent; 10951 font.ascender = cast(float)ascent/cast(float)fh; 10952 font.descender = cast(float)descent/cast(float)fh; 10953 font.lineh = cast(float)(fh+lineGap)/cast(float)fh; 10954 10955 //{ import core.stdc.stdio; printf("created font [%.*s] (idx=%d)...\n", cast(uint)name.length, name.ptr, idx); } 10956 return newidx; 10957 } 10958 10959 // isize: size*10 10960 float getVertAlign (FONSfont* font, NVGTextAlign talign, short isize) pure nothrow @trusted @nogc { 10961 if (params.isZeroTopLeft) { 10962 final switch (talign.vertical) { 10963 case NVGTextAlign.V.Top: return font.ascender*cast(float)isize/10.0f; 10964 case NVGTextAlign.V.Middle: return (font.ascender+font.descender)/2.0f*cast(float)isize/10.0f; 10965 case NVGTextAlign.V.Baseline: return 0.0f; 10966 case NVGTextAlign.V.Bottom: return font.descender*cast(float)isize/10.0f; 10967 } 10968 } else { 10969 final switch (talign.vertical) { 10970 case NVGTextAlign.V.Top: return -font.ascender*cast(float)isize/10.0f; 10971 case NVGTextAlign.V.Middle: return -(font.ascender+font.descender)/2.0f*cast(float)isize/10.0f; 10972 case NVGTextAlign.V.Baseline: return 0.0f; 10973 case NVGTextAlign.V.Bottom: return -font.descender*cast(float)isize/10.0f; 10974 } 10975 } 10976 assert(0); 10977 } 10978 10979 public: 10980 /** Create new FontStash context. It can be destroyed with `fs.kill()` later. 10981 * 10982 * Note that if you don't plan to rasterize glyphs (i.e. you will use created 10983 * FontStash only to measure text), you can simply pass `FONSParams.init`). 10984 */ 10985 static FONSContext create() (const scope auto ref FONSParams params) nothrow @trusted @nogc { 10986 import core.stdc.string : memcpy; 10987 10988 FONSContext stash = null; 10989 10990 // allocate memory for the font stash 10991 stash = cast(FONSContext)malloc(FONScontextInternal.sizeof); 10992 if (stash is null) goto error; 10993 memset(stash, 0, FONScontextInternal.sizeof); 10994 10995 memcpy(&stash.params, ¶ms, params.sizeof); 10996 if (stash.params.width < 1) stash.params.width = 32; 10997 if (stash.params.height < 1) stash.params.height = 32; 10998 10999 // allocate scratch buffer 11000 stash.scratch = cast(ubyte*)malloc(FONS_SCRATCH_BUF_SIZE); 11001 if (stash.scratch is null) goto error; 11002 11003 // initialize implementation library 11004 if (!fons__tt_init(stash)) goto error; 11005 11006 if (stash.params.renderCreate !is null) { 11007 if (!stash.params.renderCreate(stash.params.userPtr, stash.params.width, stash.params.height)) goto error; 11008 } 11009 11010 stash.atlas = FONSAtlas.create(stash.params.width, stash.params.height, FONS_INIT_ATLAS_NODES); 11011 if (stash.atlas is null) goto error; 11012 11013 // don't allocate space for fonts: hash manager will do that for us later 11014 //stash.cfonts = 0; 11015 //stash.nfonts = 0; 11016 11017 // create texture for the cache 11018 stash.itw = 1.0f/stash.params.width; 11019 stash.ith = 1.0f/stash.params.height; 11020 stash.texData = cast(ubyte*)malloc(stash.params.width*stash.params.height); 11021 if (stash.texData is null) goto error; 11022 memset(stash.texData, 0, stash.params.width*stash.params.height); 11023 11024 stash.dirtyRect.ptr[0] = stash.params.width; 11025 stash.dirtyRect.ptr[1] = stash.params.height; 11026 stash.dirtyRect.ptr[2] = 0; 11027 stash.dirtyRect.ptr[3] = 0; 11028 11029 // add white rect at 0, 0 for debug drawing 11030 stash.addWhiteRect(2, 2); 11031 11032 stash.pushState(); 11033 stash.clearState(); 11034 11035 return stash; 11036 11037 error: 11038 stash.kill(); 11039 return null; 11040 } 11041 11042 public: 11043 /// Add fallback font (FontStash will try to find missing glyph in all fallback fonts before giving up). 11044 bool addFallbackFont (int base, int fallback) nothrow @trusted @nogc { 11045 FONSfont* baseFont = fonts[base]; 11046 if (baseFont !is null && baseFont.nfallbacks < FONS_MAX_FALLBACKS) { 11047 baseFont.fallbacks.ptr[baseFont.nfallbacks++] = fallback; 11048 return true; 11049 } 11050 return false; 11051 } 11052 11053 @property void size (float size) nothrow @trusted @nogc { pragma(inline, true); getState.size = size; } /// Set current font size. 11054 @property float size () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.size; } /// Get current font size. 11055 11056 @property void spacing (float spacing) nothrow @trusted @nogc { pragma(inline, true); getState.spacing = spacing; } /// Set current letter spacing. 11057 @property float spacing () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.spacing; } /// Get current letter spacing. 11058 11059 @property void blur (float blur) nothrow @trusted @nogc { pragma(inline, true); getState.blur = blur; } /// Set current letter blur. 11060 @property float blur () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.blur; } /// Get current letter blur. 11061 11062 @property void textAlign (NVGTextAlign talign) nothrow @trusted @nogc { pragma(inline, true); getState.talign = talign; } /// Set current text align. 11063 @property NVGTextAlign textAlign () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.talign; } /// Get current text align. 11064 11065 @property void fontId (int font) nothrow @trusted @nogc { pragma(inline, true); getState.font = font; } /// Set current font id. 11066 @property int fontId () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.font; } /// Get current font id. 11067 11068 @property void fontId (const(char)[] name) nothrow @trusted @nogc { pragma(inline, true); getState.font = getFontByName(name); } /// Set current font using its name. 11069 11070 /// Check if FontStash has a font with the given name loaded. 11071 bool hasFont (const(char)[] name) const pure nothrow @trusted @nogc { pragma(inline, true); return (getFontByName(name) >= 0); } 11072 11073 /// Get AA for the current font, or for the specified font. 11074 bool getFontAA (int font=-1) nothrow @trusted @nogc { 11075 FONSstate* state = getState; 11076 if (font < 0) font = state.font; 11077 if (font < 0 || font >= nfonts) return false; 11078 FONSfont* f = fonts[font]; 11079 return (f !is null ? !f.font.mono : false); 11080 } 11081 11082 /// Push current state. Returns `false` if state stack overflowed. 11083 bool pushState () nothrow @trusted @nogc { 11084 if (nstates >= FONS_MAX_STATES) { 11085 if (handleError !is null) handleError(FONSError.StatesOverflow, 0); 11086 return false; 11087 } 11088 if (nstates > 0) { 11089 import core.stdc.string : memcpy; 11090 memcpy(&states[nstates], &states[nstates-1], FONSstate.sizeof); 11091 } 11092 ++nstates; 11093 return true; 11094 } 11095 11096 /// Pop current state. Returns `false` if state stack underflowed. 11097 bool popState () nothrow @trusted @nogc { 11098 if (nstates <= 1) { 11099 if (handleError !is null) handleError(FONSError.StatesUnderflow, 0); 11100 return false; 11101 } 11102 --nstates; 11103 return true; 11104 } 11105 11106 /// Clear current state (i.e. set it to some sane defaults). 11107 void clearState () nothrow @trusted @nogc { 11108 FONSstate* state = getState; 11109 state.size = 12.0f; 11110 state.font = 0; 11111 state.blur = 0; 11112 state.spacing = 0; 11113 state.talign.reset; 11114 } 11115 11116 private enum NoAlias = ":noaa"; 11117 11118 /** Add font to FontStash. 11119 * 11120 * Load scalable font from disk, and add it to FontStash. If you will try to load a font 11121 * with same name and path several times, FontStash will load it only once. Also, you can 11122 * load new disk font for any existing logical font. 11123 * 11124 * Params: 11125 * name = logical font name, that will be used to select this font later. 11126 * path = path to disk file with your font. 11127 * defAA = should FontStash use antialiased font rasterizer? 11128 * 11129 * Returns: 11130 * font id or [FONS_INVALID]. 11131 */ 11132 int addFont (const(char)[] name, const(char)[] path, bool defAA=false) nothrow @trusted { 11133 if (path.length == 0 || name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID; 11134 if (path.length > 32768) return FONS_INVALID; // arbitrary limit 11135 11136 // if font path ends with ":noaa", turn off antialiasing 11137 if (path.length >= NoAlias.length && strequci(path[$-NoAlias.length..$], NoAlias)) { 11138 path = path[0..$-NoAlias.length]; 11139 if (path.length == 0) return FONS_INVALID; 11140 defAA = false; 11141 } 11142 11143 // if font name ends with ":noaa", turn off antialiasing 11144 if (name.length > NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) { 11145 name = name[0..$-NoAlias.length]; 11146 defAA = false; 11147 } 11148 11149 // find a font with the given name 11150 int fidx = findNameInHash(name); 11151 //{ import core.stdc.stdio; printf("loading font '%.*s' [%s] (fidx=%d)...\n", cast(uint)path.length, path.ptr, fontnamebuf.ptr, fidx); } 11152 11153 int loadFontFile (const(char)[] path) { 11154 // check if existing font (if any) has the same path 11155 if (fidx >= 0) { 11156 import core.stdc.string : strlen; 11157 auto plen = (fonts[fidx].path !is null ? strlen(fonts[fidx].path) : 0); 11158 version(Posix) { 11159 //{ 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); } 11160 if (plen == path.length && fonts[fidx].path[0..plen] == path) { 11161 //{ import core.stdc.stdio; printf("*** font [%.*s] already loaded from [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)plen, path.ptr); } 11162 // i found her! 11163 return fidx; 11164 } 11165 } else { 11166 if (plen == path.length && strequci(fonts[fidx].path[0..plen], path)) { 11167 // i found her! 11168 return fidx; 11169 } 11170 } 11171 } 11172 version(Windows) { 11173 // special shitdows check: this will reject fontconfig font names (but still allow things like "c:myfont") 11174 foreach (immutable char ch; path[(path.length >= 2 && path[1] == ':' ? 2 : 0)..$]) if (ch == ':') return FONS_INVALID; 11175 } 11176 // either no such font, or different path 11177 //{ import core.stdc.stdio; printf("trying font [%.*s] from file [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)path.length, path.ptr); } 11178 int xres = FONS_INVALID; 11179 try { 11180 import core.stdc.stdlib : free, malloc; 11181 static if (NanoVegaHasIVVFS) { 11182 auto fl = VFile(path); 11183 auto dataSize = fl.size; 11184 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID; 11185 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize); 11186 if (data is null) assert(0, "out of memory in NanoVega fontstash"); 11187 scope(failure) free(data); // oops 11188 fl.rawReadExact(data[0..cast(uint)dataSize]); 11189 fl.close(); 11190 } else { 11191 import core.stdc.stdio : FILE, fopen, fclose, fread, ftell, fseek; 11192 import std.internal.cstring : tempCString; 11193 auto fl = fopen(path.tempCString, "rb"); 11194 if (fl is null) return FONS_INVALID; 11195 scope(exit) fclose(fl); 11196 if (fseek(fl, 0, 2/*SEEK_END*/) != 0) return FONS_INVALID; 11197 auto dataSize = ftell(fl); 11198 if (fseek(fl, 0, 0/*SEEK_SET*/) != 0) return FONS_INVALID; 11199 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID; 11200 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize); 11201 if (data is null) assert(0, "out of memory in NanoVega fontstash"); 11202 scope(failure) free(data); // oops 11203 ubyte* dptr = data; 11204 auto left = cast(uint)dataSize; 11205 while (left > 0) { 11206 auto rd = fread(dptr, 1, left, fl); 11207 if (rd == 0) { free(data); return FONS_INVALID; } // unexpected EOF or reading error, it doesn't matter 11208 dptr += rd; 11209 left -= rd; 11210 } 11211 } 11212 scope(failure) free(data); // oops 11213 // create font data 11214 FONSfontData* fdata = fons__createFontData(data, cast(int)dataSize, true); // free data 11215 fdata.incref(); 11216 xres = addFontWithData(name, fdata, defAA); 11217 if (xres == FONS_INVALID) { 11218 fdata.decref(); // this will free [data] and [fdata] 11219 } else { 11220 // remember path 11221 fonts[xres].setPath(path); 11222 } 11223 } catch (Exception e) { 11224 // oops; sorry 11225 } 11226 return xres; 11227 } 11228 11229 // first try direct path 11230 auto res = loadFontFile(path); 11231 // if loading failed, try fontconfig (if fontconfig is available) 11232 static if (NanoVegaHasFontConfig) { 11233 if (res == FONS_INVALID && fontconfigAvailable) { 11234 // idiotic fontconfig NEVER fails; let's skip it if `path` looks like a path 11235 bool ok = true; 11236 if (path.length > 4 && (path[$-4..$] == ".ttf" || path[$-4..$] == ".ttc")) ok = false; 11237 if (ok) { foreach (immutable char ch; path) if (ch == '/') { ok = false; break; } } 11238 if (ok) { 11239 import std.internal.cstring : tempCString; 11240 FcPattern* pat = FcNameParse(path.tempCString); 11241 if (pat !is null) { 11242 scope(exit) FcPatternDestroy(pat); 11243 if (FcConfigSubstitute(null, pat, FcMatchPattern)) { 11244 FcDefaultSubstitute(pat); 11245 // find the font 11246 FcResult result; 11247 FcPattern* font = FcFontMatch(null, pat, &result); 11248 if (font !is null) { 11249 scope(exit) FcPatternDestroy(font); 11250 char* file = null; 11251 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) { 11252 if (file !is null && file[0]) { 11253 import core.stdc.string : strlen; 11254 res = loadFontFile(file[0..strlen(file)]); 11255 } 11256 } 11257 } 11258 } 11259 } 11260 } 11261 } 11262 } 11263 return res; 11264 } 11265 11266 /** Add font to FontStash, using data from memory. 11267 * 11268 * And already loaded font to FontStash. You can replace existing logical fonts. 11269 * But note that you can't remove logical font by passing "empty" data. 11270 * 11271 * $(WARNING If [FONS_INVALID] returned, `data` won't be freed even if `freeData` is `true`.) 11272 * 11273 * Params: 11274 * name = logical font name, that will be used to select this font later. 11275 * data = font data. 11276 * dataSize = font data size. 11277 * freeData = should FontStash take ownership of the font data? 11278 * defAA = should FontStash use antialiased font rasterizer? 11279 * 11280 * Returns: 11281 * font id or [FONS_INVALID]. 11282 */ 11283 int addFontMem (const(char)[] name, ubyte* data, int dataSize, bool freeData, bool defAA=false) nothrow @trusted @nogc { 11284 if (data is null || dataSize < 16) return FONS_INVALID; 11285 FONSfontData* fdata = fons__createFontData(data, dataSize, freeData); 11286 fdata.incref(); 11287 auto res = addFontWithData(name, fdata, defAA); 11288 if (res == FONS_INVALID) { 11289 // we promised to not free data on error 11290 fdata.freeData = false; 11291 fdata.decref(); // this will free [fdata] 11292 } 11293 return res; 11294 } 11295 11296 /** Add fonts from another FontStash. 11297 * 11298 * This is more effective (and faster) than reloading fonts, because internally font data 11299 * is reference counted. 11300 */ 11301 void addFontsFrom (FONSContext source) nothrow @trusted @nogc { 11302 if (source is null) return; 11303 foreach (FONSfont* font; source.fonts[0..source.nfonts]) { 11304 if (font !is null) { 11305 auto newidx = addCookedFont(font); 11306 FONSfont* newfont = fonts[newidx]; 11307 assert(newfont !is null); 11308 assert(newfont.path is null); 11309 // copy path 11310 if (font.path !is null && font.path[0]) { 11311 import core.stdc.stdlib : malloc; 11312 import core.stdc.string : strcpy, strlen; 11313 newfont.path = cast(char*)malloc(strlen(font.path)+1); 11314 if (newfont.path is null) assert(0, "FONS: out of memory"); 11315 strcpy(newfont.path, font.path); 11316 } 11317 } 11318 } 11319 } 11320 11321 /// Returns logical font name corresponding to the given font id, or `null`. 11322 /// $(WARNING Copy returned name, as name buffer can be invalidated by next FontStash API call!) 11323 const(char)[] getNameById (int idx) const pure nothrow @trusted @nogc { 11324 if (idx < 0 || idx >= nfonts || fonts[idx] is null) return null; 11325 return fonts[idx].name[0..fonts[idx].namelen]; 11326 } 11327 11328 /// Returns font id corresponding to the given logical font name, or [FONS_INVALID]. 11329 int getFontByName (const(char)[] name) const pure nothrow @trusted @nogc { 11330 //{ import core.stdc.stdio; printf("fonsGetFontByName: [%.*s]\n", cast(uint)name.length, name.ptr); } 11331 // remove ":noaa" suffix 11332 if (name.length >= NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) { 11333 name = name[0..$-NoAlias.length]; 11334 } 11335 if (name.length == 0) return FONS_INVALID; 11336 return findNameInHash(name); 11337 } 11338 11339 /** Measures the specified text string. Parameter bounds should be a float[4], 11340 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax] 11341 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn). 11342 */ 11343 float getTextBounds(T) (float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc if (isAnyCharType!T) { 11344 FONSstate* state = getState; 11345 uint codepoint; 11346 uint utf8state = 0; 11347 FONSQuad q; 11348 FONSglyph* glyph = null; 11349 int prevGlyphIndex = -1; 11350 short isize = cast(short)(state.size*10.0f); 11351 short iblur = cast(short)state.blur; 11352 FONSfont* font; 11353 11354 if (state.font < 0 || state.font >= nfonts) return 0; 11355 font = fonts[state.font]; 11356 if (font is null || font.fdata is null) return 0; 11357 11358 float scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 11359 11360 // Align vertically. 11361 y += getVertAlign(font, state.talign, isize); 11362 11363 float minx = x, maxx = x; 11364 float miny = y, maxy = y; 11365 float startx = x; 11366 11367 foreach (T ch; str) { 11368 static if (T.sizeof == 1) { 11369 //if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue; 11370 mixin(DecUtfMixin!("utf8state", "codepoint", "(cast(ubyte)ch)")); 11371 if (utf8state) continue; 11372 } else { 11373 static if (T.sizeof == 4) { 11374 if (ch > dchar.max) ch = 0xFFFD; 11375 } 11376 codepoint = cast(uint)ch; 11377 } 11378 glyph = getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional); 11379 if (glyph !is null) { 11380 getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q); 11381 if (q.x0 < minx) minx = q.x0; 11382 if (q.x1 > maxx) maxx = q.x1; 11383 if (params.isZeroTopLeft) { 11384 if (q.y0 < miny) miny = q.y0; 11385 if (q.y1 > maxy) maxy = q.y1; 11386 } else { 11387 if (q.y1 < miny) miny = q.y1; 11388 if (q.y0 > maxy) maxy = q.y0; 11389 } 11390 prevGlyphIndex = glyph.index; 11391 } else { 11392 //{ import core.stdc.stdio; printf("NO GLYPH FOR 0x%04x\n", cast(uint)codepoint); } 11393 prevGlyphIndex = -1; 11394 } 11395 } 11396 11397 float advance = x-startx; 11398 //{ import core.stdc.stdio; printf("***: x=%g; startx=%g; advance=%g\n", cast(double)x, cast(double)startx, cast(double)advance); } 11399 11400 // Align horizontally 11401 if (state.talign.left) { 11402 // empty 11403 } else if (state.talign.right) { 11404 minx -= advance; 11405 maxx -= advance; 11406 } else if (state.talign.center) { 11407 minx -= advance*0.5f; 11408 maxx -= advance*0.5f; 11409 } 11410 11411 if (bounds.length) { 11412 if (bounds.length > 0) bounds.ptr[0] = minx; 11413 if (bounds.length > 1) bounds.ptr[1] = miny; 11414 if (bounds.length > 2) bounds.ptr[2] = maxx; 11415 if (bounds.length > 3) bounds.ptr[3] = maxy; 11416 } 11417 11418 return advance; 11419 } 11420 11421 /// Returns various font metrics. Any argument can be `null` if you aren't interested in its value. 11422 void getVertMetrics (float* ascender, float* descender, float* lineh) nothrow @trusted @nogc { 11423 FONSstate* state = getState; 11424 if (state.font < 0 || state.font >= nfonts) { 11425 if (ascender !is null) *ascender = 0; 11426 if (descender !is null) *descender = 0; 11427 if (lineh !is null) *lineh = 0; 11428 } else { 11429 FONSfont* font = fonts[state.font]; 11430 if (font is null || font.fdata is null) { 11431 if (ascender !is null) *ascender = 0; 11432 if (descender !is null) *descender = 0; 11433 if (lineh !is null) *lineh = 0; 11434 } else { 11435 short isize = cast(short)(state.size*10.0f); 11436 if (ascender !is null) *ascender = font.ascender*isize/10.0f; 11437 if (descender !is null) *descender = font.descender*isize/10.0f; 11438 if (lineh !is null) *lineh = font.lineh*isize/10.0f; 11439 } 11440 } 11441 } 11442 11443 /// Returns line bounds. Any argument can be `null` if you aren't interested in its value. 11444 void getLineBounds (float y, float* minyp, float* maxyp) nothrow @trusted @nogc { 11445 FONSfont* font; 11446 FONSstate* state = getState; 11447 short isize; 11448 11449 if (minyp !is null) *minyp = 0; 11450 if (maxyp !is null) *maxyp = 0; 11451 11452 if (state.font < 0 || state.font >= nfonts) return; 11453 font = fonts[state.font]; 11454 isize = cast(short)(state.size*10.0f); 11455 if (font is null || font.fdata is null) return; 11456 11457 y += getVertAlign(font, state.talign, isize); 11458 11459 if (params.isZeroTopLeft) { 11460 immutable float miny = y-font.ascender*cast(float)isize/10.0f; 11461 immutable float maxy = miny+font.lineh*isize/10.0f; 11462 if (minyp !is null) *minyp = miny; 11463 if (maxyp !is null) *maxyp = maxy; 11464 } else { 11465 immutable float maxy = y+font.descender*cast(float)isize/10.0f; 11466 immutable float miny = maxy-font.lineh*isize/10.0f; 11467 if (minyp !is null) *minyp = miny; 11468 if (maxyp !is null) *maxyp = maxy; 11469 } 11470 } 11471 11472 /// Returns font line height. 11473 float fontHeight () nothrow @trusted @nogc { 11474 float res = void; 11475 getVertMetrics(null, null, &res); 11476 return res; 11477 } 11478 11479 /// Returns font ascender (positive). 11480 float fontAscender () nothrow @trusted @nogc { 11481 float res = void; 11482 getVertMetrics(&res, null, null); 11483 return res; 11484 } 11485 11486 /// Returns font descender (negative). 11487 float fontDescender () nothrow @trusted @nogc { 11488 float res = void; 11489 getVertMetrics(null, &res, null); 11490 return res; 11491 } 11492 11493 //TODO: document this 11494 const(ubyte)* getTextureData (int* width, int* height) nothrow @trusted @nogc { 11495 if (width !is null) *width = params.width; 11496 if (height !is null) *height = params.height; 11497 return texData; 11498 } 11499 11500 //TODO: document this 11501 bool validateTexture (int* dirty) nothrow @trusted @nogc { 11502 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) { 11503 dirty[0] = dirtyRect.ptr[0]; 11504 dirty[1] = dirtyRect.ptr[1]; 11505 dirty[2] = dirtyRect.ptr[2]; 11506 dirty[3] = dirtyRect.ptr[3]; 11507 // reset dirty rect 11508 dirtyRect.ptr[0] = params.width; 11509 dirtyRect.ptr[1] = params.height; 11510 dirtyRect.ptr[2] = 0; 11511 dirtyRect.ptr[3] = 0; 11512 return true; 11513 } 11514 return false; 11515 } 11516 11517 //TODO: document this 11518 void errorCallback (void delegate (FONSError error, int val) nothrow @trusted @nogc callback) nothrow @trusted @nogc { 11519 handleError = callback; 11520 } 11521 11522 //TODO: document this 11523 void getAtlasSize (int* width, int* height) const pure nothrow @trusted @nogc { 11524 if (width !is null) *width = params.width; 11525 if (height !is null) *height = params.height; 11526 } 11527 11528 //TODO: document this 11529 bool expandAtlas (int width, int height) nothrow @trusted @nogc { 11530 import core.stdc.stdlib : free; 11531 import core.stdc.string : memcpy, memset; 11532 11533 int maxy = 0; 11534 ubyte* data = null; 11535 11536 width = nvg__max(width, params.width); 11537 height = nvg__max(height, params.height); 11538 11539 if (width == params.width && height == params.height) return true; 11540 11541 // Flush pending glyphs. 11542 flush(); 11543 11544 // Create new texture 11545 if (params.renderResize !is null) { 11546 if (params.renderResize(params.userPtr, width, height) == 0) return false; 11547 } 11548 // Copy old texture data over. 11549 data = cast(ubyte*)malloc(width*height); 11550 if (data is null) return 0; 11551 foreach (immutable int i; 0..params.height) { 11552 ubyte* dst = &data[i*width]; 11553 ubyte* src = &texData[i*params.width]; 11554 memcpy(dst, src, params.width); 11555 if (width > params.width) memset(dst+params.width, 0, width-params.width); 11556 } 11557 if (height > params.height) memset(&data[params.height*width], 0, (height-params.height)*width); 11558 11559 free(texData); 11560 texData = data; 11561 11562 // Increase atlas size 11563 atlas.expand(width, height); 11564 11565 // Add existing data as dirty. 11566 foreach (immutable int i; 0..atlas.nnodes) maxy = nvg__max(maxy, atlas.nodes[i].y); 11567 dirtyRect.ptr[0] = 0; 11568 dirtyRect.ptr[1] = 0; 11569 dirtyRect.ptr[2] = params.width; 11570 dirtyRect.ptr[3] = maxy; 11571 11572 params.width = width; 11573 params.height = height; 11574 itw = 1.0f/params.width; 11575 ith = 1.0f/params.height; 11576 11577 return true; 11578 } 11579 11580 //TODO: document this 11581 bool resetAtlas (int width, int height) nothrow @trusted @nogc { 11582 import core.stdc.stdlib : realloc; 11583 import core.stdc.string : memcpy, memset; 11584 11585 // flush pending glyphs 11586 flush(); 11587 11588 // create new texture 11589 if (params.renderResize !is null) { 11590 if (params.renderResize(params.userPtr, width, height) == 0) return false; 11591 } 11592 11593 // reset atlas 11594 atlas.reset(width, height); 11595 11596 // clear texture data 11597 texData = cast(ubyte*)realloc(texData, width*height); 11598 if (texData is null) assert(0, "FONS: out of memory"); 11599 memset(texData, 0, width*height); 11600 11601 // reset dirty rect 11602 dirtyRect.ptr[0] = width; 11603 dirtyRect.ptr[1] = height; 11604 dirtyRect.ptr[2] = 0; 11605 dirtyRect.ptr[3] = 0; 11606 11607 // Reset cached glyphs 11608 foreach (FONSfont* font; fonts[0..nfonts]) { 11609 if (font !is null) { 11610 font.nglyphs = 0; 11611 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; 11612 } 11613 } 11614 11615 params.width = width; 11616 params.height = height; 11617 itw = 1.0f/params.width; 11618 ith = 1.0f/params.height; 11619 11620 // Add white rect at 0, 0 for debug drawing. 11621 addWhiteRect(2, 2); 11622 11623 return true; 11624 } 11625 11626 //TODO: document this 11627 bool getPathBounds (dchar dch, float[] bounds) nothrow @trusted @nogc { 11628 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 11629 static if (is(typeof(&fons__nvg__bounds))) { 11630 FONSstate* state = getState; 11631 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; } 11632 FONSfont* font; 11633 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11634 if (g == 0) { bounds[] = 0; return false; } 11635 assert(font !is null); 11636 return fons__nvg__bounds(&font.font, g, bounds); 11637 } else { 11638 bounds[] = 0; 11639 return false; 11640 } 11641 } 11642 11643 //TODO: document this 11644 bool toPath() (NVGContext vg, dchar dch, float[] bounds=null) nothrow @trusted @nogc { 11645 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 11646 static if (is(typeof(&fons__nvg__toPath))) { 11647 if (vg is null) { bounds[] = 0; return false; } 11648 FONSstate* state = getState; 11649 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; } 11650 FONSfont* font; 11651 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11652 if (g == 0) { bounds[] = 0; return false; } 11653 assert(font !is null); 11654 return fons__nvg__toPath(vg, &font.font, g, bounds); 11655 } else { 11656 bounds[] = 0; 11657 return false; 11658 } 11659 } 11660 11661 //TODO: document this 11662 bool toOutline (dchar dch, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 11663 if (ol is null) return false; 11664 static if (is(typeof(&fons__nvg__toOutline))) { 11665 FONSstate* state = getState; 11666 if (state.font < 0 || state.font >= nfonts) return false; 11667 FONSfont* font; 11668 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11669 if (g == 0) return false; 11670 assert(font !is null); 11671 return fons__nvg__toOutline(&font.font, g, ol); 11672 } else { 11673 return false; 11674 } 11675 } 11676 11677 //TODO: document this 11678 FONSglyph* getGlyph (FONSfont* font, uint codepoint, short isize, short iblur, FONSBitmapFlag bitmapOption) nothrow @trusted @nogc { 11679 static uint fons__hashint() (uint a) pure nothrow @safe @nogc { 11680 pragma(inline, true); 11681 a += ~(a<<15); 11682 a ^= (a>>10); 11683 a += (a<<3); 11684 a ^= (a>>6); 11685 a += ~(a<<11); 11686 a ^= (a>>16); 11687 return a; 11688 } 11689 11690 // based on Exponential blur, Jani Huhtanen, 2006 11691 enum APREC = 16; 11692 enum ZPREC = 7; 11693 11694 static void fons__blurCols (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc { 11695 foreach (immutable int y; 0..h) { 11696 int z = 0; // force zero border 11697 foreach (int x; 1..w) { 11698 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC; 11699 dst[x] = cast(ubyte)(z>>ZPREC); 11700 } 11701 dst[w-1] = 0; // force zero border 11702 z = 0; 11703 for (int x = w-2; x >= 0; --x) { 11704 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC; 11705 dst[x] = cast(ubyte)(z>>ZPREC); 11706 } 11707 dst[0] = 0; // force zero border 11708 dst += dstStride; 11709 } 11710 } 11711 11712 static void fons__blurRows (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc { 11713 foreach (immutable int x; 0..w) { 11714 int z = 0; // force zero border 11715 for (int y = dstStride; y < h*dstStride; y += dstStride) { 11716 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC; 11717 dst[y] = cast(ubyte)(z>>ZPREC); 11718 } 11719 dst[(h-1)*dstStride] = 0; // force zero border 11720 z = 0; 11721 for (int y = (h-2)*dstStride; y >= 0; y -= dstStride) { 11722 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC; 11723 dst[y] = cast(ubyte)(z>>ZPREC); 11724 } 11725 dst[0] = 0; // force zero border 11726 ++dst; 11727 } 11728 } 11729 11730 static void fons__blur (ubyte* dst, int w, int h, int dstStride, int blur) nothrow @trusted @nogc { 11731 import std.math : expf = exp; 11732 if (blur < 1) return; 11733 // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity) 11734 immutable float sigma = cast(float)blur*0.57735f; // 1/sqrt(3) 11735 int alpha = cast(int)((1<<APREC)*(1.0f-expf(-2.3f/(sigma+1.0f)))); 11736 fons__blurRows(dst, w, h, dstStride, alpha); 11737 fons__blurCols(dst, w, h, dstStride, alpha); 11738 fons__blurRows(dst, w, h, dstStride, alpha); 11739 fons__blurCols(dst, w, h, dstStride, alpha); 11740 //fons__blurrows(dst, w, h, dstStride, alpha); 11741 //fons__blurcols(dst, w, h, dstStride, alpha); 11742 } 11743 11744 int advance, lsb, x0, y0, x1, y1, gx, gy; 11745 FONSglyph* glyph = null; 11746 float size = isize/10.0f; 11747 FONSfont* renderFont = font; 11748 11749 if (isize < 2) return null; 11750 if (iblur > 20) iblur = 20; 11751 int pad = iblur+2; 11752 11753 // Reset allocator. 11754 nscratch = 0; 11755 11756 // Find code point and size. 11757 uint h = fons__hashint(codepoint)&(FONS_HASH_LUT_SIZE-1); 11758 int i = font.lut.ptr[h]; 11759 while (i != -1) { 11760 //if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) return &font.glyphs[i]; 11761 if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) { 11762 glyph = &font.glyphs[i]; 11763 // Negative coordinate indicates there is no bitmap data created. 11764 if (bitmapOption == FONSBitmapFlag.Optional || (glyph.x0 >= 0 && glyph.y0 >= 0)) return glyph; 11765 // At this point, glyph exists but the bitmap data is not yet created. 11766 break; 11767 } 11768 i = font.glyphs[i].next; 11769 } 11770 11771 // Create a new glyph or rasterize bitmap data for a cached glyph. 11772 //scale = fons__tt_getPixelHeightScale(&font.font, size); 11773 int g = findGlyphForCP(font, cast(dchar)codepoint, &renderFont); 11774 // It is possible that we did not find a fallback glyph. 11775 // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph. 11776 11777 float scale = fons__tt_getPixelHeightScale(&renderFont.font, size); 11778 fons__tt_buildGlyphBitmap(&renderFont.font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1); 11779 int gw = x1-x0+pad*2; 11780 int gh = y1-y0+pad*2; 11781 11782 // Determines the spot to draw glyph in the atlas. 11783 if (bitmapOption == FONSBitmapFlag.Required) { 11784 // Find free spot for the rect in the atlas. 11785 bool added = atlas.addRect(gw, gh, &gx, &gy); 11786 if (!added && handleError !is null) { 11787 // Atlas is full, let the user to resize the atlas (or not), and try again. 11788 handleError(FONSError.AtlasFull, 0); 11789 added = atlas.addRect(gw, gh, &gx, &gy); 11790 } 11791 if (!added) return null; 11792 } else { 11793 // Negative coordinate indicates there is no bitmap data created. 11794 gx = -1; 11795 gy = -1; 11796 } 11797 11798 // Init glyph. 11799 if (glyph is null) { 11800 glyph = font.allocGlyph(); 11801 glyph.codepoint = codepoint; 11802 glyph.size = isize; 11803 glyph.blur = iblur; 11804 glyph.next = 0; 11805 11806 // Insert char to hash lookup. 11807 glyph.next = font.lut.ptr[h]; 11808 font.lut.ptr[h] = font.nglyphs-1; 11809 } 11810 glyph.index = g; 11811 glyph.x0 = cast(short)gx; 11812 glyph.y0 = cast(short)gy; 11813 glyph.x1 = cast(short)(glyph.x0+gw); 11814 glyph.y1 = cast(short)(glyph.y0+gh); 11815 glyph.xadv = cast(short)(scale*advance*10.0f); 11816 glyph.xoff = cast(short)(x0-pad); 11817 glyph.yoff = cast(short)(y0-pad); 11818 11819 if (bitmapOption == FONSBitmapFlag.Optional) return glyph; 11820 11821 // Rasterize 11822 ubyte* dst = &texData[(glyph.x0+pad)+(glyph.y0+pad)*params.width]; 11823 fons__tt_renderGlyphBitmap(&font.font, dst, gw-pad*2, gh-pad*2, params.width, scale, scale, g); 11824 11825 // Make sure there is one pixel empty border. 11826 dst = &texData[glyph.x0+glyph.y0*params.width]; 11827 foreach (immutable int y; 0..gh) { 11828 dst[y*params.width] = 0; 11829 dst[gw-1+y*params.width] = 0; 11830 } 11831 foreach (immutable int x; 0..gw) { 11832 dst[x] = 0; 11833 dst[x+(gh-1)*params.width] = 0; 11834 } 11835 11836 // Debug code to color the glyph background 11837 version(none) { 11838 foreach (immutable yy; 0..gh) { 11839 foreach (immutable xx; 0..gw) { 11840 int a = cast(int)dst[xx+yy*params.width]+42; 11841 if (a > 255) a = 255; 11842 dst[xx+yy*params.width] = cast(ubyte)a; 11843 } 11844 } 11845 } 11846 11847 // Blur 11848 if (iblur > 0) { 11849 nscratch = 0; 11850 ubyte* bdst = &texData[glyph.x0+glyph.y0*params.width]; 11851 fons__blur(bdst, gw, gh, params.width, iblur); 11852 } 11853 11854 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], glyph.x0); 11855 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], glyph.y0); 11856 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], glyph.x1); 11857 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], glyph.y1); 11858 11859 return glyph; 11860 } 11861 11862 //TODO: document this 11863 void getQuad (FONSfont* font, int prevGlyphIndex, FONSglyph* glyph, float size, float scale, float spacing, float* x, float* y, FONSQuad* q) nothrow @trusted @nogc { 11864 if (prevGlyphIndex >= 0) { 11865 immutable float adv = fons__tt_getGlyphKernAdvance(&font.font, size, prevGlyphIndex, glyph.index)/**scale*/; //k8: do we really need scale here? 11866 //if (adv != 0) { import core.stdc.stdio; printf("adv=%g (scale=%g; spacing=%g)\n", cast(double)adv, cast(double)scale, cast(double)spacing); } 11867 *x += cast(int)(adv+spacing /*+0.5f*/); //k8: for me, it looks better this way (with non-aa fonts) 11868 } 11869 11870 // Each glyph has 2px border to allow good interpolation, 11871 // one pixel to prevent leaking, and one to allow good interpolation for rendering. 11872 // Inset the texture region by one pixel for correct interpolation. 11873 immutable float xoff = cast(short)(glyph.xoff+1); 11874 immutable float yoff = cast(short)(glyph.yoff+1); 11875 immutable float x0 = cast(float)(glyph.x0+1); 11876 immutable float y0 = cast(float)(glyph.y0+1); 11877 immutable float x1 = cast(float)(glyph.x1-1); 11878 immutable float y1 = cast(float)(glyph.y1-1); 11879 11880 if (params.isZeroTopLeft) { 11881 immutable float rx = cast(float)cast(int)(*x+xoff); 11882 immutable float ry = cast(float)cast(int)(*y+yoff); 11883 11884 q.x0 = rx; 11885 q.y0 = ry; 11886 q.x1 = rx+x1-x0; 11887 q.y1 = ry+y1-y0; 11888 11889 q.s0 = x0*itw; 11890 q.t0 = y0*ith; 11891 q.s1 = x1*itw; 11892 q.t1 = y1*ith; 11893 } else { 11894 immutable float rx = cast(float)cast(int)(*x+xoff); 11895 immutable float ry = cast(float)cast(int)(*y-yoff); 11896 11897 q.x0 = rx; 11898 q.y0 = ry; 11899 q.x1 = rx+x1-x0; 11900 q.y1 = ry-y1+y0; 11901 11902 q.s0 = x0*itw; 11903 q.t0 = y0*ith; 11904 q.s1 = x1*itw; 11905 q.t1 = y1*ith; 11906 } 11907 11908 *x += cast(int)(glyph.xadv/10.0f+0.5f); 11909 } 11910 11911 void flush () nothrow @trusted @nogc { 11912 // flush texture 11913 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) { 11914 if (params.renderUpdate !is null) params.renderUpdate(params.userPtr, dirtyRect.ptr, texData); 11915 // reset dirty rect 11916 dirtyRect.ptr[0] = params.width; 11917 dirtyRect.ptr[1] = params.height; 11918 dirtyRect.ptr[2] = 0; 11919 dirtyRect.ptr[3] = 0; 11920 } 11921 } 11922 } 11923 11924 /// Free all resources used by the `stash`, and `stash` itself. 11925 /// Group: font_stash 11926 public void kill (ref FONSContext stash) nothrow @trusted @nogc { 11927 import core.stdc.stdlib : free; 11928 if (stash is null) return; 11929 stash.clear(); 11930 free(stash); 11931 stash = null; 11932 } 11933 11934 11935 // ////////////////////////////////////////////////////////////////////////// // 11936 void* fons__tmpalloc (usize size, void* up) nothrow @trusted @nogc { 11937 ubyte* ptr; 11938 FONSContext stash = cast(FONSContext)up; 11939 // 16-byte align the returned pointer 11940 size = (size+0xf)&~0xf; 11941 if (stash.nscratch+cast(int)size > FONS_SCRATCH_BUF_SIZE) { 11942 if (stash.handleError !is null) stash.handleError(FONSError.ScratchFull, stash.nscratch+cast(int)size); 11943 return null; 11944 } 11945 ptr = stash.scratch+stash.nscratch; 11946 stash.nscratch += cast(int)size; 11947 return ptr; 11948 } 11949 11950 void fons__tmpfree (void* ptr, void* up) nothrow @trusted @nogc { 11951 // empty 11952 } 11953 11954 11955 // ////////////////////////////////////////////////////////////////////////// // 11956 // Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de> 11957 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. 11958 11959 enum FONS_UTF8_ACCEPT = 0; 11960 enum FONS_UTF8_REJECT = 12; 11961 11962 static immutable ubyte[364] utf8d = [ 11963 // The first part of the table maps bytes to character classes that 11964 // to reduce the size of the transition table and create bitmasks. 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 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, 11969 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, 11970 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, 11971 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, 11972 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, 11973 11974 // The second part is a transition table that maps a combination 11975 // of a state of the automaton and a character class to a state. 11976 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 11977 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, 11978 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 11979 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 11980 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 11981 ]; 11982 11983 private enum DecUtfMixin(string state, string codep, string byte_) = 11984 `{ 11985 uint type_ = utf8d.ptr[`~byte_~`]; 11986 `~codep~` = (`~state~` != FONS_UTF8_ACCEPT ? (`~byte_~`&0x3fu)|(`~codep~`<<6) : (0xff>>type_)&`~byte_~`); 11987 if ((`~state~` = utf8d.ptr[256+`~state~`+type_]) == FONS_UTF8_REJECT) { 11988 `~state~` = FONS_UTF8_ACCEPT; 11989 `~codep~` = 0xFFFD; 11990 } 11991 }`; 11992 11993 /* 11994 uint fons__decutf8 (uint* state, uint* codep, uint byte_) { 11995 pragma(inline, true); 11996 uint type = utf8d.ptr[byte_]; 11997 *codep = (*state != FONS_UTF8_ACCEPT ? (byte_&0x3fu)|(*codep<<6) : (0xff>>type)&byte_); 11998 *state = utf8d.ptr[256 + *state+type]; 11999 return *state; 12000 } 12001 */ 12002 12003 12004 // ////////////////////////////////////////////////////////////////////////// // 12005 /// This iterator can be used to do text measurement. 12006 /// $(WARNING Don't add new fonts to stash while you are iterating, or you WILL get segfault!) 12007 /// Group: font_stash 12008 public struct FONSTextBoundsIterator { 12009 private: 12010 FONSContext stash; 12011 FONSstate state; 12012 uint codepoint = 0xFFFD; 12013 uint utf8state = 0; 12014 int prevGlyphIndex = -1; 12015 short isize, iblur; 12016 float scale = 0; 12017 FONSfont* font; 12018 float startx = 0, x = 0, y = 0; 12019 float minx = 0, miny = 0, maxx = 0, maxy = 0; 12020 12021 private: 12022 void clear () nothrow @trusted @nogc { 12023 import core.stdc.string : memset; 12024 memset(&this, 0, this.sizeof); 12025 this.prevGlyphIndex = -1; 12026 this.codepoint = 0xFFFD; 12027 } 12028 12029 public: 12030 /// Initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator. 12031 this (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { reset(astash, ax, ay); } 12032 12033 /// (Re)initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator. 12034 void reset (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { 12035 clear(); 12036 12037 if (astash is null || astash.nstates == 0) return; 12038 12039 stash = astash; 12040 state = *stash.getState; 12041 12042 if (state.font < 0 || state.font >= stash.nfonts) { clear(); return; } 12043 font = stash.fonts[state.font]; 12044 if (font is null || font.fdata is null) { clear(); return; } 12045 12046 x = ax; 12047 y = ay; 12048 isize = cast(short)(state.size*10.0f); 12049 iblur = cast(short)state.blur; 12050 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 12051 12052 // align vertically 12053 y += astash.getVertAlign(font, state.talign, isize); 12054 12055 minx = maxx = x; 12056 miny = maxy = y; 12057 startx = x; 12058 } 12059 12060 /// Can this iterator be used? 12061 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null); } 12062 12063 /// Put some text into iterator, calculate new values. 12064 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { 12065 enum DoCodePointMixin = q{ 12066 glyph = stash.getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional); 12067 if (glyph !is null) { 12068 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q); 12069 if (q.x0 < minx) minx = q.x0; 12070 if (q.x1 > maxx) maxx = q.x1; 12071 if (stash.params.isZeroTopLeft) { 12072 if (q.y0 < miny) miny = q.y0; 12073 if (q.y1 > maxy) maxy = q.y1; 12074 } else { 12075 if (q.y1 < miny) miny = q.y1; 12076 if (q.y0 > maxy) maxy = q.y0; 12077 } 12078 prevGlyphIndex = glyph.index; 12079 } else { 12080 prevGlyphIndex = -1; 12081 } 12082 }; 12083 12084 if (stash is null || str.length == 0) return; // alas 12085 12086 FONSQuad q; 12087 FONSglyph* glyph; 12088 12089 static if (is(T == char)) { 12090 foreach (char ch; str) { 12091 mixin(DecUtfMixin!("utf8state", "codepoint", "cast(ubyte)ch")); 12092 if (utf8state) continue; // full char is not collected yet 12093 mixin(DoCodePointMixin); 12094 } 12095 } else { 12096 if (utf8state) { 12097 utf8state = 0; 12098 codepoint = 0xFFFD; 12099 mixin(DoCodePointMixin); 12100 } 12101 foreach (T dch; str) { 12102 static if (is(T == dchar)) { 12103 if (dch > dchar.max) dch = 0xFFFD; 12104 } 12105 codepoint = cast(uint)dch; 12106 mixin(DoCodePointMixin); 12107 } 12108 } 12109 } 12110 12111 /// Returns current advance. 12112 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null ? x-startx : 0); } 12113 12114 /// Returns current text bounds. 12115 void getBounds (ref float[4] bounds) const pure nothrow @safe @nogc { 12116 if (stash is null) { bounds[] = 0; return; } 12117 float lminx = minx, lmaxx = maxx; 12118 // align horizontally 12119 if (state.talign.left) { 12120 // empty 12121 } else if (state.talign.right) { 12122 float ca = advance; 12123 lminx -= ca; 12124 lmaxx -= ca; 12125 } else if (state.talign.center) { 12126 float ca = advance*0.5f; 12127 lminx -= ca; 12128 lmaxx -= ca; 12129 } 12130 bounds[0] = lminx; 12131 bounds[1] = miny; 12132 bounds[2] = lmaxx; 12133 bounds[3] = maxy; 12134 } 12135 12136 /// Returns current horizontal text bounds. 12137 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc { 12138 if (stash !is null) { 12139 float lminx = minx, lmaxx = maxx; 12140 // align horizontally 12141 if (state.talign.left) { 12142 // empty 12143 } else if (state.talign.right) { 12144 float ca = advance; 12145 lminx -= ca; 12146 lmaxx -= ca; 12147 } else if (state.talign.center) { 12148 float ca = advance*0.5f; 12149 lminx -= ca; 12150 lmaxx -= ca; 12151 } 12152 xmin = lminx; 12153 xmax = lmaxx; 12154 } else { 12155 xmin = xmax = 0; 12156 } 12157 } 12158 12159 /// Returns current vertical text bounds. 12160 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc { 12161 pragma(inline, true); 12162 if (stash !is null) { 12163 ymin = miny; 12164 ymax = maxy; 12165 } else { 12166 ymin = ymax = 0; 12167 } 12168 } 12169 12170 /// Returns font line height. 12171 float lineHeight () nothrow @trusted @nogc { 12172 pragma(inline, true); 12173 return (stash !is null ? stash.fonts[state.font].lineh*cast(short)(state.size*10.0f)/10.0f : 0); 12174 } 12175 12176 /// Returns font ascender (positive). 12177 float ascender () nothrow @trusted @nogc { 12178 pragma(inline, true); 12179 return (stash !is null ? stash.fonts[state.font].ascender*cast(short)(state.size*10.0f)/10.0f : 0); 12180 } 12181 12182 /// Returns font descender (negative). 12183 float descender () nothrow @trusted @nogc { 12184 pragma(inline, true); 12185 return (stash !is null ? stash.fonts[state.font].descender*cast(short)(state.size*10.0f)/10.0f : 0); 12186 } 12187 } 12188 12189 12190 // ////////////////////////////////////////////////////////////////////////// // 12191 // backgl 12192 // ////////////////////////////////////////////////////////////////////////// // 12193 import core.stdc.stdlib : malloc, realloc, free; 12194 import core.stdc.string : memcpy, memset; 12195 12196 static if (__VERSION__ < 2076) { 12197 private auto DGNoThrowNoGC(T) (scope T t) /*if (isFunctionPointer!T || isDelegate!T)*/ { 12198 import std.traits; 12199 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_; 12200 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 12201 } 12202 } 12203 12204 12205 //import arsd.simpledisplay; 12206 version(nanovg_bindbc_opengl_bindings) { 12207 import bindbc.opengl; 12208 } else version(nanovg_builtin_opengl_bindings) { 12209 import arsd.simpledisplay; 12210 12211 /++ 12212 A SimpleWindow subclass that encapsulates some nanovega defaults. You just set a `redrawNVGScene` delegate and, optionally, your normal event handlers for simpledisplay, and the rest is set up for you. 12213 12214 History: 12215 Added January 22, 2021 (version 9.2 release) 12216 +/ 12217 public class NVGWindow : SimpleWindow { 12218 NVGContext nvg; 12219 12220 /++ 12221 12222 +/ 12223 this(int width, int height, string title) { 12224 setOpenGLContextVersion(3, 0); 12225 super(width, height, title, OpenGlOptions.yes, Resizability.allowResizing); 12226 12227 this.onClosing = delegate() { 12228 nvg.kill(); 12229 }; 12230 12231 this.visibleForTheFirstTime = delegate() { 12232 this.setAsCurrentOpenGlContext(); 12233 nvg = nvgCreateContext(); 12234 if(nvg is null) throw new Exception("cannot initialize NanoVega"); 12235 }; 12236 12237 this.redrawOpenGlScene = delegate() { 12238 if(redrawNVGScene is null) 12239 return; 12240 glViewport(0, 0, this.width, this.height); 12241 if(clearOnEachFrame) { 12242 glClearColor(0, 0, 0, 0); 12243 glClear(glNVGClearFlags); 12244 } 12245 12246 nvg.beginFrame(this.width, this.height); 12247 scope(exit) nvg.endFrame(); 12248 12249 redrawNVGScene(nvg); 12250 }; 12251 12252 this.setEventHandlers( 12253 &redrawOpenGlSceneNow, 12254 (KeyEvent ke) { 12255 if(ke.key == Key.Escape || ke.key == Key.Q) 12256 this.close(); 12257 } 12258 ); 12259 } 12260 12261 /++ 12262 12263 +/ 12264 bool clearOnEachFrame = true; 12265 12266 /++ 12267 12268 +/ 12269 void delegate(NVGContext nvg) redrawNVGScene; 12270 12271 /++ 12272 12273 +/ 12274 void redrawNVGSceneNow() { 12275 redrawOpenGlSceneNow(); 12276 } 12277 } 12278 12279 } else { 12280 import iv.glbinds; 12281 } 12282 12283 private: 12284 // sdpy is missing that yet 12285 static if (!is(typeof(GL_STENCIL_BUFFER_BIT))) enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 12286 12287 12288 12289 version(bindbc){ 12290 private extern(System) nothrow @nogc: 12291 // this definition doesn't exist in regular OpenGL (?) 12292 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 12293 private void nanovgInitOpenGL () { 12294 // i'm not aware of calling the load multiple times having negative side effects, so i don't do an initialization check 12295 GLSupport support = loadOpenGL(); 12296 if (support == GLSupport.noLibrary) 12297 assert(0, "OpenGL initialization failed: shared library failed to load"); 12298 else if (support == GLSupport.badLibrary) 12299 assert(0, "OpenGL initialization failed: a context-independent symbol failed to load"); 12300 else if (support == GLSupport.noContext) 12301 assert(0, "OpenGL initialization failed: a context needs to be created prior to initialization"); 12302 } 12303 } else { // OpenGL API missing from simpledisplay 12304 private void nanovgInitOpenGL () @nogc nothrow { 12305 __gshared bool initialized = false; 12306 if (initialized) return; 12307 12308 try 12309 gl3.loadDynamicLibrary(); 12310 catch(Exception) 12311 assert(0, "GL 3 failed to load"); 12312 12313 initialized = true; 12314 } 12315 } 12316 12317 12318 12319 /// Context creation flags. 12320 /// Group: context_management 12321 public enum NVGContextFlag : int { 12322 /// Nothing special, i.e. empty flag. 12323 None = 0, 12324 /// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA). 12325 Antialias = 1U<<0, 12326 /** Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little 12327 * slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. */ 12328 StencilStrokes = 1U<<1, 12329 /// Flag indicating that additional debug checks are done. 12330 Debug = 1U<<2, 12331 /// Filter (antialias) fonts 12332 FontAA = 1U<<7, 12333 /// Don't filter (antialias) fonts 12334 FontNoAA = 1U<<8, 12335 /// You can use this as a substitute for default flags, for cases like this: `nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);`. 12336 Default = 1U<<31, 12337 } 12338 12339 public enum NANOVG_GL_USE_STATE_FILTER = true; 12340 12341 /// Returns flags for glClear(). 12342 /// Group: context_management 12343 public uint glNVGClearFlags () pure nothrow @safe @nogc { 12344 pragma(inline, true); 12345 return (GL_COLOR_BUFFER_BIT|/*GL_DEPTH_BUFFER_BIT|*/GL_STENCIL_BUFFER_BIT); 12346 } 12347 12348 12349 // ////////////////////////////////////////////////////////////////////////// // 12350 private: 12351 12352 version = nanovega_shared_stencil; 12353 //version = nanovega_debug_clipping; 12354 12355 enum GLNVGuniformLoc { 12356 ViewSize, 12357 Tex, 12358 Frag, 12359 TMat, 12360 TTr, 12361 ClipTex, 12362 } 12363 12364 alias GLNVGshaderType = int; 12365 enum /*GLNVGshaderType*/ { 12366 NSVG_SHADER_FILLCOLOR, 12367 NSVG_SHADER_FILLGRAD, 12368 NSVG_SHADER_FILLIMG, 12369 NSVG_SHADER_SIMPLE, // also used for clipfill 12370 NSVG_SHADER_IMG, 12371 } 12372 12373 struct GLNVGshader { 12374 GLuint prog; 12375 GLuint frag; 12376 GLuint vert; 12377 GLint[GLNVGuniformLoc.max+1] loc; 12378 } 12379 12380 struct GLNVGtexture { 12381 int id; 12382 GLuint tex; 12383 int width, height; 12384 NVGtexture type; 12385 int flags; 12386 shared int rc; // this can be 0 with tex != 0 -- postponed deletion 12387 int nextfree; 12388 } 12389 12390 struct GLNVGblend { 12391 bool simple; 12392 GLenum srcRGB; 12393 GLenum dstRGB; 12394 GLenum srcAlpha; 12395 GLenum dstAlpha; 12396 } 12397 12398 alias GLNVGcallType = int; 12399 enum /*GLNVGcallType*/ { 12400 GLNVG_NONE = 0, 12401 GLNVG_FILL, 12402 GLNVG_CONVEXFILL, 12403 GLNVG_STROKE, 12404 GLNVG_TRIANGLES, 12405 GLNVG_AFFINE, // change affine transformation matrix 12406 GLNVG_PUSHCLIP, 12407 GLNVG_POPCLIP, 12408 GLNVG_RESETCLIP, 12409 GLNVG_CLIP_DDUMP_ON, 12410 GLNVG_CLIP_DDUMP_OFF, 12411 } 12412 12413 struct GLNVGcall { 12414 int type; 12415 int evenOdd; // for fill 12416 int image; 12417 int pathOffset; 12418 int pathCount; 12419 int triangleOffset; 12420 int triangleCount; 12421 int uniformOffset; 12422 NVGMatrix affine; 12423 GLNVGblend blendFunc; 12424 NVGClipMode clipmode; 12425 } 12426 12427 struct GLNVGpath { 12428 int fillOffset; 12429 int fillCount; 12430 int strokeOffset; 12431 int strokeCount; 12432 } 12433 12434 align(1) struct GLNVGfragUniforms { 12435 align(1): 12436 enum UNIFORM_ARRAY_SIZE = 13; 12437 // note: after modifying layout or size of uniform array, 12438 // don't forget to also update the fragment shader source! 12439 align(1) union { 12440 align(1): 12441 align(1) struct { 12442 align(1): 12443 float[12] scissorMat; // matrices are actually 3 vec4s 12444 float[12] paintMat; 12445 NVGColor innerCol; 12446 NVGColor middleCol; 12447 NVGColor outerCol; 12448 float[2] scissorExt; 12449 float[2] scissorScale; 12450 float[2] extent; 12451 float radius; 12452 float feather; 12453 float strokeMult; 12454 float strokeThr; 12455 float texType; 12456 float type; 12457 float doclip; 12458 float midp; // for gradients 12459 float unused2, unused3; 12460 } 12461 float[4][UNIFORM_ARRAY_SIZE] uniformArray; 12462 } 12463 } 12464 12465 enum GLMaskState { 12466 DontMask = -1, 12467 Uninitialized = 0, 12468 Initialized = 1, 12469 JustCleared = 2, 12470 } 12471 12472 import core.sync.mutex; 12473 __gshared Mutex GLNVGTextureLocker; 12474 shared static this() { 12475 GLNVGTextureLocker = new Mutex(); 12476 } 12477 12478 struct GLNVGcontext { 12479 private import core.thread : ThreadID; 12480 12481 GLNVGshader shader; 12482 GLNVGtexture* textures; 12483 float[2] view; 12484 int freetexid; // -1: none 12485 int ntextures; 12486 int ctextures; 12487 GLuint vertBuf; 12488 int fragSize; 12489 int flags; 12490 // FBOs for masks 12491 GLuint[NVG_MAX_STATES] fbo; 12492 GLuint[2][NVG_MAX_STATES] fboTex; // FBO textures: [0] is color, [1] is stencil 12493 int fboWidth, fboHeight; 12494 GLMaskState[NVG_MAX_STATES] maskStack; 12495 int msp; // mask stack pointer; starts from `0`; points to next free item; see below for logic description 12496 int lastClipFBO; // -666: cache invalidated; -1: don't mask 12497 int lastClipUniOfs; 12498 bool doClipUnion; // specal mode 12499 GLNVGshader shaderFillFBO; 12500 GLNVGshader shaderCopyFBO; 12501 12502 bool inFrame; // will be `true` if we can perform OpenGL operations (used in texture deletion) 12503 shared bool mustCleanTextures; // will be `true` if we should delete some textures 12504 ThreadID mainTID; 12505 uint mainFBO; 12506 12507 // Per frame buffers 12508 GLNVGcall* calls; 12509 int ccalls; 12510 int ncalls; 12511 GLNVGpath* paths; 12512 int cpaths; 12513 int npaths; 12514 NVGVertex* verts; 12515 int cverts; 12516 int nverts; 12517 ubyte* uniforms; 12518 int cuniforms; 12519 int nuniforms; 12520 NVGMatrix lastAffine; 12521 12522 // cached state 12523 static if (NANOVG_GL_USE_STATE_FILTER) { 12524 GLuint boundTexture; 12525 GLuint stencilMask; 12526 GLenum stencilFunc; 12527 GLint stencilFuncRef; 12528 GLuint stencilFuncMask; 12529 GLNVGblend blendFunc; 12530 } 12531 } 12532 12533 int glnvg__maxi() (int a, int b) { pragma(inline, true); return (a > b ? a : b); } 12534 12535 void glnvg__bindTexture (GLNVGcontext* gl, GLuint tex) nothrow @trusted @nogc { 12536 static if (NANOVG_GL_USE_STATE_FILTER) { 12537 if (gl.boundTexture != tex) { 12538 gl.boundTexture = tex; 12539 glBindTexture(GL_TEXTURE_2D, tex); 12540 } 12541 } else { 12542 glBindTexture(GL_TEXTURE_2D, tex); 12543 } 12544 } 12545 12546 void glnvg__stencilMask (GLNVGcontext* gl, GLuint mask) nothrow @trusted @nogc { 12547 static if (NANOVG_GL_USE_STATE_FILTER) { 12548 if (gl.stencilMask != mask) { 12549 gl.stencilMask = mask; 12550 glStencilMask(mask); 12551 } 12552 } else { 12553 glStencilMask(mask); 12554 } 12555 } 12556 12557 void glnvg__stencilFunc (GLNVGcontext* gl, GLenum func, GLint ref_, GLuint mask) nothrow @trusted @nogc { 12558 static if (NANOVG_GL_USE_STATE_FILTER) { 12559 if (gl.stencilFunc != func || gl.stencilFuncRef != ref_ || gl.stencilFuncMask != mask) { 12560 gl.stencilFunc = func; 12561 gl.stencilFuncRef = ref_; 12562 gl.stencilFuncMask = mask; 12563 glStencilFunc(func, ref_, mask); 12564 } 12565 } else { 12566 glStencilFunc(func, ref_, mask); 12567 } 12568 } 12569 12570 // texture id is never zero 12571 // sets refcount to one 12572 GLNVGtexture* glnvg__allocTexture (GLNVGcontext* gl) nothrow @trusted @nogc { 12573 GLNVGtexture* tex = null; 12574 12575 int tid = gl.freetexid; 12576 if (tid == -1) { 12577 if (gl.ntextures >= gl.ctextures) { 12578 assert(gl.ntextures == gl.ctextures); 12579 //pragma(msg, GLNVGtexture.sizeof*32); 12580 int ctextures = (gl.ctextures == 0 ? 32 : gl.ctextures+gl.ctextures/2); // 1.5x overallocate 12581 GLNVGtexture* textures = cast(GLNVGtexture*)realloc(gl.textures, GLNVGtexture.sizeof*ctextures); 12582 if (textures is null) assert(0, "NanoVega: out of memory for textures"); 12583 memset(&textures[gl.ctextures], 0, (ctextures-gl.ctextures)*GLNVGtexture.sizeof); 12584 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated more textures (n=%d; c=%d; nc=%d)\n", gl.ntextures, gl.ctextures, ctextures); }} 12585 gl.textures = textures; 12586 gl.ctextures = ctextures; 12587 } 12588 assert(gl.ntextures+1 <= gl.ctextures); 12589 tid = gl.ntextures++; 12590 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf(" got next free texture id %d, ntextures=%d\n", tid+1, gl.ntextures); }} 12591 } else { 12592 gl.freetexid = gl.textures[tid].nextfree; 12593 } 12594 assert(tid <= gl.ntextures); 12595 12596 assert(gl.textures[tid].id == 0); 12597 tex = &gl.textures[tid]; 12598 memset(tex, 0, (*tex).sizeof); 12599 tex.id = tid+1; 12600 tex.rc = 1; 12601 tex.nextfree = -1; 12602 12603 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated texture with id %d (%d)\n", tex.id, tid+1); }} 12604 12605 return tex; 12606 } 12607 12608 GLNVGtexture* glnvg__findTexture (GLNVGcontext* gl, int id) nothrow @trusted @nogc { 12609 if (id <= 0 || id > gl.ntextures) return null; 12610 if (gl.textures[id-1].id == 0) return null; // free one 12611 assert(gl.textures[id-1].id == id); 12612 return &gl.textures[id-1]; 12613 } 12614 12615 bool glnvg__deleteTexture (GLNVGcontext* gl, ref int id) nothrow @trusted @nogc { 12616 if (id <= 0 || id > gl.ntextures) return false; 12617 auto tx = &gl.textures[id-1]; 12618 if (tx.id == 0) { id = 0; return false; } // free one 12619 assert(tx.id == id); 12620 assert(tx.tex != 0); 12621 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("decrefing texture with id %d (%d)\n", tx.id, id); }} 12622 import core.atomic : atomicOp; 12623 if (atomicOp!"-="(tx.rc, 1) == 0) { 12624 import core.thread : ThreadID; 12625 ThreadID mytid; 12626 static if (__VERSION__ < 2076) { 12627 DGNoThrowNoGC(() { 12628 import core.thread; mytid = Thread.getThis.id; 12629 })(); 12630 } else { 12631 try { import core.thread; mytid = Thread.getThis.id; } catch (Exception e) {} 12632 } 12633 if (gl.mainTID == mytid && gl.inFrame) { 12634 // can delete it right now 12635 if ((tx.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tx.tex); 12636 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** deleted texture with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }} 12637 memset(tx, 0, (*tx).sizeof); 12638 //{ import core.stdc.stdio; printf("deleting texture with id %d\n", id); } 12639 tx.nextfree = gl.freetexid; 12640 gl.freetexid = id-1; 12641 } else { 12642 // alas, we aren't doing frame business, so we should postpone deletion 12643 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** POSTPONED texture deletion with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }} 12644 version(aliced) { 12645 { 12646 GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow; 12647 tx.id = 0; // mark it as dead 12648 gl.mustCleanTextures = true; // set "need cleanup" flag 12649 } 12650 } else { 12651 try { 12652 GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow; 12653 tx.id = 0; // mark it as dead 12654 gl.mustCleanTextures = true; // set "need cleanup" flag 12655 } catch (Exception e) {} 12656 } 12657 } 12658 } 12659 id = 0; 12660 return true; 12661 } 12662 12663 void glnvg__dumpShaderError (GLuint shader, const(char)* name, const(char)* type) nothrow @trusted @nogc { 12664 import core.stdc.stdio : fprintf, stderr; 12665 GLchar[512+1] str = 0; 12666 GLsizei len = 0; 12667 glGetShaderInfoLog(shader, 512, &len, str.ptr); 12668 if (len > 512) len = 512; 12669 str[len] = '\0'; 12670 fprintf(stderr, "Shader %s/%s error:\n%s\n", name, type, str.ptr); 12671 } 12672 12673 void glnvg__dumpProgramError (GLuint prog, const(char)* name) nothrow @trusted @nogc { 12674 import core.stdc.stdio : fprintf, stderr; 12675 GLchar[512+1] str = 0; 12676 GLsizei len = 0; 12677 glGetProgramInfoLog(prog, 512, &len, str.ptr); 12678 if (len > 512) len = 512; 12679 str[len] = '\0'; 12680 fprintf(stderr, "Program %s error:\n%s\n", name, str.ptr); 12681 } 12682 12683 void glnvg__resetError(bool force=false) (GLNVGcontext* gl) nothrow @trusted @nogc { 12684 static if (!force) { 12685 if ((gl.flags&NVGContextFlag.Debug) == 0) return; 12686 } 12687 glGetError(); 12688 } 12689 12690 void glnvg__checkError(bool force=false) (GLNVGcontext* gl, const(char)* str) nothrow @trusted @nogc { 12691 GLenum err; 12692 static if (!force) { 12693 if ((gl.flags&NVGContextFlag.Debug) == 0) return; 12694 } 12695 err = glGetError(); 12696 if (err != GL_NO_ERROR) { 12697 import core.stdc.stdio : fprintf, stderr; 12698 fprintf(stderr, "Error %08x after %s\n", err, str); 12699 return; 12700 } 12701 } 12702 12703 bool glnvg__createShader (GLNVGshader* shader, const(char)* name, const(char)* header, const(char)* opts, const(char)* vshader, const(char)* fshader) nothrow @trusted @nogc { 12704 GLint status; 12705 GLuint prog, vert, frag; 12706 const(char)*[3] str; 12707 12708 memset(shader, 0, (*shader).sizeof); 12709 12710 prog = glCreateProgram(); 12711 vert = glCreateShader(GL_VERTEX_SHADER); 12712 frag = glCreateShader(GL_FRAGMENT_SHADER); 12713 str[0] = header; 12714 str[1] = (opts !is null ? opts : ""); 12715 str[2] = vshader; 12716 glShaderSource(vert, 3, cast(const(char)**)str.ptr, null); 12717 12718 glCompileShader(vert); 12719 glGetShaderiv(vert, GL_COMPILE_STATUS, &status); 12720 if (status != GL_TRUE) { 12721 glnvg__dumpShaderError(vert, name, "vert"); 12722 return false; 12723 } 12724 12725 str[0] = header; 12726 str[1] = (opts !is null ? opts : ""); 12727 str[2] = fshader; 12728 glShaderSource(frag, 3, cast(const(char)**)str.ptr, null); 12729 12730 glCompileShader(frag); 12731 glGetShaderiv(frag, GL_COMPILE_STATUS, &status); 12732 if (status != GL_TRUE) { 12733 glnvg__dumpShaderError(frag, name, "frag"); 12734 return false; 12735 } 12736 12737 glAttachShader(prog, vert); 12738 glAttachShader(prog, frag); 12739 12740 glBindAttribLocation(prog, 0, "vertex"); 12741 glBindAttribLocation(prog, 1, "tcoord"); 12742 12743 glLinkProgram(prog); 12744 glGetProgramiv(prog, GL_LINK_STATUS, &status); 12745 if (status != GL_TRUE) { 12746 glnvg__dumpProgramError(prog, name); 12747 return false; 12748 } 12749 12750 shader.prog = prog; 12751 shader.vert = vert; 12752 shader.frag = frag; 12753 12754 return true; 12755 } 12756 12757 void glnvg__deleteShader (GLNVGshader* shader) nothrow @trusted @nogc { 12758 if (shader.prog != 0) glDeleteProgram(shader.prog); 12759 if (shader.vert != 0) glDeleteShader(shader.vert); 12760 if (shader.frag != 0) glDeleteShader(shader.frag); 12761 } 12762 12763 void glnvg__getUniforms (GLNVGshader* shader) nothrow @trusted @nogc { 12764 shader.loc[GLNVGuniformLoc.ViewSize] = glGetUniformLocation(shader.prog, "viewSize"); 12765 shader.loc[GLNVGuniformLoc.Tex] = glGetUniformLocation(shader.prog, "tex"); 12766 shader.loc[GLNVGuniformLoc.Frag] = glGetUniformLocation(shader.prog, "frag"); 12767 shader.loc[GLNVGuniformLoc.TMat] = glGetUniformLocation(shader.prog, "tmat"); 12768 shader.loc[GLNVGuniformLoc.TTr] = glGetUniformLocation(shader.prog, "ttr"); 12769 shader.loc[GLNVGuniformLoc.ClipTex] = glGetUniformLocation(shader.prog, "clipTex"); 12770 } 12771 12772 void glnvg__killFBOs (GLNVGcontext* gl) nothrow @trusted @nogc { 12773 foreach (immutable fidx, ref GLuint fbo; gl.fbo[]) { 12774 if (fbo != 0) { 12775 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); 12776 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); 12777 foreach (ref GLuint tid; gl.fboTex.ptr[fidx][]) if (tid != 0) { glDeleteTextures(1, &tid); tid = 0; } 12778 glDeleteFramebuffers(1, &fbo); 12779 fbo = 0; 12780 } 12781 } 12782 gl.fboWidth = gl.fboHeight = 0; 12783 } 12784 12785 // returns `true` is new FBO was created 12786 // will not unbind buffer, if it was created 12787 bool glnvg__allocFBO (GLNVGcontext* gl, int fidx, bool doclear=true) nothrow @trusted @nogc { 12788 assert(fidx >= 0 && fidx < gl.fbo.length); 12789 assert(gl.fboWidth > 0); 12790 assert(gl.fboHeight > 0); 12791 12792 if (gl.fbo.ptr[fidx] != 0) return false; // nothing to do, this FBO is already initialized 12793 12794 glnvg__resetError(gl); 12795 12796 // allocate FBO object 12797 GLuint fbo = 0; 12798 glGenFramebuffers(1, &fbo); 12799 if (fbo == 0) assert(0, "NanoVega: cannot create FBO"); 12800 glnvg__checkError(gl, "glnvg__allocFBO: glGenFramebuffers"); 12801 glBindFramebuffer(GL_FRAMEBUFFER, fbo); 12802 //scope(exit) glBindFramebuffer(GL_FRAMEBUFFER, 0); 12803 12804 // attach 2D texture to this FBO 12805 GLuint tidColor = 0; 12806 glGenTextures(1, &tidColor); 12807 if (tidColor == 0) assert(0, "NanoVega: cannot create RGBA texture for FBO"); 12808 glBindTexture(GL_TEXTURE_2D, tidColor); 12809 //scope(exit) glBindTexture(GL_TEXTURE_2D, 0); 12810 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 12811 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_S"); 12812 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 12813 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_T"); 12814 //FIXME: linear or nearest? 12815 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 12816 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 12817 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MIN_FILTER"); 12818 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 12819 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 12820 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MAG_FILTER"); 12821 // empty texture 12822 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 12823 // create texture with only one color channel 12824 glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, gl.fboWidth, gl.fboHeight, 0, GL_RED, GL_UNSIGNED_BYTE, null); 12825 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 12826 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (color)"); 12827 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tidColor, 0); 12828 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (color)"); 12829 12830 // attach stencil texture to this FBO 12831 GLuint tidStencil = 0; 12832 version(nanovega_shared_stencil) { 12833 if (gl.fboTex.ptr[0].ptr[0] == 0) { 12834 glGenTextures(1, &tidStencil); 12835 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO"); 12836 gl.fboTex.ptr[0].ptr[0] = tidStencil; 12837 } else { 12838 tidStencil = gl.fboTex.ptr[0].ptr[0]; 12839 } 12840 if (fidx != 0) gl.fboTex.ptr[fidx].ptr[1] = 0; // stencil texture is shared among FBOs 12841 } else { 12842 glGenTextures(1, &tidStencil); 12843 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO"); 12844 gl.fboTex.ptr[0].ptr[0] = tidStencil; 12845 } 12846 glBindTexture(GL_TEXTURE_2D, tidStencil); 12847 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, gl.fboWidth, gl.fboHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, null); 12848 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (stencil)"); 12849 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tidStencil, 0); 12850 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (stencil)"); 12851 12852 { 12853 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); 12854 if (status != GL_FRAMEBUFFER_COMPLETE) { 12855 version(all) { 12856 import core.stdc.stdio; 12857 if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) printf("fucked attachement\n"); 12858 if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) printf("fucked dimensions\n"); 12859 if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) printf("missing attachement\n"); 12860 if (status == GL_FRAMEBUFFER_UNSUPPORTED) printf("unsupported\n"); 12861 } 12862 assert(0, "NanoVega: framebuffer creation failed"); 12863 } 12864 } 12865 12866 // clear 'em all 12867 if (doclear) { 12868 glClearColor(0, 0, 0, 0); 12869 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 12870 } 12871 12872 // save texture ids 12873 gl.fbo.ptr[fidx] = fbo; 12874 gl.fboTex.ptr[fidx].ptr[0] = tidColor; 12875 version(nanovega_shared_stencil) {} else { 12876 gl.fboTex.ptr[fidx].ptr[1] = tidStencil; 12877 } 12878 12879 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture); 12880 12881 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): created with index %d\n", gl.msp-1, fidx); } 12882 12883 return true; 12884 } 12885 12886 // will not unbind buffer 12887 void glnvg__clearFBO (GLNVGcontext* gl, int fidx) nothrow @trusted @nogc { 12888 assert(fidx >= 0 && fidx < gl.fbo.length); 12889 assert(gl.fboWidth > 0); 12890 assert(gl.fboHeight > 0); 12891 assert(gl.fbo.ptr[fidx] != 0); 12892 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[fidx]); 12893 glClearColor(0, 0, 0, 0); 12894 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 12895 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cleared with index %d\n", gl.msp-1, fidx); } 12896 } 12897 12898 // will not unbind buffer 12899 void glnvg__copyFBOToFrom (GLNVGcontext* gl, int didx, int sidx) nothrow @trusted @nogc { 12900 import core.stdc.string : memset; 12901 assert(didx >= 0 && didx < gl.fbo.length); 12902 assert(sidx >= 0 && sidx < gl.fbo.length); 12903 assert(gl.fboWidth > 0); 12904 assert(gl.fboHeight > 0); 12905 assert(gl.fbo.ptr[didx] != 0); 12906 assert(gl.fbo.ptr[sidx] != 0); 12907 if (didx == sidx) return; 12908 12909 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copy FBO: %d -> %d\n", gl.msp-1, sidx, didx); } 12910 12911 glUseProgram(gl.shaderCopyFBO.prog); 12912 12913 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[didx]); 12914 glDisable(GL_CULL_FACE); 12915 glDisable(GL_BLEND); 12916 glDisable(GL_SCISSOR_TEST); 12917 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[sidx].ptr[0]); 12918 // copy texture by drawing full quad 12919 enum x = 0; 12920 enum y = 0; 12921 immutable int w = gl.fboWidth; 12922 immutable int h = gl.fboHeight; 12923 immutable(NVGVertex[4]) vertices = 12924 [NVGVertex(x, y), // top-left 12925 NVGVertex(w, y), // top-right 12926 NVGVertex(w, h), // bottom-right 12927 NVGVertex(x, h)]; // bottom-left 12928 12929 glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices); 12930 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 12931 12932 // restore state (but don't unbind FBO) 12933 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture); 12934 glEnable(GL_CULL_FACE); 12935 glEnable(GL_BLEND); 12936 glUseProgram(gl.shader.prog); 12937 } 12938 12939 void glnvg__resetFBOClipTextureCache (GLNVGcontext* gl) nothrow @trusted @nogc { 12940 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): texture cache invalidated (%d)\n", gl.msp-1, gl.lastClipFBO); } 12941 /* 12942 if (gl.lastClipFBO >= 0) { 12943 glActiveTexture(GL_TEXTURE1); 12944 glBindTexture(GL_TEXTURE_2D, 0); 12945 glActiveTexture(GL_TEXTURE0); 12946 } 12947 */ 12948 gl.lastClipFBO = -666; 12949 } 12950 12951 void glnvg__setFBOClipTexture (GLNVGcontext* gl, GLNVGfragUniforms* frag) nothrow @trusted @nogc { 12952 //assert(gl.msp > 0 && gl.msp <= gl.maskStack.length); 12953 if (gl.lastClipFBO != -666) { 12954 // cached 12955 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cached (%d)\n", gl.msp-1, gl.lastClipFBO); } 12956 frag.doclip = (gl.lastClipFBO >= 0 ? 1 : 0); 12957 return; 12958 } 12959 12960 // no cache 12961 int fboidx = -1; 12962 mainloop: foreach_reverse (immutable sp, GLMaskState mst; gl.maskStack.ptr[0..gl.msp]/*; reverse*/) { 12963 final switch (mst) { 12964 case GLMaskState.DontMask: fboidx = -1; break mainloop; 12965 case GLMaskState.Uninitialized: break; 12966 case GLMaskState.Initialized: fboidx = cast(int)sp; break mainloop; 12967 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__setFBOClipTexture()` internal error"); 12968 } 12969 } 12970 12971 if (fboidx < 0) { 12972 // don't mask 12973 gl.lastClipFBO = -1; 12974 frag.doclip = 0; 12975 } else { 12976 // do masking 12977 assert(gl.fbo.ptr[fboidx] != 0); 12978 gl.lastClipFBO = fboidx; 12979 frag.doclip = 1; 12980 } 12981 12982 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): new cache (new:%d)\n", gl.msp-1, gl.lastClipFBO); } 12983 12984 if (gl.lastClipFBO >= 0) { 12985 assert(gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 12986 glActiveTexture(GL_TEXTURE1); 12987 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 12988 glActiveTexture(GL_TEXTURE0); 12989 } 12990 } 12991 12992 // returns index in `gl.fbo`, or -1 for "don't mask" 12993 int glnvg__generateFBOClipTexture (GLNVGcontext* gl) nothrow @trusted @nogc { 12994 assert(gl.msp > 0 && gl.msp <= gl.maskStack.length); 12995 // we need initialized FBO, even for "don't mask" case 12996 // for this, look back in stack, and either copy initialized FBO, 12997 // or stop at first uninitialized one, and clear it 12998 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.Initialized) { 12999 // shortcut 13000 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); } 13001 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[gl.msp-1]); 13002 return gl.msp-1; 13003 } 13004 foreach_reverse (immutable sp; 0..gl.msp/*; reverse*/) { 13005 final switch (gl.maskStack.ptr[sp]) { 13006 case GLMaskState.DontMask: 13007 // clear it 13008 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture\n", gl.msp-1); } 13009 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1); 13010 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared; 13011 return gl.msp-1; 13012 case GLMaskState.Uninitialized: break; // do nothing 13013 case GLMaskState.Initialized: 13014 // i found her! copy to TOS 13015 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copying texture from %d\n", gl.msp-1, cast(int)sp); } 13016 glnvg__allocFBO(gl, gl.msp-1, false); 13017 glnvg__copyFBOToFrom(gl, gl.msp-1, sp); 13018 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized; 13019 return gl.msp-1; 13020 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__generateFBOClipTexture()` internal error"); 13021 } 13022 } 13023 // nothing was initialized, lol 13024 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture (first one)\n", gl.msp-1); } 13025 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1); 13026 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared; 13027 return gl.msp-1; 13028 } 13029 13030 void glnvg__renderPushClip (void* uptr) nothrow @trusted @nogc { 13031 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13032 GLNVGcall* call = glnvg__allocCall(gl); 13033 if (call is null) return; 13034 call.type = GLNVG_PUSHCLIP; 13035 } 13036 13037 void glnvg__renderPopClip (void* uptr) nothrow @trusted @nogc { 13038 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13039 GLNVGcall* call = glnvg__allocCall(gl); 13040 if (call is null) return; 13041 call.type = GLNVG_POPCLIP; 13042 } 13043 13044 void glnvg__renderResetClip (void* uptr) nothrow @trusted @nogc { 13045 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13046 GLNVGcall* call = glnvg__allocCall(gl); 13047 if (call is null) return; 13048 call.type = GLNVG_RESETCLIP; 13049 } 13050 13051 void glnvg__clipDebugDump (void* uptr, bool doit) nothrow @trusted @nogc { 13052 version(nanovega_debug_clipping) { 13053 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13054 GLNVGcall* call = glnvg__allocCall(gl); 13055 call.type = (doit ? GLNVG_CLIP_DDUMP_ON : GLNVG_CLIP_DDUMP_OFF); 13056 } 13057 } 13058 13059 bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc { 13060 import core.stdc.stdio : snprintf; 13061 13062 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13063 enum align_ = 4; 13064 13065 char[64] shaderHeader = void; 13066 //enum shaderHeader = "#define UNIFORM_ARRAY_SIZE 12\n"; 13067 snprintf(shaderHeader.ptr, shaderHeader.length, "#define UNIFORM_ARRAY_SIZE %u\n", cast(uint)GLNVGfragUniforms.UNIFORM_ARRAY_SIZE); 13068 13069 enum fillVertShader = q{ 13070 uniform vec2 viewSize; 13071 attribute vec2 vertex; 13072 attribute vec2 tcoord; 13073 varying vec2 ftcoord; 13074 varying vec2 fpos; 13075 uniform vec4 tmat; /* abcd of affine matrix: xyzw */ 13076 uniform vec2 ttr; /* tx and ty of affine matrix */ 13077 void main (void) { 13078 /* affine transformation */ 13079 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x; 13080 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y; 13081 ftcoord = tcoord; 13082 fpos = vec2(nx, ny); 13083 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1); 13084 } 13085 }; 13086 13087 enum fillFragShader = ` 13088 uniform vec4 frag[UNIFORM_ARRAY_SIZE]; 13089 uniform sampler2D tex; 13090 uniform sampler2D clipTex; 13091 uniform vec2 viewSize; 13092 varying vec2 ftcoord; 13093 varying vec2 fpos; 13094 #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz) 13095 #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz) 13096 #define innerCol frag[6] 13097 #define middleCol frag[7] 13098 #define outerCol frag[7+1] 13099 #define scissorExt frag[8+1].xy 13100 #define scissorScale frag[8+1].zw 13101 #define extent frag[9+1].xy 13102 #define radius frag[9+1].z 13103 #define feather frag[9+1].w 13104 #define strokeMult frag[10+1].x 13105 #define strokeThr frag[10+1].y 13106 #define texType int(frag[10+1].z) 13107 #define type int(frag[10+1].w) 13108 #define doclip int(frag[11+1].x) 13109 #define midp frag[11+1].y 13110 13111 float sdroundrect (in vec2 pt, in vec2 ext, in float rad) { 13112 vec2 ext2 = ext-vec2(rad, rad); 13113 vec2 d = abs(pt)-ext2; 13114 return min(max(d.x, d.y), 0.0)+length(max(d, 0.0))-rad; 13115 } 13116 13117 // Scissoring 13118 float scissorMask (in vec2 p) { 13119 vec2 sc = (abs((scissorMat*vec3(p, 1.0)).xy)-scissorExt); 13120 sc = vec2(0.5, 0.5)-sc*scissorScale; 13121 return clamp(sc.x, 0.0, 1.0)*clamp(sc.y, 0.0, 1.0); 13122 } 13123 13124 #ifdef EDGE_AA 13125 // Stroke - from [0..1] to clipped pyramid, where the slope is 1px. 13126 float strokeMask () { 13127 return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult)*min(1.0, ftcoord.y); 13128 } 13129 #endif 13130 13131 void main (void) { 13132 // clipping 13133 if (doclip != 0) { 13134 /*vec4 clr = texelFetch(clipTex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);*/ 13135 vec4 clr = texture2D(clipTex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y)); 13136 if (clr.r == 0.0) discard; 13137 } 13138 float scissor = scissorMask(fpos); 13139 if (scissor <= 0.0) discard; //k8: is it really faster? 13140 #ifdef EDGE_AA 13141 float strokeAlpha = strokeMask(); 13142 if (strokeAlpha < strokeThr) discard; 13143 #else 13144 float strokeAlpha = 1.0; 13145 #endif 13146 // rendering 13147 vec4 color; 13148 if (type == 0) { /* NSVG_SHADER_FILLCOLOR */ 13149 color = innerCol; 13150 // Combine alpha 13151 color *= strokeAlpha*scissor; 13152 } else if (type == 1) { /* NSVG_SHADER_FILLGRAD */ 13153 // Gradient 13154 // Calculate gradient color using box gradient 13155 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy; 13156 float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0); 13157 if (midp <= 0.0) { 13158 color = mix(innerCol, outerCol, d); 13159 } else { 13160 float gdst = min(midp, 1.0); 13161 if (d < gdst) { 13162 color = mix(innerCol, middleCol, d/gdst); 13163 } else { 13164 color = mix(middleCol, outerCol, (d-gdst)/gdst); 13165 } 13166 } 13167 // Combine alpha 13168 color *= strokeAlpha*scissor; 13169 } else if (type == 2) { /* NSVG_SHADER_FILLIMG */ 13170 // Image 13171 // Calculate color from texture 13172 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy/extent; 13173 color = texture2D(tex, pt); 13174 if (texType == 1) color = vec4(color.xyz*color.w, color.w); 13175 if (texType == 2) color = vec4(color.x); 13176 // Apply color tint and alpha 13177 color *= innerCol; 13178 // Combine alpha 13179 color *= strokeAlpha*scissor; 13180 } else if (type == 3) { /* NSVG_SHADER_SIMPLE */ 13181 // Stencil fill 13182 color = vec4(1, 1, 1, 1); 13183 } else if (type == 4) { /* NSVG_SHADER_IMG */ 13184 // Textured tris 13185 color = texture2D(tex, ftcoord); 13186 if (texType == 1) color = vec4(color.xyz*color.w, color.w); 13187 if (texType == 2) color = vec4(color.x); 13188 color *= scissor; 13189 color *= innerCol; // Apply color tint 13190 } 13191 gl_FragColor = color; 13192 } 13193 `; 13194 13195 enum clipVertShaderFill = q{ 13196 uniform vec2 viewSize; 13197 attribute vec2 vertex; 13198 uniform vec4 tmat; /* abcd of affine matrix: xyzw */ 13199 uniform vec2 ttr; /* tx and ty of affine matrix */ 13200 void main (void) { 13201 /* affine transformation */ 13202 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x; 13203 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y; 13204 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1); 13205 } 13206 }; 13207 13208 enum clipFragShaderFill = q{ 13209 uniform vec2 viewSize; 13210 void main (void) { 13211 gl_FragColor = vec4(1, 1, 1, 1); 13212 } 13213 }; 13214 13215 enum clipVertShaderCopy = q{ 13216 uniform vec2 viewSize; 13217 attribute vec2 vertex; 13218 void main (void) { 13219 gl_Position = vec4(2.0*vertex.x/viewSize.x-1.0, 1.0-2.0*vertex.y/viewSize.y, 0, 1); 13220 } 13221 }; 13222 13223 enum clipFragShaderCopy = q{ 13224 uniform sampler2D tex; 13225 uniform vec2 viewSize; 13226 void main (void) { 13227 //gl_FragColor = texelFetch(tex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0); 13228 gl_FragColor = texture2D(tex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y)); 13229 } 13230 }; 13231 13232 glnvg__checkError(gl, "init"); 13233 13234 string defines = (gl.flags&NVGContextFlag.Antialias ? "#define EDGE_AA 1\n" : null); 13235 if (!glnvg__createShader(&gl.shader, "shader", shaderHeader.ptr, defines.ptr, fillVertShader, fillFragShader)) return false; 13236 if (!glnvg__createShader(&gl.shaderFillFBO, "shaderFillFBO", shaderHeader.ptr, defines.ptr, clipVertShaderFill, clipFragShaderFill)) return false; 13237 if (!glnvg__createShader(&gl.shaderCopyFBO, "shaderCopyFBO", shaderHeader.ptr, defines.ptr, clipVertShaderCopy, clipFragShaderCopy)) return false; 13238 13239 glnvg__checkError(gl, "uniform locations"); 13240 glnvg__getUniforms(&gl.shader); 13241 glnvg__getUniforms(&gl.shaderFillFBO); 13242 glnvg__getUniforms(&gl.shaderCopyFBO); 13243 13244 // Create dynamic vertex array 13245 glGenBuffers(1, &gl.vertBuf); 13246 13247 gl.fragSize = GLNVGfragUniforms.sizeof+align_-GLNVGfragUniforms.sizeof%align_; 13248 13249 glnvg__checkError(gl, "create done"); 13250 13251 glFinish(); 13252 13253 return true; 13254 } 13255 13256 int glnvg__renderCreateTexture (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc { 13257 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13258 GLNVGtexture* tex = glnvg__allocTexture(gl); 13259 13260 if (tex is null) return 0; 13261 13262 glGenTextures(1, &tex.tex); 13263 tex.width = w; 13264 tex.height = h; 13265 tex.type = type; 13266 tex.flags = imageFlags; 13267 glnvg__bindTexture(gl, tex.tex); 13268 13269 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("created texture with id %d; glid=%u\n", tex.id, tex.tex); }} 13270 13271 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 13272 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width); 13273 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13274 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13275 13276 13277 13278 immutable ttype = (type == NVGtexture.RGBA ? GL_RGBA : GL_RED); 13279 glTexImage2D(GL_TEXTURE_2D, 0, ttype, w, h, 0, ttype, GL_UNSIGNED_BYTE, data); 13280 // GL 3.0 and later have support for a dedicated function for generating mipmaps 13281 // it needs to be called after the glTexImage2D call 13282 if (imageFlags & NVGImageFlag.GenerateMipmaps) 13283 glGenerateMipmap(GL_TEXTURE_2D); 13284 13285 immutable tfmin = 13286 (imageFlags & NVGImageFlag.GenerateMipmaps ? GL_LINEAR_MIPMAP_LINEAR : 13287 imageFlags & NVGImageFlag.NoFiltering ? GL_NEAREST : 13288 GL_LINEAR); 13289 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.0); 13290 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tfmin); 13291 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST : GL_LINEAR)); 13292 13293 int flag; 13294 if (imageFlags&NVGImageFlag.RepeatX) 13295 flag = GL_REPEAT; 13296 else if (imageFlags&NVGImageFlag.ClampToBorderX) 13297 flag = GL_CLAMP_TO_BORDER; 13298 else 13299 flag = GL_CLAMP_TO_EDGE; 13300 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, flag); 13301 13302 13303 if (imageFlags&NVGImageFlag.RepeatY) 13304 flag = GL_REPEAT; 13305 else if (imageFlags&NVGImageFlag.ClampToBorderY) 13306 flag = GL_CLAMP_TO_BORDER; 13307 else 13308 flag = GL_CLAMP_TO_EDGE; 13309 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, flag); 13310 13311 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 13312 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 13313 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13314 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13315 13316 glnvg__checkError(gl, "create tex"); 13317 glnvg__bindTexture(gl, 0); 13318 13319 return tex.id; 13320 } 13321 13322 bool glnvg__renderDeleteTexture (void* uptr, int image) nothrow @trusted @nogc { 13323 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13324 return glnvg__deleteTexture(gl, image); 13325 } 13326 13327 bool glnvg__renderTextureIncRef (void* uptr, int image) nothrow @trusted @nogc { 13328 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13329 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13330 if (tex is null) { 13331 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT incref texture with id %d\n", image); }} 13332 return false; 13333 } 13334 import core.atomic : atomicOp; 13335 atomicOp!"+="(tex.rc, 1); 13336 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("texture #%d: incref; newref=%d\n", image, tex.rc); }} 13337 return true; 13338 } 13339 13340 bool glnvg__renderUpdateTexture (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc { 13341 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13342 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13343 13344 if (tex is null) { 13345 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT update texture with id %d\n", image); }} 13346 return false; 13347 } 13348 13349 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("updated texture with id %d; glid=%u\n", tex.id, image, tex.tex); }} 13350 13351 glnvg__bindTexture(gl, tex.tex); 13352 13353 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 13354 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width); 13355 glPixelStorei(GL_UNPACK_SKIP_PIXELS, x); 13356 glPixelStorei(GL_UNPACK_SKIP_ROWS, y); 13357 13358 immutable ttype = (tex.type == NVGtexture.RGBA ? GL_RGBA : GL_RED); 13359 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, ttype, GL_UNSIGNED_BYTE, data); 13360 13361 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 13362 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 13363 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13364 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13365 13366 glnvg__bindTexture(gl, 0); 13367 13368 return true; 13369 } 13370 13371 bool glnvg__renderGetTextureSize (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc { 13372 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13373 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13374 if (tex is null) { 13375 if (w !is null) *w = 0; 13376 if (h !is null) *h = 0; 13377 return false; 13378 } else { 13379 if (w !is null) *w = tex.width; 13380 if (h !is null) *h = tex.height; 13381 return true; 13382 } 13383 } 13384 13385 void glnvg__xformToMat3x4 (float[] m3, const(float)[] t) nothrow @trusted @nogc { 13386 assert(t.length >= 6); 13387 assert(m3.length >= 12); 13388 m3.ptr[0] = t.ptr[0]; 13389 m3.ptr[1] = t.ptr[1]; 13390 m3.ptr[2] = 0.0f; 13391 m3.ptr[3] = 0.0f; 13392 m3.ptr[4] = t.ptr[2]; 13393 m3.ptr[5] = t.ptr[3]; 13394 m3.ptr[6] = 0.0f; 13395 m3.ptr[7] = 0.0f; 13396 m3.ptr[8] = t.ptr[4]; 13397 m3.ptr[9] = t.ptr[5]; 13398 m3.ptr[10] = 1.0f; 13399 m3.ptr[11] = 0.0f; 13400 } 13401 13402 NVGColor glnvg__premulColor() (const scope auto ref NVGColor c) nothrow @trusted @nogc { 13403 //pragma(inline, true); 13404 NVGColor res = void; 13405 res.r = c.r*c.a; 13406 res.g = c.g*c.a; 13407 res.b = c.b*c.a; 13408 res.a = c.a; 13409 return res; 13410 } 13411 13412 bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* paint, NVGscissor* scissor, float width, float fringe, float strokeThr) nothrow @trusted @nogc { 13413 import core.stdc.math : sqrtf; 13414 GLNVGtexture* tex = null; 13415 NVGMatrix invxform = void; 13416 13417 memset(frag, 0, (*frag).sizeof); 13418 13419 frag.innerCol = glnvg__premulColor(paint.innerColor); 13420 frag.middleCol = glnvg__premulColor(paint.middleColor); 13421 frag.outerCol = glnvg__premulColor(paint.outerColor); 13422 frag.midp = paint.midp; 13423 13424 if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) { 13425 memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof); 13426 frag.scissorExt.ptr[0] = 1.0f; 13427 frag.scissorExt.ptr[1] = 1.0f; 13428 frag.scissorScale.ptr[0] = 1.0f; 13429 frag.scissorScale.ptr[1] = 1.0f; 13430 } else { 13431 //nvgTransformInverse(invxform[], scissor.xform[]); 13432 invxform = scissor.xform.inverted; 13433 glnvg__xformToMat3x4(frag.scissorMat[], invxform.mat[]); 13434 frag.scissorExt.ptr[0] = scissor.extent.ptr[0]; 13435 frag.scissorExt.ptr[1] = scissor.extent.ptr[1]; 13436 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; 13437 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; 13438 } 13439 13440 memcpy(frag.extent.ptr, paint.extent.ptr, frag.extent.sizeof); 13441 frag.strokeMult = (width*0.5f+fringe*0.5f)/fringe; 13442 frag.strokeThr = strokeThr; 13443 13444 if (paint.image.valid) { 13445 tex = glnvg__findTexture(gl, paint.image.id); 13446 if (tex is null) return false; 13447 if ((tex.flags&NVGImageFlag.FlipY) != 0) { 13448 /* 13449 NVGMatrix flipped; 13450 nvgTransformScale(flipped[], 1.0f, -1.0f); 13451 nvgTransformMultiply(flipped[], paint.xform[]); 13452 nvgTransformInverse(invxform[], flipped[]); 13453 */ 13454 /* 13455 NVGMatrix m1 = void, m2 = void; 13456 nvgTransformTranslate(m1[], 0.0f, frag.extent.ptr[1]*0.5f); 13457 nvgTransformMultiply(m1[], paint.xform[]); 13458 nvgTransformScale(m2[], 1.0f, -1.0f); 13459 nvgTransformMultiply(m2[], m1[]); 13460 nvgTransformTranslate(m1[], 0.0f, -frag.extent.ptr[1]*0.5f); 13461 nvgTransformMultiply(m1[], m2[]); 13462 nvgTransformInverse(invxform[], m1[]); 13463 */ 13464 NVGMatrix m1 = NVGMatrix.Translated(0.0f, frag.extent.ptr[1]*0.5f); 13465 m1.mul(paint.xform); 13466 NVGMatrix m2 = NVGMatrix.Scaled(1.0f, -1.0f); 13467 m2.mul(m1); 13468 m1 = NVGMatrix.Translated(0.0f, -frag.extent.ptr[1]*0.5f); 13469 m1.mul(m2); 13470 invxform = m1.inverted; 13471 } else { 13472 //nvgTransformInverse(invxform[], paint.xform[]); 13473 invxform = paint.xform.inverted; 13474 } 13475 frag.type = NSVG_SHADER_FILLIMG; 13476 13477 if (tex.type == NVGtexture.RGBA) { 13478 frag.texType = (tex.flags&NVGImageFlag.Premultiplied ? 0 : 1); 13479 } else { 13480 frag.texType = 2; 13481 } 13482 //printf("frag.texType = %d\n", frag.texType); 13483 } else { 13484 frag.type = (paint.simpleColor ? NSVG_SHADER_FILLCOLOR : NSVG_SHADER_FILLGRAD); 13485 frag.radius = paint.radius; 13486 frag.feather = paint.feather; 13487 //nvgTransformInverse(invxform[], paint.xform[]); 13488 invxform = paint.xform.inverted; 13489 } 13490 13491 glnvg__xformToMat3x4(frag.paintMat[], invxform.mat[]); 13492 13493 return true; 13494 } 13495 13496 void glnvg__setUniforms (GLNVGcontext* gl, int uniformOffset, int image) nothrow @trusted @nogc { 13497 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); 13498 glnvg__setFBOClipTexture(gl, frag); 13499 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.Frag], frag.UNIFORM_ARRAY_SIZE, &(frag.uniformArray.ptr[0].ptr[0])); 13500 glnvg__checkError(gl, "glnvg__setUniforms"); 13501 if (image != 0) { 13502 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13503 glnvg__bindTexture(gl, (tex !is null ? tex.tex : 0)); 13504 glnvg__checkError(gl, "tex paint tex"); 13505 } else { 13506 glnvg__bindTexture(gl, 0); 13507 } 13508 } 13509 13510 void glnvg__finishClip (GLNVGcontext* gl, NVGClipMode clipmode) nothrow @trusted @nogc { 13511 assert(clipmode != NVGClipMode.None); 13512 13513 // fill FBO, clear stencil buffer 13514 //TODO: optimize with bounds? 13515 version(all) { 13516 //glnvg__resetAffine(gl); 13517 //glUseProgram(gl.shaderFillFBO.prog); 13518 glDisable(GL_CULL_FACE); 13519 glDisable(GL_BLEND); 13520 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13521 glEnable(GL_STENCIL_TEST); 13522 if (gl.doClipUnion) { 13523 // for "and" we should clear everything that is NOT stencil-masked 13524 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13525 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13526 } else { 13527 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x00, 0xff); 13528 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13529 } 13530 13531 immutable(NVGVertex[4]) vertices = 13532 [NVGVertex(0, 0, 0, 0), 13533 NVGVertex(0, gl.fboHeight, 0, 0), 13534 NVGVertex(gl.fboWidth, gl.fboHeight, 0, 0), 13535 NVGVertex(gl.fboWidth, 0, 0, 0)]; 13536 13537 glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices); 13538 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 13539 13540 //glnvg__restoreAffine(gl); 13541 } 13542 13543 glBindFramebuffer(GL_FRAMEBUFFER, gl.mainFBO); 13544 glDisable(GL_COLOR_LOGIC_OP); 13545 //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // done above 13546 glEnable(GL_BLEND); 13547 glDisable(GL_STENCIL_TEST); 13548 glEnable(GL_CULL_FACE); 13549 glUseProgram(gl.shader.prog); 13550 13551 // set current FBO as used one 13552 assert(gl.msp > 0 && gl.fbo.ptr[gl.msp-1] > 0 && gl.fboTex.ptr[gl.msp-1].ptr[0] > 0); 13553 if (gl.lastClipFBO != gl.msp-1) { 13554 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); } 13555 gl.lastClipFBO = gl.msp-1; 13556 glActiveTexture(GL_TEXTURE1); 13557 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 13558 glActiveTexture(GL_TEXTURE0); 13559 } 13560 } 13561 13562 void glnvg__setClipUniforms (GLNVGcontext* gl, int uniformOffset, NVGClipMode clipmode) nothrow @trusted @nogc { 13563 assert(clipmode != NVGClipMode.None); 13564 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); 13565 // save uniform offset for `glnvg__finishClip()` 13566 gl.lastClipUniOfs = uniformOffset; 13567 // get FBO index, bind this FBO 13568 immutable int clipTexId = glnvg__generateFBOClipTexture(gl); 13569 assert(clipTexId >= 0); 13570 glUseProgram(gl.shaderFillFBO.prog); 13571 glnvg__checkError(gl, "use"); 13572 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[clipTexId]); 13573 // set logic op for clip 13574 gl.doClipUnion = false; 13575 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.JustCleared) { 13576 // it is cleared to zero, we can just draw a path 13577 glDisable(GL_COLOR_LOGIC_OP); 13578 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized; 13579 } else { 13580 glEnable(GL_COLOR_LOGIC_OP); 13581 final switch (clipmode) { 13582 case NVGClipMode.None: assert(0, "wtf?!"); 13583 case NVGClipMode.Union: glLogicOp(GL_CLEAR); gl.doClipUnion = true; break; // use `GL_CLEAR` to avoid adding another shader mode 13584 case NVGClipMode.Or: glLogicOp(GL_COPY); break; // GL_OR 13585 case NVGClipMode.Xor: glLogicOp(GL_XOR); break; 13586 case NVGClipMode.Sub: glLogicOp(GL_CLEAR); break; 13587 case NVGClipMode.Replace: glLogicOp(GL_COPY); break; 13588 } 13589 } 13590 // set affine matrix 13591 glUniform4fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TMat], 1, gl.lastAffine.mat.ptr); 13592 glnvg__checkError(gl, "affine 0"); 13593 glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TTr], 1, gl.lastAffine.mat.ptr+4); 13594 glnvg__checkError(gl, "affine 1"); 13595 // setup common OpenGL parameters 13596 glDisable(GL_BLEND); 13597 glDisable(GL_CULL_FACE); 13598 glEnable(GL_STENCIL_TEST); 13599 glnvg__stencilMask(gl, 0xff); 13600 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13601 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 13602 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13603 } 13604 13605 void glnvg__renderViewport (void* uptr, int width, int height) nothrow @trusted @nogc { 13606 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13607 gl.inFrame = true; 13608 gl.view.ptr[0] = cast(float)width; 13609 gl.view.ptr[1] = cast(float)height; 13610 // kill FBOs if we need to create new ones (flushing will recreate 'em if necessary) 13611 if (width != gl.fboWidth || height != gl.fboHeight) { 13612 glnvg__killFBOs(gl); 13613 gl.fboWidth = width; 13614 gl.fboHeight = height; 13615 } 13616 gl.msp = 1; 13617 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13618 // texture cleanup 13619 import core.atomic : atomicLoad; 13620 if (atomicLoad(gl.mustCleanTextures)) { 13621 try { 13622 import core.thread : Thread; 13623 static if (__VERSION__ < 2076) { 13624 DGNoThrowNoGC(() { 13625 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13626 })(); 13627 } else { 13628 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13629 } 13630 { 13631 GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow; 13632 gl.mustCleanTextures = false; 13633 foreach (immutable tidx, ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) { 13634 // no need to use atomic ops here, as we're locked 13635 if (tex.rc == 0 && tex.tex != 0 && tex.id == 0) { 13636 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** cleaned up texture with glid=%u\n", tex.tex); }} 13637 import core.stdc.string : memset; 13638 if ((tex.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tex.tex); 13639 memset(&tex, 0, tex.sizeof); 13640 tex.nextfree = gl.freetexid; 13641 gl.freetexid = cast(int)tidx; 13642 } 13643 } 13644 } 13645 } catch (Exception e) {} 13646 } 13647 } 13648 13649 void glnvg__fill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13650 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13651 int npaths = call.pathCount; 13652 13653 if (call.clipmode == NVGClipMode.None) { 13654 // Draw shapes 13655 glEnable(GL_STENCIL_TEST); 13656 glnvg__stencilMask(gl, 0xffU); 13657 glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xffU); 13658 13659 glnvg__setUniforms(gl, call.uniformOffset, 0); 13660 glnvg__checkError(gl, "fill simple"); 13661 13662 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13663 if (call.evenOdd) { 13664 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT); 13665 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT); 13666 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); 13667 } else { 13668 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 13669 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 13670 } 13671 glDisable(GL_CULL_FACE); 13672 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13673 glEnable(GL_CULL_FACE); 13674 13675 // Draw anti-aliased pixels 13676 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13677 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image); 13678 glnvg__checkError(gl, "fill fill"); 13679 13680 if (gl.flags&NVGContextFlag.Antialias) { 13681 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xffU); 13682 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13683 // Draw fringes 13684 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13685 } 13686 13687 // Draw fill 13688 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xffU); 13689 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13690 if (call.evenOdd) { 13691 glDisable(GL_CULL_FACE); 13692 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount); 13693 //foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13694 glEnable(GL_CULL_FACE); 13695 } else { 13696 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount); 13697 } 13698 13699 glDisable(GL_STENCIL_TEST); 13700 } else { 13701 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); // this activates our FBO 13702 glnvg__checkError(gl, "fillclip simple"); 13703 glnvg__stencilFunc(gl, GL_ALWAYS, 0x00, 0xffU); 13704 if (call.evenOdd) { 13705 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT); 13706 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT); 13707 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); 13708 } else { 13709 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 13710 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 13711 } 13712 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13713 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13714 } 13715 } 13716 13717 void glnvg__convexFill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13718 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13719 int npaths = call.pathCount; 13720 13721 if (call.clipmode == NVGClipMode.None) { 13722 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13723 glnvg__checkError(gl, "convex fill"); 13724 if (call.evenOdd) glDisable(GL_CULL_FACE); 13725 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13726 if (gl.flags&NVGContextFlag.Antialias) { 13727 // Draw fringes 13728 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13729 } 13730 if (call.evenOdd) glEnable(GL_CULL_FACE); 13731 } else { 13732 glnvg__setClipUniforms(gl, call.uniformOffset, call.clipmode); // this activates our FBO 13733 glnvg__checkError(gl, "clip convex fill"); 13734 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13735 if (gl.flags&NVGContextFlag.Antialias) { 13736 // Draw fringes 13737 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13738 } 13739 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13740 } 13741 } 13742 13743 void glnvg__stroke (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13744 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13745 int npaths = call.pathCount; 13746 13747 if (call.clipmode == NVGClipMode.None) { 13748 if (gl.flags&NVGContextFlag.StencilStrokes) { 13749 glEnable(GL_STENCIL_TEST); 13750 glnvg__stencilMask(gl, 0xff); 13751 13752 // Fill the stroke base without overlap 13753 glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff); 13754 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 13755 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image); 13756 glnvg__checkError(gl, "stroke fill 0"); 13757 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13758 13759 // Draw anti-aliased pixels. 13760 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13761 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13762 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13763 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13764 13765 // Clear stencil buffer. 13766 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13767 glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff); 13768 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13769 glnvg__checkError(gl, "stroke fill 1"); 13770 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13771 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13772 13773 glDisable(GL_STENCIL_TEST); 13774 13775 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f); 13776 } else { 13777 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13778 glnvg__checkError(gl, "stroke fill"); 13779 // Draw Strokes 13780 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13781 } 13782 } else { 13783 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); 13784 glnvg__checkError(gl, "stroke fill 0"); 13785 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13786 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13787 } 13788 } 13789 13790 void glnvg__triangles (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13791 if (call.clipmode == NVGClipMode.None) { 13792 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13793 glnvg__checkError(gl, "triangles fill"); 13794 glDrawArrays(GL_TRIANGLES, call.triangleOffset, call.triangleCount); 13795 } else { 13796 //TODO(?): use texture as mask? 13797 } 13798 } 13799 13800 void glnvg__affine (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13801 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, call.affine.mat.ptr); 13802 glnvg__checkError(gl, "affine"); 13803 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, call.affine.mat.ptr+4); 13804 glnvg__checkError(gl, "affine"); 13805 //glnvg__setUniforms(gl, call.uniformOffset, call.image); 13806 } 13807 13808 void glnvg__renderCancelInternal (GLNVGcontext* gl, bool clearTextures) nothrow @trusted @nogc { 13809 scope(exit) gl.inFrame = false; 13810 if (clearTextures && gl.inFrame) { 13811 try { 13812 import core.thread : Thread; 13813 static if (__VERSION__ < 2076) { 13814 DGNoThrowNoGC(() { 13815 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13816 })(); 13817 } else { 13818 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13819 } 13820 } catch (Exception e) {} 13821 foreach (ref GLNVGcall c; gl.calls[0..gl.ncalls]) if (c.image > 0) glnvg__deleteTexture(gl, c.image); 13822 } 13823 gl.nverts = 0; 13824 gl.npaths = 0; 13825 gl.ncalls = 0; 13826 gl.nuniforms = 0; 13827 gl.msp = 1; 13828 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13829 } 13830 13831 void glnvg__renderCancel (void* uptr) nothrow @trusted @nogc { 13832 glnvg__renderCancelInternal(cast(GLNVGcontext*)uptr, true); 13833 } 13834 13835 GLenum glnvg_convertBlendFuncFactor (NVGBlendFactor factor) pure nothrow @trusted @nogc { 13836 if (factor == NVGBlendFactor.Zero) return GL_ZERO; 13837 if (factor == NVGBlendFactor.One) return GL_ONE; 13838 if (factor == NVGBlendFactor.SrcColor) return GL_SRC_COLOR; 13839 if (factor == NVGBlendFactor.OneMinusSrcColor) return GL_ONE_MINUS_SRC_COLOR; 13840 if (factor == NVGBlendFactor.DstColor) return GL_DST_COLOR; 13841 if (factor == NVGBlendFactor.OneMinusDstColor) return GL_ONE_MINUS_DST_COLOR; 13842 if (factor == NVGBlendFactor.SrcAlpha) return GL_SRC_ALPHA; 13843 if (factor == NVGBlendFactor.OneMinusSrcAlpha) return GL_ONE_MINUS_SRC_ALPHA; 13844 if (factor == NVGBlendFactor.DstAlpha) return GL_DST_ALPHA; 13845 if (factor == NVGBlendFactor.OneMinusDstAlpha) return GL_ONE_MINUS_DST_ALPHA; 13846 if (factor == NVGBlendFactor.SrcAlphaSaturate) return GL_SRC_ALPHA_SATURATE; 13847 return GL_INVALID_ENUM; 13848 } 13849 13850 GLNVGblend glnvg__buildBlendFunc (NVGCompositeOperationState op) pure nothrow @trusted @nogc { 13851 GLNVGblend res; 13852 res.simple = op.simple; 13853 res.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB); 13854 res.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB); 13855 res.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha); 13856 res.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha); 13857 if (res.simple) { 13858 if (res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) { 13859 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM; 13860 } 13861 } else { 13862 if (res.srcRGB == GL_INVALID_ENUM || res.dstRGB == GL_INVALID_ENUM || res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) { 13863 res.simple = true; 13864 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM; 13865 } 13866 } 13867 return res; 13868 } 13869 13870 void glnvg__blendCompositeOperation() (GLNVGcontext* gl, const scope auto ref GLNVGblend op) nothrow @trusted @nogc { 13871 //glBlendFuncSeparate(glnvg_convertBlendFuncFactor(op.srcRGB), glnvg_convertBlendFuncFactor(op.dstRGB), glnvg_convertBlendFuncFactor(op.srcAlpha), glnvg_convertBlendFuncFactor(op.dstAlpha)); 13872 static if (NANOVG_GL_USE_STATE_FILTER) { 13873 if (gl.blendFunc.simple == op.simple) { 13874 if (op.simple) { 13875 if (gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return; 13876 } else { 13877 if (gl.blendFunc.srcRGB == op.srcRGB && gl.blendFunc.dstRGB == op.dstRGB && gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return; 13878 } 13879 } 13880 gl.blendFunc = op; 13881 } 13882 if (op.simple) { 13883 if (op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) { 13884 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13885 } else { 13886 glBlendFunc(op.srcAlpha, op.dstAlpha); 13887 } 13888 } else { 13889 if (op.srcRGB == GL_INVALID_ENUM || op.dstRGB == GL_INVALID_ENUM || op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) { 13890 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13891 } else { 13892 glBlendFuncSeparate(op.srcRGB, op.dstRGB, op.srcAlpha, op.dstAlpha); 13893 } 13894 } 13895 } 13896 13897 void glnvg__renderSetAffine (void* uptr, const scope ref NVGMatrix mat) nothrow @trusted @nogc { 13898 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13899 GLNVGcall* call; 13900 // if last operation was GLNVG_AFFINE, simply replace the matrix 13901 if (gl.ncalls > 0 && gl.calls[gl.ncalls-1].type == GLNVG_AFFINE) { 13902 call = &gl.calls[gl.ncalls-1]; 13903 } else { 13904 call = glnvg__allocCall(gl); 13905 if (call is null) return; 13906 call.type = GLNVG_AFFINE; 13907 } 13908 call.affine.mat.ptr[0..6] = mat.mat.ptr[0..6]; 13909 } 13910 13911 version(nanovega_debug_clipping) public __gshared bool nanovegaClipDebugDump = false; 13912 13913 void glnvg__renderFlush (void* uptr) nothrow @trusted @nogc { 13914 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13915 if (!gl.inFrame) assert(0, "NanoVega: internal driver error"); 13916 try { 13917 import core.thread : Thread; 13918 static if (__VERSION__ < 2076) { 13919 DGNoThrowNoGC(() { 13920 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13921 })(); 13922 } else { 13923 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13924 } 13925 } catch (Exception e) {} 13926 scope(exit) gl.inFrame = false; 13927 13928 glnvg__resetError!true(gl); 13929 { 13930 int vv = 0; 13931 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &vv); 13932 if (glGetError() || vv < 0) vv = 0; 13933 gl.mainFBO = cast(uint)vv; 13934 } 13935 13936 enum ShaderType { None, Fill, Clip } 13937 auto lastShader = ShaderType.None; 13938 if (gl.ncalls > 0) { 13939 gl.msp = 1; 13940 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13941 13942 // Setup require GL state. 13943 glUseProgram(gl.shader.prog); 13944 13945 glActiveTexture(GL_TEXTURE1); 13946 glBindTexture(GL_TEXTURE_2D, 0); 13947 glActiveTexture(GL_TEXTURE0); 13948 glnvg__resetFBOClipTextureCache(gl); 13949 13950 //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13951 static if (NANOVG_GL_USE_STATE_FILTER) { 13952 gl.blendFunc.simple = true; 13953 gl.blendFunc.srcRGB = gl.blendFunc.dstRGB = gl.blendFunc.srcAlpha = gl.blendFunc.dstAlpha = GL_INVALID_ENUM; 13954 } 13955 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // just in case 13956 glEnable(GL_CULL_FACE); 13957 glCullFace(GL_BACK); 13958 glFrontFace(GL_CCW); 13959 glEnable(GL_BLEND); 13960 glDisable(GL_DEPTH_TEST); 13961 glDisable(GL_SCISSOR_TEST); 13962 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13963 glStencilMask(0xffffffff); 13964 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13965 glStencilFunc(GL_ALWAYS, 0, 0xffffffff); 13966 glActiveTexture(GL_TEXTURE0); 13967 glBindTexture(GL_TEXTURE_2D, 0); 13968 static if (NANOVG_GL_USE_STATE_FILTER) { 13969 gl.boundTexture = 0; 13970 gl.stencilMask = 0xffffffff; 13971 gl.stencilFunc = GL_ALWAYS; 13972 gl.stencilFuncRef = 0; 13973 gl.stencilFuncMask = 0xffffffff; 13974 } 13975 glnvg__checkError(gl, "OpenGL setup"); 13976 13977 // Upload vertex data 13978 glBindBuffer(GL_ARRAY_BUFFER, gl.vertBuf); 13979 // ensure that there's space for at least 4 vertices in case we need to draw a quad (e. g. framebuffer copy) 13980 glBufferData(GL_ARRAY_BUFFER, (gl.nverts < 4 ? 4 : gl.nverts) * NVGVertex.sizeof, gl.verts, GL_STREAM_DRAW); 13981 glEnableVertexAttribArray(0); 13982 glEnableVertexAttribArray(1); 13983 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)cast(usize)0); 13984 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)(0+2*float.sizeof)); 13985 glnvg__checkError(gl, "vertex data uploading"); 13986 13987 // Set view and texture just once per frame. 13988 glUniform1i(gl.shader.loc[GLNVGuniformLoc.Tex], 0); 13989 if (gl.shader.loc[GLNVGuniformLoc.ClipTex] != -1) { 13990 //{ import core.stdc.stdio; printf("%d\n", gl.shader.loc[GLNVGuniformLoc.ClipTex]); } 13991 glUniform1i(gl.shader.loc[GLNVGuniformLoc.ClipTex], 1); 13992 } 13993 if (gl.shader.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shader.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 13994 glnvg__checkError(gl, "render shader setup"); 13995 13996 // Reset affine transformations. 13997 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, NVGMatrix.IdentityMat.ptr); 13998 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, NVGMatrix.IdentityMat.ptr+4); 13999 glnvg__checkError(gl, "affine setup"); 14000 14001 // set clip shaders params 14002 // fill 14003 glUseProgram(gl.shaderFillFBO.prog); 14004 glnvg__checkError(gl, "clip shaders setup (fill 0)"); 14005 if (gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 14006 glnvg__checkError(gl, "clip shaders setup (fill 1)"); 14007 // copy 14008 glUseProgram(gl.shaderCopyFBO.prog); 14009 glnvg__checkError(gl, "clip shaders setup (copy 0)"); 14010 if (gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 14011 glnvg__checkError(gl, "clip shaders setup (copy 1)"); 14012 //glUniform1i(gl.shaderFillFBO.loc[GLNVGuniformLoc.Tex], 0); 14013 glUniform1i(gl.shaderCopyFBO.loc[GLNVGuniformLoc.Tex], 0); 14014 glnvg__checkError(gl, "clip shaders setup (copy 2)"); 14015 // restore render shader 14016 glUseProgram(gl.shader.prog); 14017 14018 //{ 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]); } 14019 14020 gl.lastAffine.identity; 14021 14022 foreach (int i; 0..gl.ncalls) { 14023 GLNVGcall* call = &gl.calls[i]; 14024 switch (call.type) { 14025 case GLNVG_FILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__fill(gl, call); break; 14026 case GLNVG_CONVEXFILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__convexFill(gl, call); break; 14027 case GLNVG_STROKE: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__stroke(gl, call); break; 14028 case GLNVG_TRIANGLES: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__triangles(gl, call); break; 14029 case GLNVG_AFFINE: gl.lastAffine = call.affine; glnvg__affine(gl, call); break; 14030 // clip region management 14031 case GLNVG_PUSHCLIP: 14032 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]); } 14033 if (gl.msp >= gl.maskStack.length) assert(0, "NanoVega: mask stack overflow in OpenGL backend"); 14034 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.DontMask) { 14035 gl.maskStack.ptr[gl.msp++] = GLMaskState.DontMask; 14036 } else { 14037 gl.maskStack.ptr[gl.msp++] = GLMaskState.Uninitialized; 14038 } 14039 // no need to reset FBO cache here, as nothing was changed 14040 break; 14041 case GLNVG_POPCLIP: 14042 if (gl.msp <= 1) assert(0, "NanoVega: mask stack underflow in OpenGL backend"); 14043 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]); } 14044 --gl.msp; 14045 assert(gl.msp > 0); 14046 //{ import core.stdc.stdio; printf("popped; new msp is %d; state is %d\n", gl.msp, gl.maskStack.ptr[gl.msp]); } 14047 // check popped item 14048 final switch (gl.maskStack.ptr[gl.msp]) { 14049 case GLMaskState.DontMask: 14050 // if last FBO was "don't mask", reset cache if current is not "don't mask" 14051 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) { 14052 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14053 glnvg__resetFBOClipTextureCache(gl); 14054 } 14055 break; 14056 case GLMaskState.Uninitialized: 14057 // if last FBO texture was uninitialized, it means that nothing was changed, 14058 // so we can keep using cached FBO 14059 break; 14060 case GLMaskState.Initialized: 14061 // if last FBO was initialized, it means that something was definitely changed 14062 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14063 glnvg__resetFBOClipTextureCache(gl); 14064 break; 14065 case GLMaskState.JustCleared: assert(0, "NanoVega: internal FBO stack error"); 14066 } 14067 break; 14068 case GLNVG_RESETCLIP: 14069 // mark current mask as "don't mask" 14070 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]); } 14071 if (gl.msp > 0) { 14072 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) { 14073 gl.maskStack.ptr[gl.msp-1] = GLMaskState.DontMask; 14074 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14075 glnvg__resetFBOClipTextureCache(gl); 14076 } 14077 } 14078 break; 14079 case GLNVG_CLIP_DDUMP_ON: 14080 version(nanovega_debug_clipping) nanovegaClipDebugDump = true; 14081 break; 14082 case GLNVG_CLIP_DDUMP_OFF: 14083 version(nanovega_debug_clipping) nanovegaClipDebugDump = false; 14084 break; 14085 case GLNVG_NONE: break; 14086 default: 14087 { 14088 import core.stdc.stdio; stderr.fprintf("NanoVega FATAL: invalid command in OpenGL backend: %d\n", call.type); 14089 } 14090 assert(0, "NanoVega: invalid command in OpenGL backend (fatal internal error)"); 14091 } 14092 // and free texture, why not 14093 glnvg__deleteTexture(gl, call.image); 14094 } 14095 14096 glDisableVertexAttribArray(0); 14097 glDisableVertexAttribArray(1); 14098 glDisable(GL_CULL_FACE); 14099 glBindBuffer(GL_ARRAY_BUFFER, 0); 14100 glUseProgram(0); 14101 glnvg__bindTexture(gl, 0); 14102 } 14103 14104 // this will do all necessary cleanup 14105 glnvg__renderCancelInternal(gl, false); // no need to clear textures 14106 } 14107 14108 int glnvg__maxVertCount (const(NVGpath)* paths, int npaths) nothrow @trusted @nogc { 14109 int count = 0; 14110 foreach (int i; 0..npaths) { 14111 count += paths[i].nfill; 14112 count += paths[i].nstroke; 14113 } 14114 return count; 14115 } 14116 14117 GLNVGcall* glnvg__allocCall (GLNVGcontext* gl) nothrow @trusted @nogc { 14118 GLNVGcall* ret = null; 14119 if (gl.ncalls+1 > gl.ccalls) { 14120 GLNVGcall* calls; 14121 int ccalls = glnvg__maxi(gl.ncalls+1, 128)+gl.ccalls/2; // 1.5x Overallocate 14122 calls = cast(GLNVGcall*)realloc(gl.calls, GLNVGcall.sizeof*ccalls); 14123 if (calls is null) return null; 14124 gl.calls = calls; 14125 gl.ccalls = ccalls; 14126 } 14127 ret = &gl.calls[gl.ncalls++]; 14128 memset(ret, 0, GLNVGcall.sizeof); 14129 return ret; 14130 } 14131 14132 int glnvg__allocPaths (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14133 int ret = 0; 14134 if (gl.npaths+n > gl.cpaths) { 14135 GLNVGpath* paths; 14136 int cpaths = glnvg__maxi(gl.npaths+n, 128)+gl.cpaths/2; // 1.5x Overallocate 14137 paths = cast(GLNVGpath*)realloc(gl.paths, GLNVGpath.sizeof*cpaths); 14138 if (paths is null) return -1; 14139 gl.paths = paths; 14140 gl.cpaths = cpaths; 14141 } 14142 ret = gl.npaths; 14143 gl.npaths += n; 14144 return ret; 14145 } 14146 14147 int glnvg__allocVerts (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14148 int ret = 0; 14149 if (gl.nverts+n > gl.cverts) { 14150 NVGVertex* verts; 14151 int cverts = glnvg__maxi(gl.nverts+n, 4096)+gl.cverts/2; // 1.5x Overallocate 14152 verts = cast(NVGVertex*)realloc(gl.verts, NVGVertex.sizeof*cverts); 14153 if (verts is null) return -1; 14154 gl.verts = verts; 14155 gl.cverts = cverts; 14156 } 14157 ret = gl.nverts; 14158 gl.nverts += n; 14159 return ret; 14160 } 14161 14162 int glnvg__allocFragUniforms (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14163 int ret = 0, structSize = gl.fragSize; 14164 if (gl.nuniforms+n > gl.cuniforms) { 14165 ubyte* uniforms; 14166 int cuniforms = glnvg__maxi(gl.nuniforms+n, 128)+gl.cuniforms/2; // 1.5x Overallocate 14167 uniforms = cast(ubyte*)realloc(gl.uniforms, structSize*cuniforms); 14168 if (uniforms is null) return -1; 14169 gl.uniforms = uniforms; 14170 gl.cuniforms = cuniforms; 14171 } 14172 ret = gl.nuniforms*structSize; 14173 gl.nuniforms += n; 14174 return ret; 14175 } 14176 14177 GLNVGfragUniforms* nvg__fragUniformPtr (GLNVGcontext* gl, int i) nothrow @trusted @nogc { 14178 return cast(GLNVGfragUniforms*)&gl.uniforms[i]; 14179 } 14180 14181 void glnvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc { 14182 vtx.x = x; 14183 vtx.y = y; 14184 vtx.u = u; 14185 vtx.v = v; 14186 } 14187 14188 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 { 14189 if (npaths < 1) return; 14190 14191 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14192 GLNVGcall* call = glnvg__allocCall(gl); 14193 NVGVertex* quad; 14194 GLNVGfragUniforms* frag; 14195 int maxverts, offset; 14196 14197 if (call is null) return; 14198 14199 call.type = GLNVG_FILL; 14200 call.evenOdd = evenOdd; 14201 call.clipmode = clipmode; 14202 //if (clipmode != NVGClipMode.None) { import core.stdc.stdio; printf("CLIP!\n"); } 14203 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14204 call.triangleCount = 4; 14205 call.pathOffset = glnvg__allocPaths(gl, npaths); 14206 if (call.pathOffset == -1) goto error; 14207 call.pathCount = npaths; 14208 call.image = paint.image.id; 14209 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14210 14211 if (npaths == 1 && paths[0].convex) { 14212 call.type = GLNVG_CONVEXFILL; 14213 call.triangleCount = 0; // Bounding box fill quad not needed for convex fill 14214 } 14215 14216 // Allocate vertices for all the paths. 14217 maxverts = glnvg__maxVertCount(paths, npaths)+call.triangleCount; 14218 offset = glnvg__allocVerts(gl, maxverts); 14219 if (offset == -1) goto error; 14220 14221 foreach (int i; 0..npaths) { 14222 GLNVGpath* copy = &gl.paths[call.pathOffset+i]; 14223 const(NVGpath)* path = &paths[i]; 14224 memset(copy, 0, GLNVGpath.sizeof); 14225 if (path.nfill > 0) { 14226 copy.fillOffset = offset; 14227 copy.fillCount = path.nfill; 14228 memcpy(&gl.verts[offset], path.fill, NVGVertex.sizeof*path.nfill); 14229 offset += path.nfill; 14230 } 14231 if (path.nstroke > 0) { 14232 copy.strokeOffset = offset; 14233 copy.strokeCount = path.nstroke; 14234 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke); 14235 offset += path.nstroke; 14236 } 14237 } 14238 14239 // Setup uniforms for draw calls 14240 if (call.type == GLNVG_FILL) { 14241 import core.stdc.string : memcpy; 14242 // Quad 14243 call.triangleOffset = offset; 14244 quad = &gl.verts[call.triangleOffset]; 14245 glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f); 14246 glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f); 14247 glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f); 14248 glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f); 14249 // Get uniform 14250 call.uniformOffset = glnvg__allocFragUniforms(gl, 2); 14251 if (call.uniformOffset == -1) goto error; 14252 // Simple shader for stencil 14253 frag = nvg__fragUniformPtr(gl, call.uniformOffset); 14254 memset(frag, 0, (*frag).sizeof); 14255 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f); 14256 memcpy(nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), frag, (*frag).sizeof); 14257 frag.strokeThr = -1.0f; 14258 frag.type = NSVG_SHADER_SIMPLE; 14259 // Fill shader 14260 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, fringe, fringe, -1.0f); 14261 } else { 14262 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14263 if (call.uniformOffset == -1) goto error; 14264 // Fill shader 14265 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f); 14266 } 14267 14268 return; 14269 14270 error: 14271 // We get here if call alloc was ok, but something else is not. 14272 // Roll back the last call to prevent drawing it. 14273 if (gl.ncalls > 0) --gl.ncalls; 14274 } 14275 14276 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 { 14277 if (npaths < 1) return; 14278 14279 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14280 GLNVGcall* call = glnvg__allocCall(gl); 14281 int maxverts, offset; 14282 14283 if (call is null) return; 14284 14285 call.type = GLNVG_STROKE; 14286 call.clipmode = clipmode; 14287 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14288 call.pathOffset = glnvg__allocPaths(gl, npaths); 14289 if (call.pathOffset == -1) goto error; 14290 call.pathCount = npaths; 14291 call.image = paint.image.id; 14292 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14293 14294 // Allocate vertices for all the paths. 14295 maxverts = glnvg__maxVertCount(paths, npaths); 14296 offset = glnvg__allocVerts(gl, maxverts); 14297 if (offset == -1) goto error; 14298 14299 foreach (int i; 0..npaths) { 14300 GLNVGpath* copy = &gl.paths[call.pathOffset+i]; 14301 const(NVGpath)* path = &paths[i]; 14302 memset(copy, 0, GLNVGpath.sizeof); 14303 if (path.nstroke) { 14304 copy.strokeOffset = offset; 14305 copy.strokeCount = path.nstroke; 14306 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke); 14307 offset += path.nstroke; 14308 } 14309 } 14310 14311 if (gl.flags&NVGContextFlag.StencilStrokes) { 14312 // Fill shader 14313 call.uniformOffset = glnvg__allocFragUniforms(gl, 2); 14314 if (call.uniformOffset == -1) goto error; 14315 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); 14316 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f); 14317 } else { 14318 // Fill shader 14319 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14320 if (call.uniformOffset == -1) goto error; 14321 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); 14322 } 14323 14324 return; 14325 14326 error: 14327 // We get here if call alloc was ok, but something else is not. 14328 // Roll back the last call to prevent drawing it. 14329 if (gl.ncalls > 0) --gl.ncalls; 14330 } 14331 14332 void glnvg__renderTriangles (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc { 14333 if (nverts < 1) return; 14334 14335 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14336 GLNVGcall* call = glnvg__allocCall(gl); 14337 GLNVGfragUniforms* frag; 14338 14339 if (call is null) return; 14340 14341 call.type = GLNVG_TRIANGLES; 14342 call.clipmode = clipmode; 14343 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14344 call.image = paint.image.id; 14345 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14346 14347 // Allocate vertices for all the paths. 14348 call.triangleOffset = glnvg__allocVerts(gl, nverts); 14349 if (call.triangleOffset == -1) goto error; 14350 call.triangleCount = nverts; 14351 14352 memcpy(&gl.verts[call.triangleOffset], verts, NVGVertex.sizeof*nverts); 14353 14354 // Fill shader 14355 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14356 if (call.uniformOffset == -1) goto error; 14357 frag = nvg__fragUniformPtr(gl, call.uniformOffset); 14358 glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f); 14359 frag.type = NSVG_SHADER_IMG; 14360 14361 return; 14362 14363 error: 14364 // We get here if call alloc was ok, but something else is not. 14365 // Roll back the last call to prevent drawing it. 14366 if (gl.ncalls > 0) --gl.ncalls; 14367 } 14368 14369 void glnvg__renderDelete (void* uptr) nothrow @trusted @nogc { 14370 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14371 if (gl is null) return; 14372 14373 glnvg__killFBOs(gl); 14374 glnvg__deleteShader(&gl.shader); 14375 glnvg__deleteShader(&gl.shaderFillFBO); 14376 glnvg__deleteShader(&gl.shaderCopyFBO); 14377 14378 if (gl.vertBuf != 0) glDeleteBuffers(1, &gl.vertBuf); 14379 14380 foreach (ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) { 14381 if (tex.id != 0 && (tex.flags&NVGImageFlag.NoDelete) == 0) { 14382 assert(tex.tex != 0); 14383 glDeleteTextures(1, &tex.tex); 14384 } 14385 } 14386 free(gl.textures); 14387 14388 free(gl.paths); 14389 free(gl.verts); 14390 free(gl.uniforms); 14391 free(gl.calls); 14392 14393 free(gl); 14394 } 14395 14396 14397 /** Creates NanoVega contexts for OpenGL2+. 14398 * 14399 * Specify creation flags as additional arguments, like this: 14400 * `nvgCreateContext(NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes);` 14401 * 14402 * If you won't specify any flags, defaults will be used: 14403 * `[NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes]`. 14404 * 14405 * Group: context_management 14406 */ 14407 public NVGContext nvgCreateContext (const(NVGContextFlag)[] flagList...) nothrow @trusted @nogc { 14408 version(aliced) { 14409 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA; 14410 } else { 14411 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes; 14412 } 14413 uint flags = 0; 14414 if (flagList.length != 0) { 14415 foreach (immutable flg; flagList) flags |= (flg != NVGContextFlag.Default ? flg : DefaultFlags); 14416 } else { 14417 flags = DefaultFlags; 14418 } 14419 NVGparams params = void; 14420 NVGContext ctx = null; 14421 version(nanovg_builtin_opengl_bindings) nanovgInitOpenGL(); // why not? 14422 version(nanovg_bindbc_opengl_bindings) nanovgInitOpenGL(); 14423 GLNVGcontext* gl = cast(GLNVGcontext*)malloc(GLNVGcontext.sizeof); 14424 if (gl is null) goto error; 14425 memset(gl, 0, GLNVGcontext.sizeof); 14426 14427 memset(¶ms, 0, params.sizeof); 14428 params.renderCreate = &glnvg__renderCreate; 14429 params.renderCreateTexture = &glnvg__renderCreateTexture; 14430 params.renderTextureIncRef = &glnvg__renderTextureIncRef; 14431 params.renderDeleteTexture = &glnvg__renderDeleteTexture; 14432 params.renderUpdateTexture = &glnvg__renderUpdateTexture; 14433 params.renderGetTextureSize = &glnvg__renderGetTextureSize; 14434 params.renderViewport = &glnvg__renderViewport; 14435 params.renderCancel = &glnvg__renderCancel; 14436 params.renderFlush = &glnvg__renderFlush; 14437 params.renderPushClip = &glnvg__renderPushClip; 14438 params.renderPopClip = &glnvg__renderPopClip; 14439 params.renderResetClip = &glnvg__renderResetClip; 14440 params.renderFill = &glnvg__renderFill; 14441 params.renderStroke = &glnvg__renderStroke; 14442 params.renderTriangles = &glnvg__renderTriangles; 14443 params.renderSetAffine = &glnvg__renderSetAffine; 14444 params.renderDelete = &glnvg__renderDelete; 14445 params.userPtr = gl; 14446 params.edgeAntiAlias = (flags&NVGContextFlag.Antialias ? true : false); 14447 if (flags&(NVGContextFlag.FontAA|NVGContextFlag.FontNoAA)) { 14448 params.fontAA = (flags&NVGContextFlag.FontNoAA ? NVG_INVERT_FONT_AA : !NVG_INVERT_FONT_AA); 14449 } else { 14450 params.fontAA = NVG_INVERT_FONT_AA; 14451 } 14452 14453 gl.flags = flags; 14454 gl.freetexid = -1; 14455 14456 ctx = createInternal(¶ms); 14457 if (ctx is null) goto error; 14458 14459 static if (__VERSION__ < 2076) { 14460 DGNoThrowNoGC(() { import core.thread; gl.mainTID = Thread.getThis.id; })(); 14461 } else { 14462 try { import core.thread; gl.mainTID = Thread.getThis.id; } catch (Exception e) {} 14463 } 14464 14465 return ctx; 14466 14467 error: 14468 // 'gl' is freed by nvgDeleteInternal. 14469 if (ctx !is null) ctx.deleteInternal(); 14470 return null; 14471 } 14472 14473 /// Using OpenGL texture id creates GLNVGtexture and return its id. 14474 /// Group: images 14475 public int glCreateImageFromHandleGL2 (NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc { 14476 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr; 14477 GLNVGtexture* tex = glnvg__allocTexture(gl); 14478 14479 if (tex is null) return 0; 14480 14481 tex.type = NVGtexture.RGBA; 14482 tex.tex = textureId; 14483 tex.flags = imageFlags; 14484 tex.width = w; 14485 tex.height = h; 14486 14487 return tex.id; 14488 } 14489 14490 /// Create NVGImage from OpenGL texture id. 14491 /// Group: images 14492 public NVGImage glCreateImageFromOpenGLTexture(NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc { 14493 auto id = glCreateImageFromHandleGL2(ctx, textureId, w, h, imageFlags); 14494 14495 NVGImage res; 14496 if (id > 0) { 14497 res.id = id; 14498 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); } 14499 res.ctx = ctx; 14500 ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount 14501 } 14502 return res; 14503 } 14504 14505 /// Returns OpenGL texture id for NanoVega image. 14506 /// Group: images 14507 public GLuint glImageHandleGL2 (NVGContext ctx, int image) nothrow @trusted @nogc { 14508 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr; 14509 GLNVGtexture* tex = glnvg__findTexture(gl, image); 14510 return tex.tex; 14511 } 14512 14513 14514 // ////////////////////////////////////////////////////////////////////////// // 14515 private: 14516 14517 static if (NanoVegaHasFontConfig) { 14518 version(nanovg_builtin_fontconfig_bindings) { 14519 pragma(lib, "fontconfig"); 14520 14521 private extern(C) nothrow @trusted @nogc { 14522 enum FC_FILE = "file"; /* String */ 14523 alias FcBool = int; 14524 alias FcChar8 = char; 14525 struct FcConfig; 14526 struct FcPattern; 14527 alias FcMatchKind = int; 14528 enum : FcMatchKind { 14529 FcMatchPattern, 14530 FcMatchFont, 14531 FcMatchScan 14532 } 14533 alias FcResult = int; 14534 enum : FcResult { 14535 FcResultMatch, 14536 FcResultNoMatch, 14537 FcResultTypeMismatch, 14538 FcResultNoId, 14539 FcResultOutOfMemory 14540 } 14541 FcBool FcInit (); 14542 FcBool FcConfigSubstituteWithPat (FcConfig* config, FcPattern* p, FcPattern* p_pat, FcMatchKind kind); 14543 void FcDefaultSubstitute (FcPattern* pattern); 14544 FcBool FcConfigSubstitute (FcConfig* config, FcPattern* p, FcMatchKind kind); 14545 FcPattern* FcFontMatch (FcConfig* config, FcPattern* p, FcResult* result); 14546 FcPattern* FcNameParse (const(FcChar8)* name); 14547 void FcPatternDestroy (FcPattern* p); 14548 FcResult FcPatternGetString (const(FcPattern)* p, const(char)* object, int n, FcChar8** s); 14549 } 14550 } 14551 14552 __gshared bool fontconfigAvailable = false; 14553 // initialize fontconfig 14554 shared static this () { 14555 if (FcInit()) { 14556 fontconfigAvailable = true; 14557 } else { 14558 import core.stdc.stdio : stderr, fprintf; 14559 stderr.fprintf("***NanoVega WARNING: cannot init fontconfig!\n"); 14560 } 14561 } 14562 } 14563 14564 14565 // ////////////////////////////////////////////////////////////////////////// // 14566 public enum BaphometDims = 512.0f; // baphomet icon is 512x512 ([0..511]) 14567 14568 private static immutable ubyte[7641] baphometPath = [ 14569 0x01,0x04,0x06,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x08,0xa0,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43, 14570 0x00,0x80,0xff,0x43,0xa2,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x08,0x00,0x80,0xff, 14571 0x43,0x7a,0x89,0xe5,0x42,0xa0,0x1d,0xc6,0x43,0x00,0x00,0x00,0x00,0x30,0x89,0x7f,0x43,0x00,0x00,0x00, 14572 0x00,0x08,0x7a,0x89,0xe5,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7a,0x89,0xe5,0x42,0x00,0x00, 14573 0x00,0x00,0x30,0x89,0x7f,0x43,0x08,0x00,0x00,0x00,0x00,0xa2,0x1d,0xc6,0x43,0x7a,0x89,0xe5,0x42,0x00, 14574 0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x09,0x06,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd, 14575 0x43,0x08,0x16,0x68,0xb3,0x43,0x72,0x87,0xdd,0x43,0x71,0x87,0xdd,0x43,0x17,0x68,0xb3,0x43,0x71,0x87, 14576 0xdd,0x43,0x30,0x89,0x7f,0x43,0x08,0x71,0x87,0xdd,0x43,0xd2,0x2f,0x18,0x43,0x16,0x68,0xb3,0x43,0x35, 14577 0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x35,0xe2,0x87,0x42,0x08,0xd1,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42, 14578 0x35,0xe2,0x87,0x42,0xd2,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x08,0x35,0xe2,0x87, 14579 0x42,0x17,0x68,0xb3,0x43,0xd1,0x2f,0x18,0x43,0x72,0x87,0xdd,0x43,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd, 14580 0x43,0x09,0x06,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x07,0xa4,0x3f,0x7f,0x43,0x0b,0x86,0xdc,0x43, 14581 0x07,0x6c,0xb9,0xb2,0x43,0xe8,0xd1,0xca,0x42,0x07,0x6e,0x4d,0xa0,0x42,0xa9,0x10,0x9c,0x43,0x07,0xb7, 14582 0x40,0xd7,0x43,0xa9,0x10,0x9c,0x43,0x07,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x09,0x06,0x98,0x42, 14583 0x74,0x43,0xb1,0x8d,0x68,0x43,0x08,0xd7,0x24,0x79,0x43,0xba,0x83,0x6e,0x43,0xa9,0x16,0x7c,0x43,0x56, 14584 0xa1,0x76,0x43,0x74,0x2a,0x7d,0x43,0x44,0x73,0x80,0x43,0x08,0x55,0xd1,0x7e,0x43,0xe3,0xea,0x76,0x43, 14585 0xbc,0x18,0x81,0x43,0x7f,0xa8,0x6e,0x43,0x8f,0x0a,0x84,0x43,0x02,0xfc,0x68,0x43,0x09,0x06,0x92,0x29, 14586 0x8d,0x43,0x73,0xc3,0x67,0x43,0x08,0xa4,0xd9,0x8e,0x43,0xf2,0xa6,0x7a,0x43,0x8f,0x22,0x88,0x43,0x75, 14587 0x2a,0x7d,0x43,0x42,0x7f,0x82,0x43,0x08,0xc8,0x88,0x43,0x09,0x06,0xc1,0x79,0x74,0x43,0x50,0x64,0x89, 14588 0x43,0x08,0x68,0x2d,0x72,0x43,0xee,0x21,0x81,0x43,0xcd,0x97,0x55,0x43,0xe6,0xf1,0x7b,0x43,0x91,0xec, 14589 0x5d,0x43,0xa8,0xc7,0x6a,0x43,0x09,0x06,0xfa,0xa5,0x52,0x43,0x60,0x97,0x7c,0x43,0x08,0x19,0xff,0x50, 14590 0x43,0xe9,0x6e,0x8a,0x43,0xb0,0xbd,0x70,0x43,0x4c,0x51,0x82,0x43,0x04,0xeb,0x69,0x43,0x66,0x0f,0x8e, 14591 0x43,0x09,0x06,0x17,0xbf,0x71,0x43,0x2c,0x58,0x94,0x43,0x08,0x1c,0x96,0x6e,0x43,0x61,0x68,0x99,0x43, 14592 0x2d,0x3a,0x6e,0x43,0xc8,0x81,0x9e,0x43,0xb7,0x9b,0x72,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0x30,0xdb, 14593 0x82,0x43,0xdb,0xe9,0x93,0x43,0x08,0x11,0x82,0x84,0x43,0x61,0x68,0x99,0x43,0xe8,0x4a,0x84,0x43,0x8e, 14594 0xa6,0x9e,0x43,0x42,0x7f,0x82,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0xc4,0x02,0x85,0x43,0xd1,0x0b,0x92, 14595 0x43,0x08,0xd6,0xb2,0x86,0x43,0x34,0x1e,0x92,0x43,0x4f,0x58,0x87,0x43,0xa4,0xf1,0x92,0x43,0x03,0xd9, 14596 0x87,0x43,0x7b,0xc6,0x94,0x43,0x09,0x06,0x87,0x3e,0x64,0x43,0x31,0x3b,0x93,0x43,0x08,0x3b,0xbf,0x64, 14597 0x43,0x6f,0xf9,0x91,0x43,0x96,0x0b,0x67,0x43,0xc5,0x4a,0x91,0x43,0xcf,0xfe,0x6a,0x43,0x31,0x2f,0x91, 14598 0x43,0x09,0x06,0x16,0x74,0xb5,0x43,0x08,0xec,0x8e,0x43,0x08,0x1b,0x4b,0xb2,0x43,0xee,0x5d,0x8b,0x43, 14599 0x48,0x4d,0xad,0x43,0x12,0xa6,0x8a,0x43,0xf3,0xd7,0xa7,0x43,0x74,0xb8,0x8a,0x43,0x08,0x8c,0xb2,0xa0, 14600 0x43,0xcd,0xf8,0x8a,0x43,0x68,0x46,0x9b,0x43,0x79,0x8f,0x87,0x43,0x49,0xc9,0x96,0x43,0xe9,0x3e,0x82, 14601 0x43,0x08,0x60,0x5c,0x97,0x43,0xa1,0xde,0x8b,0x43,0x4e,0xa0,0x93,0x43,0x31,0x3b,0x93,0x43,0x9f,0xea, 14602 0x8d,0x43,0x27,0x8d,0x99,0x43,0x08,0x07,0xe0,0x8c,0x43,0x06,0x34,0x9b,0x43,0x38,0xe9,0x8c,0x43,0x46, 14603 0x0a,0x9e,0x43,0x3d,0xcc,0x8b,0x43,0xb2,0x06,0xa2,0x43,0x08,0xf1,0x40,0x8a,0x43,0xb0,0x12,0xa4,0x43, 14604 0x39,0xd1,0x88,0x43,0x76,0x43,0xa6,0x43,0xfa,0x06,0x88,0x43,0xa4,0x75,0xa9,0x43,0x08,0x19,0x6c,0x88, 14605 0x43,0x9f,0x9e,0xac,0x43,0x66,0xeb,0x87,0x43,0x44,0x76,0xb0,0x43,0x6b,0xce,0x86,0x43,0x3b,0xbc,0xb4, 14606 0x43,0x08,0xa9,0x8c,0x85,0x43,0x06,0xd0,0xb5,0x43,0xfa,0xee,0x83,0x43,0x74,0xa3,0xb6,0x43,0x3d,0x90, 14607 0x81,0x43,0x31,0xf6,0xb6,0x43,0x08,0x9d,0x61,0x7d,0x43,0xee,0x48,0xb7,0x43,0x3b,0x1f,0x75,0x43,0xcf, 14608 0xe3,0xb6,0x43,0xee,0x6f,0x6d,0x43,0x68,0xe2,0xb5,0x43,0x08,0xd4,0xed,0x6b,0x43,0x87,0x2f,0xb2,0x43, 14609 0x0e,0xc9,0x6b,0x43,0xa7,0x7c,0xae,0x43,0x98,0xfa,0x67,0x43,0xab,0x53,0xab,0x43,0x08,0x25,0x2c,0x64, 14610 0x43,0x33,0xa2,0xa8,0x43,0x40,0x96,0x61,0x43,0xc3,0xc2,0xa5,0x43,0x64,0xde,0x60,0x43,0xfa,0xa2,0xa2, 14611 0x43,0x08,0xb0,0x5d,0x60,0x43,0x06,0x4c,0x9f,0x43,0x9a,0xca,0x5f,0x43,0x38,0x3d,0x9b,0x43,0x3b,0x8f, 14612 0x5c,0x43,0x85,0xb0,0x98,0x43,0x08,0x42,0x36,0x51,0x43,0x3d,0xf0,0x91,0x43,0xcd,0x4f,0x49,0x43,0xdb, 14613 0xb9,0x8b,0x43,0xe0,0xdb,0x44,0x43,0x42,0x8b,0x84,0x43,0x08,0x7e,0xc9,0x44,0x43,0x8a,0x57,0x8d,0x43, 14614 0xbc,0x6c,0x0f,0x43,0x23,0x62,0x8e,0x43,0xf5,0x17,0x07,0x43,0xc5,0x3e,0x8f,0x43,0x09,0x06,0xe0,0xea, 14615 0x76,0x43,0xab,0xef,0xc5,0x43,0x08,0x12,0x00,0x79,0x43,0xab,0xcb,0xbf,0x43,0x79,0xb9,0x6d,0x43,0x7e, 14616 0x8d,0xba,0x43,0xee,0x6f,0x6d,0x43,0x98,0xeb,0xb5,0x43,0x08,0xe0,0x02,0x7b,0x43,0x5f,0x1c,0xb8,0x43, 14617 0x85,0x2c,0x82,0x43,0xe9,0x65,0xb8,0x43,0xd6,0xb2,0x86,0x43,0xc6,0x05,0xb5,0x43,0x08,0x03,0xcd,0x85, 14618 0x43,0x5a,0x39,0xb9,0x43,0xe4,0x4f,0x81,0x43,0xdb,0xd4,0xbf,0x43,0xdf,0x6c,0x82,0x43,0xbc,0x93,0xc5, 14619 0x43,0x09,0x06,0xf0,0xd0,0x22,0x43,0x5d,0x19,0x08,0x43,0x08,0xbc,0xab,0x49,0x43,0x4a,0x35,0x29,0x43, 14620 0xcb,0xf7,0x65,0x43,0xce,0x37,0x45,0x43,0x0e,0x99,0x63,0x43,0x67,0xc6,0x5c,0x43,0x09,0x06,0x05,0x94, 14621 0xab,0x43,0xc2,0x13,0x04,0x43,0x08,0x9f,0x26,0x98,0x43,0x11,0x42,0x25,0x43,0x97,0x00,0x8a,0x43,0x32, 14622 0x32,0x41,0x43,0xf5,0x2f,0x8b,0x43,0xc7,0xc0,0x58,0x43,0x09,0x06,0x8f,0x85,0x48,0x43,0xe0,0xa8,0x8c, 14623 0x43,0x08,0x55,0xaa,0x48,0x43,0xe0,0xa8,0x8c,0x43,0x6b,0x3d,0x49,0x43,0xc1,0x43,0x8c,0x43,0x31,0x62, 14624 0x49,0x43,0xc1,0x43,0x8c,0x43,0x08,0x2f,0xe3,0x2f,0x43,0xad,0xe7,0x98,0x43,0xff,0x0d,0x0d,0x43,0xad, 14625 0xf3,0x9a,0x43,0xf0,0xaf,0xcc,0x42,0x74,0x00,0x97,0x43,0x08,0xbb,0xa2,0xf7,0x42,0x93,0x4d,0x93,0x43, 14626 0x5e,0x19,0x08,0x43,0x5a,0x2a,0x87,0x43,0x23,0x6e,0x10,0x43,0x42,0x97,0x86,0x43,0x08,0xca,0xe8,0x33, 14627 0x43,0x1b,0x3c,0x80,0x43,0x80,0xe8,0x4d,0x43,0xda,0xf4,0x70,0x43,0xae,0x0e,0x4f,0x43,0x2b,0x1b,0x65, 14628 0x43,0x08,0x66,0x96,0x54,0x43,0xa3,0xe1,0x3b,0x43,0x4e,0xc4,0x19,0x43,0xa0,0x1a,0x16,0x43,0x10,0xe2, 14629 0x14,0x43,0x26,0x14,0xe0,0x42,0x08,0x5c,0x91,0x1c,0x43,0xcb,0x27,0xee,0x42,0xa9,0x40,0x24,0x43,0x71, 14630 0x3b,0xfc,0x42,0xf3,0xef,0x2b,0x43,0x8b,0x27,0x05,0x43,0x08,0xe2,0x4b,0x2c,0x43,0x48,0x86,0x07,0x43, 14631 0x79,0x62,0x2f,0x43,0x05,0xe5,0x09,0x43,0x55,0x32,0x34,0x43,0xa0,0xd2,0x09,0x43,0x08,0x74,0xa3,0x36, 14632 0x43,0x3a,0xd1,0x08,0x43,0x7e,0x81,0x38,0x43,0x09,0xd4,0x0a,0x43,0x0d,0xba,0x39,0x43,0xa0,0xea,0x0d, 14633 0x43,0x08,0x6f,0xe4,0x3d,0x43,0x43,0xc7,0x0e,0x43,0xd6,0xe5,0x3e,0x43,0xc4,0x4a,0x11,0x43,0x55,0x7a, 14634 0x40,0x43,0x59,0x72,0x13,0x43,0x08,0x55,0x92,0x44,0x43,0xbf,0x73,0x14,0x43,0x23,0x95,0x46,0x43,0xa5, 14635 0x09,0x17,0x43,0xe0,0xf3,0x48,0x43,0xfe,0x55,0x19,0x43,0x08,0xcd,0x4f,0x49,0x43,0xaa,0x10,0x1c,0x43, 14636 0x61,0x77,0x4b,0x43,0xfe,0x6d,0x1d,0x43,0x80,0xe8,0x4d,0x43,0x2b,0x94,0x1e,0x43,0x08,0x58,0xc9,0x51, 14637 0x43,0x41,0x27,0x1f,0x43,0x9b,0x82,0x53,0x43,0x35,0x72,0x20,0x43,0x53,0xf2,0x54,0x43,0x88,0xcf,0x21, 14638 0x43,0x08,0x7b,0x29,0x55,0x43,0xe8,0x0a,0x25,0x43,0xb2,0x2d,0x58,0x43,0xef,0xe8,0x26,0x43,0x9b,0xb2, 14639 0x5b,0x43,0xd0,0x8f,0x28,0x43,0x08,0x5f,0xef,0x5f,0x43,0xeb,0x11,0x2a,0x43,0xfd,0xdc,0x5f,0x43,0x6e, 14640 0x95,0x2c,0x43,0x3b,0xa7,0x60,0x43,0x2b,0xf4,0x2e,0x43,0x08,0x06,0xbb,0x61,0x43,0xfd,0xe5,0x31,0x43, 14641 0xe7,0x61,0x63,0x43,0xef,0x30,0x33,0x43,0x53,0x52,0x65,0x43,0xa3,0xb1,0x33,0x43,0x08,0x12,0xa0,0x68, 14642 0x43,0x7f,0x69,0x34,0x43,0x40,0xc6,0x69,0x43,0x64,0xff,0x36,0x43,0x7e,0x90,0x6a,0x43,0x71,0xcc,0x39, 14643 0x43,0x08,0xbc,0x5a,0x6b,0x43,0x51,0x73,0x3b,0x43,0xc1,0x49,0x6c,0x43,0xa5,0xd0,0x3c,0x43,0xe0,0xba, 14644 0x6e,0x43,0xb8,0x74,0x3c,0x43,0x08,0x6b,0x1c,0x73,0x43,0x13,0xc1,0x3e,0x43,0x40,0xf6,0x71,0x43,0xce, 14645 0x1f,0x41,0x43,0x55,0x89,0x72,0x43,0x8d,0x7e,0x43,0x43,0x08,0x68,0x2d,0x72,0x43,0x89,0xae,0x4b,0x43, 14646 0xc1,0x79,0x74,0x43,0xcb,0x78,0x4c,0x43,0x55,0xa1,0x76,0x43,0x5b,0xb1,0x4d,0x43,0x08,0xa2,0x38,0x7a, 14647 0x43,0xd1,0x56,0x4e,0x43,0x85,0xb6,0x78,0x43,0xb1,0x15,0x54,0x43,0x83,0xc7,0x77,0x43,0x89,0x0e,0x5c, 14648 0x43,0x08,0xcf,0x46,0x77,0x43,0x0f,0x81,0x5f,0x43,0x1a,0xde,0x7a,0x43,0xce,0xc7,0x5d,0x43,0x42,0x73, 14649 0x80,0x43,0x99,0xc3,0x5a,0x43,0x08,0x85,0x2c,0x82,0x43,0xf6,0xe6,0x59,0x43,0x81,0x3d,0x81,0x43,0x16, 14650 0x10,0x50,0x43,0xd6,0x8e,0x80,0x43,0x5b,0x99,0x49,0x43,0x08,0xc4,0xea,0x80,0x43,0x22,0x95,0x46,0x43, 14651 0xfa,0xe2,0x81,0x43,0xda,0xec,0x43,0x43,0x78,0x77,0x83,0x43,0xe4,0xb2,0x41,0x43,0x08,0x8a,0x27,0x85, 14652 0x43,0x86,0x77,0x3e,0x43,0x0c,0x9f,0x85,0x43,0x07,0xf4,0x3b,0x43,0x8f,0x16,0x86,0x43,0xe6,0x82,0x39, 14653 0x43,0x08,0x85,0x44,0x86,0x43,0x37,0xd9,0x35,0x43,0x1e,0x4f,0x87,0x43,0xe1,0x7b,0x34,0x43,0xdf,0x90, 14654 0x88,0x43,0xb6,0x55,0x33,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0xe5,0x31,0x43,0xfa,0x12,0x8a,0x43,0xbf, 14655 0x03,0x2d,0x43,0x19,0x78,0x8a,0x43,0x45,0x5e,0x2c,0x43,0x08,0x03,0xf1,0x8b,0x43,0xac,0x47,0x29,0x43, 14656 0x2f,0x17,0x8d,0x43,0x45,0x46,0x28,0x43,0xc8,0x21,0x8e,0x43,0x30,0xb3,0x27,0x43,0x08,0xa9,0xc8,0x8f, 14657 0x43,0xef,0xe8,0x26,0x43,0xbf,0x5b,0x90,0x43,0x5b,0xc1,0x24,0x43,0x10,0xca,0x90,0x43,0xa0,0x62,0x22, 14658 0x43,0x08,0x26,0x5d,0x91,0x43,0xbb,0xcc,0x1f,0x43,0xf0,0x70,0x92,0x43,0x78,0x13,0x1e,0x43,0x77,0xd7, 14659 0x93,0x43,0x73,0x24,0x1d,0x43,0x08,0x65,0x3f,0x96,0x43,0xce,0x58,0x1b,0x43,0xbe,0x7f,0x96,0x43,0xbf, 14660 0x8b,0x18,0x43,0x60,0x5c,0x97,0x43,0xb6,0xad,0x16,0x43,0x08,0xba,0xa8,0x99,0x43,0x78,0xcb,0x11,0x43, 14661 0x49,0xe1,0x9a,0x43,0x78,0xcb,0x11,0x43,0x01,0x51,0x9c,0x43,0x73,0xdc,0x10,0x43,0x08,0x72,0x24,0x9d, 14662 0x43,0xd2,0xff,0x0f,0x43,0x1c,0xd3,0x9d,0x43,0x07,0xec,0x0e,0x43,0xeb,0xc9,0x9d,0x43,0xe8,0x7a,0x0c, 14663 0x43,0x08,0x60,0x80,0x9d,0x43,0xd7,0xbe,0x08,0x43,0x4d,0xe8,0x9f,0x43,0x86,0x50,0x08,0x43,0x25,0xbd, 14664 0xa1,0x43,0x5b,0x2a,0x07,0x43,0x08,0x99,0x7f,0xa3,0x43,0xc9,0xf1,0x05,0x43,0x48,0x1d,0xa5,0x43,0x86, 14665 0x38,0x04,0x43,0x6c,0x71,0xa6,0x43,0x18,0x59,0x01,0x43,0x08,0x32,0x96,0xa6,0x43,0x6e,0x64,0xff,0x42, 14666 0x48,0x29,0xa7,0x43,0xed,0xcf,0xfd,0x42,0x5f,0xbc,0xa7,0x43,0x71,0x3b,0xfc,0x42,0x08,0xf3,0xe3,0xa9, 14667 0x43,0xf7,0x7d,0xf7,0x42,0xd8,0x6d,0xaa,0x43,0x45,0xe5,0xf2,0x42,0x48,0x41,0xab,0x43,0xcb,0x27,0xee, 14668 0x42,0x08,0x24,0xf9,0xab,0x43,0x52,0x6a,0xe9,0x42,0xee,0x0c,0xad,0x43,0x4c,0x8c,0xe7,0x42,0x1b,0x33, 14669 0xae,0x43,0xcc,0xf7,0xe5,0x42,0x08,0xaa,0x6b,0xaf,0x43,0xe8,0x61,0xe3,0x42,0x90,0xf5,0xaf,0x43,0xc9, 14670 0xf0,0xe0,0x42,0xe0,0x63,0xb0,0x43,0xe5,0x5a,0xde,0x42,0x08,0xaa,0x83,0xb3,0x43,0x29,0x2d,0x09,0x43, 14671 0x6a,0xfe,0x8e,0x43,0xb8,0x74,0x3c,0x43,0xd5,0x06,0x95,0x43,0xe6,0x79,0x67,0x43,0x08,0x2f,0x53,0x97, 14672 0x43,0xe9,0xb0,0x74,0x43,0xa8,0x28,0xa0,0x43,0x43,0xfd,0x76,0x43,0x83,0x28,0xad,0x43,0x17,0x59,0x81, 14673 0x43,0x08,0x3d,0xe7,0xbf,0x43,0x4b,0x8d,0x8c,0x43,0xae,0x96,0xba,0x43,0x66,0x27,0x92,0x43,0x15,0xe0, 14674 0xc7,0x43,0x6f,0x11,0x96,0x43,0x08,0x7e,0x5d,0xb2,0x43,0xdb,0x01,0x98,0x43,0x9e,0x56,0xa0,0x43,0x80, 14675 0xc1,0x97,0x43,0x69,0x2e,0x97,0x43,0x31,0x17,0x8d,0x43,0x09,0x06,0xab,0xa7,0x39,0x43,0x67,0x0f,0x0e, 14676 0x43,0x08,0xdb,0xbc,0x3b,0x43,0xe8,0x92,0x10,0x43,0xb5,0x85,0x3b,0x43,0x97,0x3c,0x14,0x43,0xab,0xa7, 14677 0x39,0x43,0x0c,0x0b,0x18,0x43,0x09,0x06,0xca,0x30,0x40,0x43,0x30,0x3b,0x13,0x43,0x08,0x17,0xc8,0x43, 14678 0x43,0xa5,0x09,0x17,0x43,0x7e,0xc9,0x44,0x43,0x1a,0xd8,0x1a,0x43,0x9d,0x22,0x43,0x43,0x8d,0xa6,0x1e, 14679 0x43,0x09,0x06,0xc8,0x78,0x4c,0x43,0xed,0xc9,0x1d,0x43,0x08,0x0b,0x32,0x4e,0x43,0x22,0xce,0x20,0x43, 14680 0x23,0xc5,0x4e,0x43,0x58,0xd2,0x23,0x43,0x0b,0x32,0x4e,0x43,0x2b,0xc4,0x26,0x43,0x09,0x06,0xec,0x08, 14681 0x58,0x43,0xc7,0xb1,0x26,0x43,0x08,0x02,0x9c,0x58,0x43,0xef,0x00,0x2b,0x43,0xd9,0x64,0x58,0x43,0x02, 14682 0xbd,0x2e,0x43,0x10,0x51,0x57,0x43,0x37,0xc1,0x31,0x43,0x09,0x06,0xcb,0xdf,0x61,0x43,0x4a,0x65,0x31, 14683 0x43,0x08,0xbe,0x2a,0x63,0x43,0xbd,0x33,0x35,0x43,0x32,0xe1,0x62,0x43,0x56,0x4a,0x38,0x43,0xde,0x83, 14684 0x61,0x43,0x3c,0xe0,0x3a,0x43,0x09,0x06,0x1c,0x7e,0x6a,0x43,0x5b,0x39,0x39,0x43,0x08,0x31,0x11,0x6b, 14685 0x43,0x0c,0xd2,0x3d,0x43,0x1c,0x7e,0x6a,0x43,0x13,0xd9,0x42,0x43,0xd9,0xc4,0x68,0x43,0xcb,0x60,0x48, 14686 0x43,0x09,0x06,0xe5,0xc1,0x73,0x43,0x16,0xf8,0x4b,0x43,0x08,0xa6,0xf7,0x72,0x43,0xb1,0xfd,0x4f,0x43, 14687 0x3b,0x07,0x71,0x43,0x4a,0x14,0x53,0x43,0xa2,0xf0,0x6d,0x43,0x7c,0x29,0x55,0x43,0x09,0x06,0x00,0x8d, 14688 0xa6,0x43,0xef,0x21,0x01,0x43,0x08,0x52,0xfb,0xa6,0x43,0xce,0xc8,0x02,0x43,0xe6,0x16,0xa7,0x43,0x51, 14689 0x4c,0x05,0x43,0x3b,0x68,0xa6,0x43,0x4c,0x75,0x08,0x43,0x09,0x06,0xde,0x20,0xa1,0x43,0x86,0x50,0x08, 14690 0x43,0x08,0xd4,0x4e,0xa1,0x43,0xd3,0xe7,0x0b,0x43,0xb5,0xe9,0xa0,0x43,0x59,0x5a,0x0f,0x43,0xba,0xcc, 14691 0x9f,0x43,0x54,0x83,0x12,0x43,0x09,0x06,0x77,0xfb,0x99,0x43,0x6c,0x16,0x13,0x43,0x08,0xde,0xfc,0x9a, 14692 0x43,0x4a,0xbd,0x14,0x43,0x06,0x34,0x9b,0x43,0xfe,0x55,0x19,0x43,0x13,0xe9,0x99,0x43,0x41,0x27,0x1f, 14693 0x43,0x09,0x06,0x46,0xce,0x93,0x43,0x26,0xa5,0x1d,0x43,0x08,0xe7,0xaa,0x94,0x43,0xbb,0xcc,0x1f,0x43, 14694 0x18,0xb4,0x94,0x43,0xa8,0x40,0x24,0x43,0xe2,0xbb,0x93,0x43,0x21,0xfe,0x28,0x43,0x09,0x06,0xb1,0x8e, 14695 0x8d,0x43,0xa8,0x58,0x28,0x43,0x08,0x19,0x90,0x8e,0x43,0x54,0x13,0x2b,0x43,0xa4,0xd9,0x8e,0x43,0x84, 14696 0x40,0x31,0x43,0x46,0xaa,0x8d,0x43,0x29,0x24,0x37,0x43,0x09,0x06,0xd6,0xbe,0x88,0x43,0xef,0x30,0x33, 14697 0x43,0x08,0x0c,0xb7,0x89,0x43,0x0e,0xa2,0x35,0x43,0xc0,0x37,0x8a,0x43,0x7a,0xaa,0x3b,0x43,0xbb,0x48, 14698 0x89,0x43,0xbb,0x7b,0x41,0x43,0x09,0x06,0x3a,0xad,0x82,0x43,0xc4,0x59,0x43,0x43,0x08,0xd2,0xb7,0x83, 14699 0x43,0x2b,0x5b,0x44,0x43,0x35,0xd6,0x85,0x43,0x48,0xf5,0x49,0x43,0x42,0x97,0x86,0x43,0xc4,0xa1,0x4f, 14700 0x43,0x09,0x06,0x9c,0xb3,0x80,0x43,0x48,0x55,0x5a,0x43,0x08,0xff,0xc5,0x80,0x43,0x09,0x73,0x55,0x43, 14701 0x93,0xe1,0x80,0x43,0x0f,0x39,0x53,0x43,0xf1,0xbe,0x7e,0x43,0x18,0xe7,0x4c,0x43,0x09,0x06,0xe0,0x02, 14702 0x7b,0x43,0x92,0xec,0x5d,0x43,0x08,0x09,0x3a,0x7b,0x43,0xf0,0xf7,0x58,0x43,0x09,0x3a,0x7b,0x43,0xe6, 14703 0x31,0x5b,0x43,0xe0,0x02,0x7b,0x43,0xa8,0x4f,0x56,0x43,0x09,0x06,0x39,0x4f,0x7d,0x43,0x3e,0x8f,0x5c, 14704 0x43,0x08,0xe9,0xe0,0x7c,0x43,0x03,0x9c,0x58,0x43,0x1e,0x2b,0x81,0x43,0x7f,0x30,0x5a,0x43,0xff,0x73, 14705 0x7d,0x43,0xf6,0xb6,0x51,0x43,0x09,0x06,0x5c,0xb8,0x52,0x43,0x28,0x21,0x87,0x43,0x08,0xae,0x3e,0x57, 14706 0x43,0x12,0x9a,0x88,0x43,0x23,0xf5,0x56,0x43,0x04,0xf1,0x8b,0x43,0x25,0xfc,0x5b,0x43,0x85,0x74,0x8e, 14707 0x43,0x08,0x2f,0xf2,0x61,0x43,0x8e,0x52,0x90,0x43,0xd9,0xdc,0x6c,0x43,0x85,0x74,0x8e,0x43,0xc6,0x20, 14708 0x69,0x43,0x3d,0xd8,0x8d,0x43,0x08,0x6d,0x8c,0x5a,0x43,0xf5,0x3b,0x8d,0x43,0x3d,0x77,0x58,0x43,0xa1, 14709 0xc6,0x87,0x43,0xf8,0xed,0x5e,0x43,0x5e,0x0d,0x86,0x43,0x09,0x06,0xde,0xcc,0x92,0x43,0xf7,0x17,0x87, 14710 0x43,0x08,0xb6,0x89,0x90,0x43,0xae,0x87,0x88,0x43,0x4a,0xa5,0x90,0x43,0xa1,0xde,0x8b,0x43,0xf9,0x2a, 14711 0x8e,0x43,0x23,0x62,0x8e,0x43,0x08,0xf5,0x2f,0x8b,0x43,0x5c,0x49,0x90,0x43,0x35,0xd6,0x85,0x43,0x8e, 14712 0x46,0x8e,0x43,0x3d,0xb4,0x87,0x43,0x47,0xaa,0x8d,0x43,0x08,0x6a,0xfe,0x8e,0x43,0xff,0x0d,0x8d,0x43, 14713 0xbb,0x6c,0x8f,0x43,0xf7,0x17,0x87,0x43,0x5c,0x31,0x8c,0x43,0xb2,0x5e,0x85,0x43,0x09,0x06,0x60,0x38, 14714 0x91,0x43,0x69,0x5d,0x7a,0x43,0x08,0x34,0x1e,0x92,0x43,0x1e,0x5b,0x89,0x43,0x04,0x63,0x7e,0x43,0x5e, 14715 0x01,0x84,0x43,0x59,0x2a,0x87,0x43,0x0d,0xcf,0x8d,0x43,0x09,0x03,0x04,0x06,0x5a,0x18,0x63,0x43,0x82, 14716 0x79,0x8b,0x43,0x08,0x25,0x2c,0x64,0x43,0x82,0x79,0x8b,0x43,0x2a,0x1b,0x65,0x43,0x9d,0xef,0x8a,0x43, 14717 0x2a,0x1b,0x65,0x43,0xc1,0x37,0x8a,0x43,0x08,0x2a,0x1b,0x65,0x43,0x17,0x89,0x89,0x43,0x25,0x2c,0x64, 14718 0x43,0x31,0xff,0x88,0x43,0x5a,0x18,0x63,0x43,0x31,0xff,0x88,0x43,0x08,0xf3,0x16,0x62,0x43,0x31,0xff, 14719 0x88,0x43,0xee,0x27,0x61,0x43,0x17,0x89,0x89,0x43,0xee,0x27,0x61,0x43,0xc1,0x37,0x8a,0x43,0x08,0xee, 14720 0x27,0x61,0x43,0x9d,0xef,0x8a,0x43,0xf3,0x16,0x62,0x43,0x82,0x79,0x8b,0x43,0x5a,0x18,0x63,0x43,0x82, 14721 0x79,0x8b,0x43,0x09,0x06,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x08,0x34,0xee,0x89,0x43,0x82,0x79, 14722 0x8b,0x43,0x85,0x5c,0x8a,0x43,0x9d,0xef,0x8a,0x43,0x85,0x5c,0x8a,0x43,0xc1,0x37,0x8a,0x43,0x08,0x85, 14723 0x5c,0x8a,0x43,0x17,0x89,0x89,0x43,0x34,0xee,0x89,0x43,0x31,0xff,0x88,0x43,0x4f,0x64,0x89,0x43,0x31, 14724 0xff,0x88,0x43,0x08,0x9c,0xe3,0x88,0x43,0x31,0xff,0x88,0x43,0x19,0x6c,0x88,0x43,0x17,0x89,0x89,0x43, 14725 0x19,0x6c,0x88,0x43,0xc1,0x37,0x8a,0x43,0x08,0x19,0x6c,0x88,0x43,0x9d,0xef,0x8a,0x43,0x9c,0xe3,0x88, 14726 0x43,0x82,0x79,0x8b,0x43,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x09,0x02,0x04,0x06,0x19,0x60,0x86, 14727 0x43,0xec,0xed,0xa3,0x43,0x08,0x35,0xd6,0x85,0x43,0x76,0x43,0xa6,0x43,0x93,0xe1,0x80,0x43,0x57,0x02, 14728 0xac,0x43,0x61,0xd8,0x80,0x43,0x87,0x17,0xae,0x43,0x08,0xa5,0x85,0x80,0x43,0xc3,0xfe,0xaf,0x43,0xce, 14729 0xbc,0x80,0x43,0x83,0x40,0xb1,0x43,0xa5,0x91,0x82,0x43,0x79,0x6e,0xb1,0x43,0x08,0x23,0x26,0x84,0x43, 14730 0x40,0x93,0xb1,0x43,0x30,0xe7,0x84,0x43,0xbe,0x1b,0xb1,0x43,0x11,0x82,0x84,0x43,0xab,0x6b,0xaf,0x43, 14731 0x08,0xb7,0x41,0x84,0x43,0x3b,0x98,0xae,0x43,0xb7,0x41,0x84,0x43,0xc3,0xf2,0xad,0x43,0xa1,0xae,0x83, 14732 0x43,0x83,0x28,0xad,0x43,0x08,0xb2,0x52,0x83,0x43,0x80,0x39,0xac,0x43,0x81,0x49,0x83,0x43,0xf0,0x00, 14733 0xab,0x43,0xe4,0x67,0x85,0x43,0x76,0x4f,0xa8,0x43,0x08,0x9c,0xd7,0x86,0x43,0xd1,0x83,0xa6,0x43,0xec, 14734 0x45,0x87,0x43,0x01,0x75,0xa2,0x43,0x19,0x60,0x86,0x43,0xec,0xed,0xa3,0x43,0x09,0x06,0xd9,0xdc,0x6c, 14735 0x43,0x14,0x25,0xa4,0x43,0x08,0xa2,0xf0,0x6d,0x43,0x9f,0x7a,0xa6,0x43,0x47,0xec,0x77,0x43,0x80,0x39, 14736 0xac,0x43,0xa9,0xfe,0x77,0x43,0xb0,0x4e,0xae,0x43,0x08,0x23,0xa4,0x78,0x43,0xea,0x35,0xb0,0x43,0xd2, 14737 0x35,0x78,0x43,0xab,0x77,0xb1,0x43,0xc1,0x79,0x74,0x43,0xa2,0xa5,0xb1,0x43,0x08,0xc6,0x50,0x71,0x43, 14738 0x68,0xca,0xb1,0x43,0xab,0xce,0x6f,0x43,0xe7,0x52,0xb1,0x43,0xea,0x98,0x70,0x43,0xd4,0xa2,0xaf,0x43, 14739 0x08,0x9d,0x19,0x71,0x43,0x96,0xd8,0xae,0x43,0x9d,0x19,0x71,0x43,0xec,0x29,0xae,0x43,0xca,0x3f,0x72, 14740 0x43,0xab,0x5f,0xad,0x43,0x08,0xa6,0xf7,0x72,0x43,0xa7,0x70,0xac,0x43,0x09,0x0a,0x73,0x43,0x17,0x38, 14741 0xab,0x43,0x44,0xcd,0x6e,0x43,0x9f,0x86,0xa8,0x43,0x08,0xd4,0xed,0x6b,0x43,0xf8,0xba,0xa6,0x43,0x31, 14742 0x11,0x6b,0x43,0x2a,0xac,0xa2,0x43,0xd9,0xdc,0x6c,0x43,0x14,0x25,0xa4,0x43,0x09,0x01,0x05,0x06,0x66, 14743 0x5d,0x7a,0x43,0x74,0xeb,0xc2,0x43,0x08,0x09,0x22,0x77,0x43,0x50,0xbb,0xc7,0x43,0xe9,0xe0,0x7c,0x43, 14744 0xf5,0x86,0xc9,0x43,0x8f,0x94,0x7a,0x43,0xc5,0x95,0xcd,0x43,0x09,0x06,0x08,0x98,0x80,0x43,0x6b,0x19, 14745 0xc3,0x43,0x08,0xb7,0x35,0x82,0x43,0x79,0xf2,0xc7,0x43,0xf1,0xbe,0x7e,0x43,0x1e,0xbe,0xc9,0x43,0x73, 14746 0x7c,0x80,0x43,0xec,0xcc,0xcd,0x43,0x09,0x06,0x28,0xab,0x7d,0x43,0xae,0xde,0xc6,0x43,0x08,0x1e,0xcd, 14747 0x7b,0x43,0x8a,0xa2,0xc9,0x43,0x30,0x89,0x7f,0x43,0x5c,0x94,0xcc,0x43,0x28,0xab,0x7d,0x43,0x42,0x2a, 14748 0xcf,0x43,0x09,0x01,0x05,0x06,0x24,0x14,0xe0,0x42,0xf5,0x77,0x97,0x43,0x08,0xf7,0x1d,0xe7,0x42,0x74, 14749 0x00,0x97,0x43,0x4d,0x93,0xec,0x42,0xdb,0xf5,0x95,0x43,0x29,0x4b,0xed,0x42,0xcd,0x34,0x95,0x43,0x09, 14750 0x06,0x29,0x7b,0xf5,0x42,0x6f,0x1d,0x98,0x43,0x08,0xe4,0xf1,0xfb,0x42,0x61,0x5c,0x97,0x43,0xdb,0x7d, 14751 0x01,0x43,0xb2,0xbe,0x95,0x43,0x55,0x23,0x02,0x43,0xe7,0xaa,0x94,0x43,0x09,0x06,0x98,0xdc,0x03,0x43, 14752 0xbe,0x8b,0x98,0x43,0x08,0x66,0xdf,0x05,0x43,0x47,0xe6,0x97,0x43,0xae,0x87,0x08,0x43,0x98,0x48,0x96, 14753 0x43,0x61,0x08,0x09,0x43,0xd6,0x06,0x95,0x43,0x09,0x06,0x31,0x0b,0x0b,0x43,0x8e,0x82,0x98,0x43,0x08, 14754 0xdb,0xc5,0x0d,0x43,0x80,0xc1,0x97,0x43,0xd6,0xee,0x10,0x43,0xa9,0xec,0x95,0x43,0x79,0xcb,0x11,0x43, 14755 0x55,0x8f,0x94,0x43,0x09,0x06,0xd1,0x2f,0x18,0x43,0xdb,0x01,0x98,0x43,0x08,0xad,0xe7,0x18,0x43,0x38, 14756 0x25,0x97,0x43,0x8a,0x9f,0x19,0x43,0x80,0xb5,0x95,0x43,0xd6,0x1e,0x19,0x43,0xe0,0xd8,0x94,0x43,0x09, 14757 0x06,0x9a,0x5b,0x1d,0x43,0x58,0x8a,0x97,0x43,0x08,0x01,0x5d,0x1e,0x43,0xf1,0x88,0x96,0x43,0x2f,0x83, 14758 0x1f,0x43,0x19,0xb4,0x94,0x43,0x19,0xf0,0x1e,0x43,0x6f,0x05,0x94,0x43,0x09,0x06,0x0b,0x53,0x24,0x43, 14759 0xae,0xdb,0x96,0x43,0x08,0x25,0xd5,0x25,0x43,0x50,0xac,0x95,0x43,0x53,0xfb,0x26,0x43,0x8a,0x7b,0x93, 14760 0x43,0x76,0x43,0x26,0x43,0xb7,0x95,0x92,0x43,0x09,0x06,0x76,0x5b,0x2a,0x43,0x47,0xda,0x95,0x43,0x08, 14761 0xf3,0xef,0x2b,0x43,0x10,0xe2,0x94,0x43,0x6d,0x95,0x2c,0x43,0xae,0xc3,0x92,0x43,0x68,0xa6,0x2b,0x43, 14762 0x47,0xc2,0x91,0x43,0x09,0x06,0x36,0xc1,0x31,0x43,0x2c,0x58,0x94,0x43,0x08,0x8c,0x1e,0x33,0x43,0x31, 14763 0x3b,0x93,0x43,0x79,0x7a,0x33,0x43,0xff,0x25,0x91,0x43,0xd9,0x9d,0x32,0x43,0xc1,0x5b,0x90,0x43,0x09, 14764 0x06,0x25,0x35,0x36,0x43,0x31,0x3b,0x93,0x43,0x08,0x3f,0xb7,0x37,0x43,0xc1,0x67,0x92,0x43,0xe0,0x93, 14765 0x38,0x43,0xae,0xb7,0x90,0x43,0x7e,0x81,0x38,0x43,0x0d,0xdb,0x8f,0x43,0x09,0x06,0xb5,0x85,0x3b,0x43, 14766 0xe4,0xaf,0x91,0x43,0x08,0xcf,0x07,0x3d,0x43,0x9d,0x13,0x91,0x43,0xbc,0x63,0x3d,0x43,0x47,0xb6,0x8f, 14767 0x43,0xe5,0x9a,0x3d,0x43,0x74,0xd0,0x8e,0x43,0x09,0x06,0xae,0xc6,0x42,0x43,0xa4,0xd9,0x8e,0x43,0x08, 14768 0xca,0x48,0x44,0x43,0xfa,0x2a,0x8e,0x43,0xa2,0x11,0x44,0x43,0x9d,0xfb,0x8c,0x43,0x55,0x92,0x44,0x43, 14769 0x0d,0xc3,0x8b,0x43,0x09,0x06,0x39,0x10,0xc3,0x43,0x34,0x36,0x96,0x43,0x08,0x92,0x44,0xc1,0x43,0xe4, 14770 0xc7,0x95,0x43,0x6f,0xf0,0xbf,0x43,0x4b,0xbd,0x94,0x43,0x47,0xb9,0xbf,0x43,0x0b,0xf3,0x93,0x43,0x09, 14771 0x06,0x8f,0x49,0xbe,0x43,0xb7,0xad,0x96,0x43,0x08,0x11,0xb5,0xbc,0x43,0x77,0xe3,0x95,0x43,0x9c,0xf2, 14772 0xba,0x43,0xfa,0x4e,0x94,0x43,0xae,0x96,0xba,0x43,0x31,0x3b,0x93,0x43,0x09,0x06,0xdb,0xb0,0xb9,0x43, 14773 0x10,0xee,0x96,0x43,0x08,0x42,0xa6,0xb8,0x43,0xc8,0x51,0x96,0x43,0x50,0x5b,0xb7,0x43,0x19,0xb4,0x94, 14774 0x43,0xf7,0x1a,0xb7,0x43,0x58,0x72,0x93,0x43,0x09,0x06,0xf2,0x2b,0xb6,0x43,0x10,0xee,0x96,0x43,0x08, 14775 0x9d,0xce,0xb4,0x43,0x04,0x2d,0x96,0x43,0xed,0x30,0xb3,0x43,0x2c,0x58,0x94,0x43,0xce,0xcb,0xb2,0x43, 14776 0xd6,0xfa,0x92,0x43,0x09,0x06,0x5a,0x09,0xb1,0x43,0x19,0xc0,0x96,0x43,0x08,0x6c,0xad,0xb0,0x43,0x77, 14777 0xe3,0x95,0x43,0x7e,0x51,0xb0,0x43,0xc0,0x73,0x94,0x43,0xd8,0x91,0xb0,0x43,0x1e,0x97,0x93,0x43,0x09, 14778 0x06,0x48,0x4d,0xad,0x43,0xbe,0x7f,0x96,0x43,0x08,0x95,0xcc,0xac,0x43,0x58,0x7e,0x95,0x43,0x4d,0x30, 14779 0xac,0x43,0x80,0xa9,0x93,0x43,0xd8,0x79,0xac,0x43,0xd6,0xfa,0x92,0x43,0x09,0x06,0x90,0xd1,0xa9,0x43, 14780 0x14,0xd1,0x95,0x43,0x08,0x83,0x10,0xa9,0x43,0xb7,0xa1,0x94,0x43,0x3b,0x74,0xa8,0x43,0xf1,0x70,0x92, 14781 0x43,0x29,0xd0,0xa8,0x43,0x1e,0x8b,0x91,0x43,0x09,0x06,0x5a,0xcd,0xa6,0x43,0x8a,0x87,0x95,0x43,0x08, 14782 0x1c,0x03,0xa6,0x43,0x23,0x86,0x94,0x43,0x5f,0xb0,0xa5,0x43,0xc1,0x67,0x92,0x43,0xe1,0x27,0xa6,0x43, 14783 0x8a,0x6f,0x91,0x43,0x09,0x06,0xd4,0x5a,0xa3,0x43,0x2c,0x58,0x94,0x43,0x08,0x29,0xac,0xa2,0x43,0x31, 14784 0x3b,0x93,0x43,0x32,0x7e,0xa2,0x43,0xff,0x25,0x91,0x43,0x83,0xec,0xa2,0x43,0x8e,0x52,0x90,0x43,0x09, 14785 0x06,0xf8,0x96,0xa0,0x43,0x1e,0x97,0x93,0x43,0x08,0xeb,0xd5,0x9f,0x43,0x7b,0xba,0x92,0x43,0x99,0x67, 14786 0x9f,0x43,0x9d,0x13,0x91,0x43,0x99,0x67,0x9f,0x43,0xfa,0x36,0x90,0x43,0x09,0x06,0xeb,0xc9,0x9d,0x43, 14787 0xc8,0x39,0x92,0x43,0x08,0xde,0x08,0x9d,0x43,0xb2,0xa6,0x91,0x43,0xe6,0xda,0x9c,0x43,0x2c,0x40,0x90, 14788 0x43,0x52,0xbf,0x9c,0x43,0x5a,0x5a,0x8f,0x43,0x09,0x06,0x37,0x3d,0x9b,0x43,0x85,0x80,0x90,0x43,0x08, 14789 0x2a,0x7c,0x9a,0x43,0xdb,0xd1,0x8f,0x43,0xf0,0xa0,0x9a,0x43,0x7d,0xa2,0x8e,0x43,0x65,0x57,0x9a,0x43, 14790 0xee,0x69,0x8d,0x43,0x09,0x02,0x04,0x06,0x2a,0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x08,0x0d,0x8a,0x31, 14791 0x42,0x9f,0x0e,0x94,0x43,0xf3,0x1f,0x34,0x42,0x3d,0xfc,0x93,0x43,0x63,0xff,0x36,0x42,0xa9,0xe0,0x93, 14792 0x43,0x08,0xb5,0x34,0x5d,0x42,0x0b,0xf3,0x93,0x43,0x6d,0xa4,0x5e,0x42,0x03,0x39,0x98,0x43,0xe7,0x31, 14793 0x5b,0x42,0x93,0x89,0x9d,0x43,0x08,0x02,0x9c,0x58,0x42,0xd4,0x5a,0xa3,0x43,0x38,0x70,0x53,0x42,0x14, 14794 0x49,0xaa,0x43,0xf8,0xed,0x5e,0x42,0x83,0x28,0xad,0x43,0x08,0xea,0x68,0x68,0x42,0x20,0x22,0xaf,0x43, 14795 0x12,0xb8,0x6c,0x42,0xb5,0x49,0xb1,0x43,0x2a,0x4b,0x6d,0x42,0x0d,0x96,0xb3,0x43,0x07,0x2a,0x4b,0x6d, 14796 0x42,0xc6,0x05,0xb5,0x43,0x08,0x87,0x6e,0x6c,0x42,0x68,0xee,0xb7,0x43,0x1c,0x66,0x66,0x42,0x31,0x0e, 14797 0xbb,0x43,0x57,0x11,0x5e,0x42,0x8f,0x49,0xbe,0x43,0x08,0x66,0x96,0x54,0x42,0xb9,0x5c,0xb8,0x43,0x2c, 14798 0x2b,0x3c,0x42,0x68,0xd6,0xb3,0x43,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x07,0x2a,0xf4,0x2e,0x42, 14799 0x61,0xa4,0xa3,0x43,0x08,0x55,0x1a,0x30,0x42,0xf0,0xd0,0xa2,0x43,0xf8,0xf6,0x30,0x42,0xb2,0x06,0xa2, 14800 0x43,0x98,0xd3,0x31,0x42,0xd6,0x4e,0xa1,0x43,0x08,0x1c,0x6f,0x38,0x42,0x2a,0x94,0x9e,0x43,0xc1,0x22, 14801 0x36,0x42,0xf5,0x9b,0x9d,0x43,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x57, 14802 0xa2,0x9b,0x43,0x08,0xab,0x8f,0x35,0x42,0x8a,0xab,0x9b,0x43,0xe9,0x71,0x3a,0x42,0xb2,0xe2,0x9b,0x43, 14803 0xb7,0x74,0x3c,0x42,0x34,0x5a,0x9c,0x43,0x08,0x23,0x7d,0x42,0x42,0x0b,0x2f,0x9e,0x43,0xe5,0x9a,0x3d, 14804 0x42,0x38,0x6d,0xa3,0x43,0x36,0xd9,0x35,0x42,0xf3,0xd7,0xa7,0x43,0x08,0x12,0x61,0x2e,0x42,0xb0,0x42, 14805 0xac,0x43,0x63,0xff,0x36,0x42,0xdd,0x74,0xaf,0x43,0x1e,0xa6,0x45,0x42,0x44,0x82,0xb2,0x43,0x08,0x74, 14806 0x1b,0x4b,0x42,0x79,0x7a,0xb3,0x43,0x10,0x21,0x4f,0x42,0x2a,0x18,0xb5,0x43,0xdb,0x4c,0x54,0x42,0x91, 14807 0x19,0xb6,0x43,0x08,0xee,0x3f,0x65,0x42,0x5f,0x28,0xba,0x43,0xa7,0xaf,0x66,0x42,0xb9,0x50,0xb6,0x43, 14808 0x14,0x58,0x5c,0x42,0xca,0xdc,0xb1,0x43,0x08,0x2c,0x8b,0x4c,0x42,0x4e,0x30,0xac,0x43,0x19,0xcf,0x48, 14809 0x42,0x2a,0xd0,0xa8,0x43,0xbc,0xab,0x49,0x42,0xa9,0x4c,0xa6,0x43,0x08,0x61,0x5f,0x47,0x42,0xfa,0xa2, 14810 0xa2,0x43,0xa7,0xaf,0x66,0x42,0x85,0x98,0x94,0x43,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x07,0x2a, 14811 0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x09,0x06,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x08,0xdc,0xe3, 14812 0xf1,0x41,0xe9,0x9e,0x92,0x43,0xd2,0xe7,0x0b,0x42,0xd6,0x06,0x95,0x43,0x2a,0xf4,0x2e,0x42,0x04,0x21, 14813 0x94,0x43,0x07,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x08,0x87,0x17,0x2e,0x42,0xc3,0x62,0x95,0x43, 14814 0xe7,0x3a,0x2d,0x42,0xf5,0x6b,0x95,0x43,0x44,0x5e,0x2c,0x42,0xf5,0x6b,0x95,0x43,0x08,0xd1,0x47,0x1c, 14815 0x42,0x19,0xc0,0x96,0x43,0x66,0xdf,0x05,0x42,0x38,0x19,0x95,0x43,0x12,0x6a,0x00,0x42,0xb2,0xbe,0x95, 14816 0x43,0x08,0xbb,0x6b,0xea,0x41,0xd6,0x12,0x97,0x43,0x2d,0x82,0xfa,0x41,0x61,0x74,0x9b,0x43,0x7e,0x72, 14817 0x06,0x42,0x8a,0xab,0x9b,0x43,0x08,0xc8,0x39,0x12,0x42,0x4e,0xd0,0x9b,0x43,0x53,0xe3,0x22,0x42,0xc3, 14818 0x86,0x9b,0x43,0x2a,0xf4,0x2e,0x42,0x57,0xa2,0x9b,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43, 14819 0x08,0x01,0xa5,0x2a,0x42,0xa4,0x2d,0x9d,0x43,0x96,0x9c,0x24,0x42,0x06,0x40,0x9d,0x43,0x8a,0xb7,0x1d, 14820 0x42,0x9a,0x5b,0x9d,0x43,0x08,0x6b,0x16,0x13,0x42,0xcd,0x64,0x9d,0x43,0x42,0xc7,0x0e,0x42,0x9a,0x5b, 14821 0x9d,0x43,0x23,0x26,0x04,0x42,0xcd,0x64,0x9d,0x43,0x08,0xe6,0x91,0xeb,0x41,0x38,0x49,0x9d,0x43,0x73, 14822 0x7b,0xdb,0x41,0xf5,0x83,0x99,0x43,0x7f,0x60,0xe2,0x41,0x0b,0x0b,0x98,0x43,0x08,0x7f,0x60,0xe2,0x41, 14823 0xec,0x99,0x95,0x43,0xe3,0x5a,0xde,0x41,0xbe,0x7f,0x96,0x43,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43, 14824 0x07,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x09,0x06,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x08, 14825 0xd4,0x7e,0x29,0x42,0xab,0x6b,0xaf,0x43,0x4e,0x0c,0x26,0x42,0x44,0x6a,0xae,0x43,0x38,0x79,0x25,0x42, 14826 0xd4,0x96,0xad,0x43,0x08,0x25,0xbd,0x21,0x42,0xe2,0x4b,0xac,0x43,0x49,0x35,0x29,0x42,0x9a,0x97,0xa7, 14827 0x43,0x2a,0xf4,0x2e,0x42,0x61,0xa4,0xa3,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x09,0x06, 14828 0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x08,0x86,0x20,0x80,0x43,0x57,0x41,0xe6,0x43,0x7d,0x4e,0x80, 14829 0x43,0x25,0x38,0xe6,0x43,0xa5,0x85,0x80,0x43,0xf3,0x2e,0xe6,0x43,0x08,0x35,0xca,0x83,0x43,0xd4,0xc9, 14830 0xe5,0x43,0x9c,0xd7,0x86,0x43,0x44,0x91,0xe4,0x43,0xd5,0xca,0x8a,0x43,0x91,0x1c,0xe6,0x43,0x08,0x53, 14831 0x5f,0x8c,0x43,0xf8,0x1d,0xe7,0x43,0x2f,0x17,0x8d,0x43,0x4e,0x7b,0xe8,0x43,0x92,0x29,0x8d,0x43,0x2f, 14832 0x22,0xea,0x43,0x07,0x92,0x29,0x8d,0x43,0x44,0xb5,0xea,0x43,0x08,0xfe,0x0d,0x8d,0x43,0x2a,0x4b,0xed, 14833 0x43,0xe3,0x8b,0x8b,0x43,0x55,0x7d,0xf0,0x43,0xec,0x51,0x89,0x43,0x72,0x0b,0xf4,0x43,0x08,0xcd,0xd4, 14834 0x84,0x43,0x9d,0x55,0xfb,0x43,0xc9,0xe5,0x83,0x43,0x74,0x1e,0xfb,0x43,0x73,0x94,0x84,0x43,0x5a,0x90, 14835 0xf7,0x43,0x08,0xe8,0x62,0x88,0x43,0xfd,0x30,0xee,0x43,0x39,0xc5,0x86,0x43,0xdd,0xbf,0xeb,0x43,0x35, 14836 0xbe,0x81,0x43,0x40,0xde,0xed,0x43,0x08,0x4f,0x34,0x81,0x43,0x36,0x0c,0xee,0x43,0x08,0x98,0x80,0x43, 14837 0xfd,0x30,0xee,0x43,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x40,0xec, 14838 0x43,0x08,0x35,0xbe,0x81,0x43,0x06,0xf7,0xeb,0x43,0x15,0x65,0x83,0x43,0x49,0xa4,0xeb,0x43,0x1e,0x43, 14839 0x85,0x43,0xbe,0x5a,0xeb,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0x18,0xea,0x43,0x42,0x97,0x86,0x43,0x5f, 14840 0x67,0xf4,0x43,0xa9,0x98,0x87,0x43,0xd4,0x1d,0xf4,0x43,0x08,0x5c,0x25,0x8a,0x43,0xcf,0x16,0xef,0x43, 14841 0x46,0xaa,0x8d,0x43,0x5a,0x3c,0xe9,0x43,0x19,0x6c,0x88,0x43,0x53,0x5e,0xe7,0x43,0x08,0xc4,0x02,0x85, 14842 0x43,0x96,0x0b,0xe7,0x43,0x85,0x2c,0x82,0x43,0x83,0x67,0xe7,0x43,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7, 14843 0x43,0x07,0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x09,0x06,0xfd,0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43, 14844 0x08,0xfa,0x6c,0x78,0x43,0xd1,0xc2,0xe0,0x43,0x25,0x5c,0x6c,0x43,0x25,0x44,0xe8,0x43,0x1d,0xe5,0x7f, 14845 0x43,0x87,0x4a,0xe6,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,0x43,0x08,0xa6,0x27,0x7b,0x43,0x91, 14846 0x28,0xe8,0x43,0xbc,0xa2,0x77,0x43,0xb0,0x8d,0xe8,0x43,0xc6,0x68,0x75,0x43,0x57,0x4d,0xe8,0x43,0x08, 14847 0xe0,0xd2,0x72,0x43,0xab,0x9e,0xe7,0x43,0x50,0x9a,0x71,0x43,0x2a,0x27,0xe7,0x43,0xea,0x98,0x70,0x43, 14848 0x57,0x35,0xe4,0x43,0x08,0x94,0x3b,0x6f,0x43,0x14,0x7c,0xe2,0x43,0xff,0x13,0x6d,0x43,0x06,0xbb,0xe1, 14849 0x43,0xcf,0xfe,0x6a,0x43,0x06,0xbb,0xe1,0x43,0x08,0x44,0x9d,0x66,0x43,0x77,0x8e,0xe2,0x43,0x3b,0xef, 14850 0x6c,0x43,0x91,0x10,0xe4,0x43,0xfd,0x24,0x6c,0x43,0xb0,0x81,0xe6,0x43,0x08,0x96,0x23,0x6b,0x43,0xee, 14851 0x57,0xe9,0x43,0xca,0x0f,0x6a,0x43,0x5f,0x37,0xec,0x43,0x55,0x71,0x6e,0x43,0x9f,0x01,0xed,0x43,0x08, 14852 0xdb,0xfb,0x75,0x43,0x3b,0xef,0xec,0x43,0x09,0x3a,0x7b,0x43,0xb0,0xa5,0xec,0x43,0x1d,0xe5,0x7f,0x43, 14853 0x91,0x40,0xec,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x08,0xa9,0x16,0x7c,0x43,0xb0,0xb1, 14854 0xee,0x43,0x47,0xec,0x77,0x43,0xd9,0xe8,0xee,0x43,0x1e,0x9d,0x73,0x43,0xcf,0x16,0xef,0x43,0x08,0x0e, 14855 0xc9,0x6b,0x43,0xee,0x7b,0xef,0x43,0x7e,0x90,0x6a,0x43,0xfd,0x30,0xee,0x43,0x01,0xfc,0x68,0x43,0x4e, 14856 0x93,0xec,0x43,0x08,0x31,0xf9,0x66,0x43,0x4e,0x87,0xea,0x43,0x31,0x11,0x6b,0x43,0xd4,0xd5,0xe7,0x43, 14857 0xd9,0xc4,0x68,0x43,0xd4,0xc9,0xe5,0x43,0x08,0xe5,0x79,0x67,0x43,0x77,0x9a,0xe4,0x43,0x44,0x9d,0x66, 14858 0x43,0xab,0x86,0xe3,0x43,0x7e,0x78,0x66,0x43,0x0b,0xaa,0xe2,0x43,0x07,0x7e,0x78,0x66,0x43,0x57,0x29, 14859 0xe2,0x43,0x08,0xa7,0xaf,0x66,0x43,0xbe,0x1e,0xe1,0x43,0x87,0x56,0x68,0x43,0x77,0x82,0xe0,0x43,0xfd, 14860 0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,0x09,0x06,0xc4,0x41,0xbf,0x43,0x85,0xc0,0x72,0x42,0x08,0x73,0xdf, 14861 0xc0,0x43,0xf4,0x76,0x72,0x42,0x97,0x33,0xc2,0x43,0x85,0xc0,0x72,0x42,0xb2,0xb5,0xc3,0x43,0x64,0x56, 14862 0x75,0x42,0x08,0x03,0x24,0xc4,0x43,0x5e,0x7f,0x78,0x42,0xfa,0x51,0xc4,0x43,0x01,0x85,0x7c,0x42,0x5c, 14863 0x64,0xc4,0x43,0xa0,0xb3,0x80,0x42,0x07,0x5c,0x64,0xc4,0x43,0x10,0x93,0x83,0x42,0x08,0xc8,0x48,0xc4, 14864 0x43,0x1c,0x78,0x8a,0x42,0x27,0x6c,0xc3,0x43,0xaf,0xcf,0x94,0x42,0x23,0x7d,0xc2,0x43,0x99,0x9c,0xa4, 14865 0x42,0x08,0x3d,0xe7,0xbf,0x43,0xfb,0xfd,0xb5,0x42,0xb3,0x9d,0xbf,0x43,0x88,0x17,0xae,0x42,0xc4,0x41, 14866 0xbf,0x43,0x69,0x76,0xa3,0x42,0x07,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x08,0x4f,0x8b,0xbf,0x43, 14867 0xed,0x81,0x91,0x42,0xe4,0xa6,0xbf,0x43,0x5d,0x61,0x94,0x42,0xfa,0x39,0xc0,0x43,0x3b,0x49,0x9d,0x42, 14868 0x08,0x2b,0x43,0xc0,0x43,0x28,0xed,0xa9,0x42,0x61,0x3b,0xc1,0x43,0x00,0x9e,0xa5,0x42,0xe4,0xb2,0xc1, 14869 0x43,0x5d,0x91,0x9c,0x42,0x08,0x78,0xce,0xc1,0x43,0xfd,0x36,0x90,0x42,0x22,0x89,0xc4,0x43,0x81,0x72, 14870 0x86,0x42,0xae,0xc6,0xc2,0x43,0xa0,0xb3,0x80,0x42,0x08,0x54,0x86,0xc2,0x43,0x58,0xd1,0x7e,0x42,0x30, 14871 0x32,0xc1,0x43,0xce,0x5e,0x7b,0x42,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x07,0xc4,0x41,0xbf,0x43, 14872 0x85,0xc0,0x72,0x42,0x09,0x06,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x08,0x35,0xfd,0xbb,0x43,0xa4, 14873 0xa1,0x5c,0x42,0x5e,0x34,0xbc,0x43,0x9d,0x2a,0x70,0x42,0x5e,0x40,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08, 14874 0x4c,0x9c,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,0xef,0xbe,0x43,0x0e,0x0a,0x73,0x42,0xc4,0x41,0xbf,0x43, 14875 0x85,0xc0,0x72,0x42,0x07,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x08,0xcd,0x13,0xbf,0x43,0xe8,0xf1, 14876 0x7b,0x42,0xd6,0xe5,0xbe,0x43,0x71,0x3b,0x7c,0x42,0xdf,0xb7,0xbe,0x43,0x71,0x3b,0x7c,0x42,0x08,0x08, 14877 0xe3,0xbc,0x43,0xa4,0x61,0x7d,0x42,0x28,0x3c,0xbb,0x43,0x91,0x45,0x69,0x42,0x28,0x3c,0xbb,0x43,0x58, 14878 0x71,0x6e,0x42,0x08,0xce,0xfb,0xba,0x43,0xd5,0x35,0x78,0x42,0x59,0x45,0xbb,0x43,0x58,0x23,0x82,0x42, 14879 0xa1,0xe1,0xbb,0x43,0xd7,0xbe,0x88,0x42,0x08,0xc9,0x18,0xbc,0x43,0xaf,0x9f,0x8c,0x42,0x1e,0x76,0xbd, 14880 0x43,0x51,0x7c,0x8d,0x42,0xd6,0xe5,0xbe,0x43,0xf4,0x58,0x8e,0x42,0x08,0x9c,0x0a,0xbf,0x43,0x45,0xc7, 14881 0x8e,0x42,0x30,0x26,0xbf,0x43,0x96,0x35,0x8f,0x42,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x07,0xc4, 14882 0x41,0xbf,0x43,0x69,0x76,0xa3,0x42,0x08,0x08,0xef,0xbe,0x43,0xb1,0xd6,0x99,0x42,0xe8,0x89,0xbe,0x43, 14883 0xde,0xc5,0x8d,0x42,0xc0,0x46,0xbc,0x43,0xc2,0x5b,0x90,0x42,0x08,0x9c,0xf2,0xba,0x43,0x86,0x80,0x90, 14884 0x42,0xf2,0x43,0xba,0x43,0xe8,0x73,0x87,0x42,0x8f,0x31,0xba,0x43,0xb6,0xf4,0x7d,0x42,0x07,0x8f,0x31, 14885 0xba,0x43,0x21,0xc6,0x76,0x42,0x08,0xc0,0x3a,0xba,0x43,0x5f,0x48,0x6b,0x42,0xae,0x96,0xba,0x43,0xe3, 14886 0x83,0x61,0x42,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x09,0x06,0xea,0x74,0xea,0x43,0x61,0x44,0x93, 14887 0x43,0x08,0x24,0x5c,0xec,0x43,0x31,0x3b,0x93,0x43,0xfb,0x30,0xee,0x43,0x93,0x4d,0x93,0x43,0x0d,0xe1, 14888 0xef,0x43,0x80,0xa9,0x93,0x43,0x08,0x8f,0x58,0xf0,0x43,0xd1,0x17,0x94,0x43,0xb7,0x8f,0xf0,0x43,0x10, 14889 0xe2,0x94,0x43,0xea,0x98,0xf0,0x43,0xa9,0xec,0x95,0x43,0x07,0xea,0x98,0xf0,0x43,0x38,0x25,0x97,0x43, 14890 0x08,0x23,0x74,0xf0,0x43,0x9f,0x32,0x9a,0x43,0x5a,0x60,0xef,0x43,0x53,0xcb,0x9e,0x43,0x2d,0x3a,0xee, 14891 0x43,0xfd,0x91,0xa3,0x43,0x08,0xa2,0xf0,0xed,0x43,0xdd,0x38,0xa5,0x43,0x17,0xa7,0xed,0x43,0xbe,0xdf, 14892 0xa6,0x43,0x5a,0x54,0xed,0x43,0x9f,0x86,0xa8,0x43,0x08,0xfc,0x24,0xec,0x43,0xca,0xc4,0xad,0x43,0x48, 14893 0xa4,0xeb,0x43,0x40,0x6f,0xab,0x43,0x28,0x3f,0xeb,0x43,0x1c,0x0f,0xa8,0x43,0x08,0x1f,0x6d,0xeb,0x43, 14894 0x72,0x48,0xa3,0x43,0x67,0x09,0xec,0x43,0xd1,0x53,0x9e,0x43,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,0x43, 14895 0x07,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x08,0x7e,0x90,0xea,0x43,0x8a,0x9f,0x99,0x43,0x12,0xac, 14896 0xea,0x43,0xbc,0xa8,0x99,0x43,0xa7,0xc7,0xea,0x43,0xbc,0xa8,0x99,0x43,0x08,0x51,0x76,0xeb,0x43,0x9f, 14897 0x32,0x9a,0x43,0x5e,0x37,0xec,0x43,0x49,0xed,0x9c,0x43,0xb0,0xa5,0xec,0x43,0x2a,0xa0,0xa0,0x43,0x08, 14898 0x09,0xe6,0xec,0x43,0xd1,0x77,0xa4,0x43,0x28,0x4b,0xed,0x43,0x61,0xa4,0xa3,0x43,0xab,0xc2,0xed,0x43, 14899 0x8e,0xb2,0xa0,0x43,0x08,0x70,0xe7,0xed,0x43,0xde,0x08,0x9d,0x43,0x87,0x86,0xf0,0x43,0x2f,0x53,0x97, 14900 0x43,0x87,0x7a,0xee,0x43,0xec,0x99,0x95,0x43,0x08,0xca,0x27,0xee,0x43,0xff,0x3d,0x95,0x43,0x74,0xca, 14901 0xec,0x43,0x55,0x8f,0x94,0x43,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x07,0xea,0x74,0xea,0x43,0x61, 14902 0x44,0x93,0x43,0x09,0x06,0x05,0xd3,0xe5,0x43,0x19,0x9c,0x90,0x43,0x08,0x09,0xc2,0xe6,0x43,0xd1,0xff, 14903 0x8f,0x43,0x4d,0x6f,0xe6,0x43,0x74,0xe8,0x92,0x43,0x3b,0xd7,0xe8,0x43,0xc3,0x56,0x93,0x43,0x08,0x1f, 14904 0x61,0xe9,0x43,0x93,0x4d,0x93,0x43,0x05,0xeb,0xe9,0x43,0x93,0x4d,0x93,0x43,0xea,0x74,0xea,0x43,0x61, 14905 0x44,0x93,0x43,0x07,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x08,0x24,0x50,0xea,0x43,0xe7,0xaa,0x94, 14906 0x43,0x2d,0x22,0xea,0x43,0xe7,0xaa,0x94,0x43,0x36,0xf4,0xe9,0x43,0xe7,0xaa,0x94,0x43,0x08,0xa2,0xcc, 14907 0xe7,0x43,0xe0,0xd8,0x94,0x43,0xd4,0xc9,0xe5,0x43,0x19,0xa8,0x92,0x43,0xd4,0xc9,0xe5,0x43,0x27,0x69, 14908 0x93,0x43,0x08,0x17,0x77,0xe5,0x43,0xe0,0xd8,0x94,0x43,0x67,0xe5,0xe5,0x43,0x47,0xda,0x95,0x43,0x43, 14909 0x9d,0xe6,0x43,0xe2,0xd3,0x97,0x43,0x08,0x9d,0xdd,0xe6,0x43,0xad,0xe7,0x98,0x43,0x09,0xce,0xe8,0x43, 14910 0xff,0x55,0x99,0x43,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x07,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b, 14911 0x43,0x08,0x71,0xcf,0xe9,0x43,0x53,0xb3,0x9a,0x43,0xa7,0xbb,0xe8,0x43,0xdb,0x0d,0x9a,0x43,0xc6,0x14, 14912 0xe7,0x43,0xdb,0x0d,0x9a,0x43,0x08,0x48,0x80,0xe5,0x43,0xdb,0x0d,0x9a,0x43,0x0a,0xb6,0xe4,0x43,0xc3, 14913 0x6e,0x97,0x43,0x76,0x9a,0xe4,0x43,0x74,0xf4,0x94,0x43,0x07,0x76,0x9a,0xe4,0x43,0x79,0xd7,0x93,0x43, 14914 0x08,0xd8,0xac,0xe4,0x43,0x66,0x27,0x92,0x43,0x29,0x1b,0xe5,0x43,0xe0,0xc0,0x90,0x43,0x05,0xd3,0xe5, 14915 0x43,0x19,0x9c,0x90,0x43,0x09,0x06,0x1b,0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x08,0x71,0x0b,0xf4,0x42, 14916 0x00,0x0e,0x8d,0x42,0x8c,0x0f,0x01,0x43,0x3e,0xc0,0x89,0x42,0xf3,0x28,0x06,0x43,0x48,0x9e,0x8b,0x42, 14917 0x08,0x15,0x89,0x09,0x43,0x00,0x0e,0x8d,0x42,0xe0,0x9c,0x0a,0x43,0xc1,0x8b,0x98,0x42,0xa6,0xc1,0x0a, 14918 0x43,0x02,0xa5,0xaa,0x42,0x07,0xa6,0xc1,0x0a,0x43,0xf9,0xf6,0xb0,0x42,0x08,0xa6,0xc1,0x0a,0x43,0x47, 14919 0x8e,0xb4,0x42,0x42,0xaf,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0xe0,0x9c,0x0a,0x43,0xba,0x74,0xbc,0x42,0x08, 14920 0xa1,0xd2,0x09,0x43,0x40,0x47,0xd0,0x42,0x0d,0xab,0x07,0x43,0x91,0xb5,0xd0,0x42,0x3b,0xb9,0x04,0x43, 14921 0xec,0x71,0xba,0x42,0x08,0xe5,0x5b,0x03,0x43,0xe3,0x33,0xa8,0x42,0x63,0xd8,0x00,0x43,0xce,0x70,0x9f, 14922 0x42,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x07,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,0x42,0x08,0xed, 14923 0x6f,0xed,0x42,0x73,0x24,0x9d,0x42,0xd8,0x0c,0xf5,0x42,0x99,0x6c,0x9c,0x42,0x27,0xab,0xfd,0x42,0xea, 14924 0xda,0x9c,0x42,0x08,0x36,0xca,0x03,0x43,0x2b,0x94,0x9e,0x42,0x68,0xc7,0x01,0x43,0x8f,0xbe,0xa2,0x42, 14925 0xfa,0x06,0x08,0x43,0x73,0xb4,0xb5,0x42,0x08,0x8e,0x2e,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0x9d,0xe3,0x08, 14926 0x43,0xd7,0x1e,0x99,0x42,0x28,0x15,0x05,0x43,0x32,0x3b,0x93,0x42,0x08,0x63,0xf0,0x04,0x43,0x70,0xed, 14927 0x8f,0x42,0x71,0x0b,0xf4,0x42,0x32,0x3b,0x93,0x42,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x07,0x1b, 14928 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x09,0x06,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,0x08,0x8e,0x55, 14929 0xc0,0x42,0xb8,0x4d,0x86,0x42,0x60,0xbf,0xd7,0x42,0x3e,0xf0,0x91,0x42,0x63,0xf6,0xe4,0x42,0x70,0xed, 14930 0x8f,0x42,0x08,0x7a,0x89,0xe5,0x42,0xac,0xc8,0x8f,0x42,0xcc,0xf7,0xe5,0x42,0xac,0xc8,0x8f,0x42,0x1b, 14931 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x07,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x08,0x63,0xf6,0xe4, 14932 0x42,0x3b,0x19,0x95,0x42,0xe6,0x61,0xe3,0x42,0x00,0x3e,0x95,0x42,0xf4,0x16,0xe2,0x42,0xc4,0x62,0x95, 14933 0x42,0x08,0x6e,0x74,0xd6,0x42,0x15,0xd1,0x95,0x42,0x97,0x63,0xca,0x42,0xaf,0xcf,0x94,0x42,0xfb,0x2d, 14934 0xbe,0x42,0x86,0x80,0x90,0x42,0x08,0x97,0x03,0xba,0x42,0xce,0x10,0x8f,0x42,0x5e,0x28,0xba,0x42,0x3e, 14935 0xf0,0x91,0x42,0xf2,0x4f,0xbc,0x42,0x45,0xf7,0x96,0x42,0x08,0x27,0x54,0xbf,0x42,0x73,0x24,0x9d,0x42, 14936 0xa5,0xe8,0xc0,0x42,0x86,0xe0,0xa0,0x42,0xe4,0xca,0xc5,0x42,0xed,0x11,0xaa,0x42,0x08,0x54,0xaa,0xc8, 14937 0x42,0x86,0x40,0xb1,0x42,0x59,0x81,0xc5,0x42,0xa1,0x11,0xc4,0x42,0x3e,0xe7,0xbf,0x42,0xfb,0x8d,0xce, 14938 0x42,0x08,0xb4,0x6d,0xb7,0x42,0x30,0xc2,0xd9,0x42,0x46,0xf5,0xc9,0x42,0xdf,0x53,0xd9,0x42,0x38,0x40, 14939 0xcb,0x42,0x62,0x8f,0xcf,0x42,0x08,0x7d,0xf9,0xcc,0x42,0xec,0xa1,0xc2,0x42,0x07,0x43,0xcd,0x42,0x6c, 14940 0xdd,0xb8,0x42,0x2b,0x8b,0xcc,0x42,0x92,0xf5,0xaf,0x42,0x08,0xf9,0x8d,0xce,0x42,0x41,0x57,0xa7,0x42, 14941 0x5b,0xb8,0xd2,0x42,0xae,0x2f,0xa5,0x42,0x18,0x2f,0xd9,0x42,0x13,0x2a,0xa1,0x42,0x08,0x41,0x7e,0xdd, 14942 0x42,0xe3,0x03,0xa0,0x42,0x2e,0xf2,0xe1,0x42,0x7c,0x02,0x9f,0x42,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e, 14943 0x42,0x07,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x08,0x4d,0x63,0xe4,0x42,0x00,0x9e,0xa5,0x42,0xf4, 14944 0x16,0xe2,0x42,0x15,0x31,0xa6,0x42,0x99,0xca,0xdf,0x42,0x2b,0xc4,0xa6,0x42,0x08,0xc0,0x82,0xc6,0x42, 14945 0xc4,0xc2,0xa5,0x42,0x57,0xe1,0xd5,0x42,0x91,0xb5,0xd0,0x42,0x54,0xda,0xd0,0x42,0x97,0x93,0xd2,0x42, 14946 0x08,0x9c,0x3a,0xc7,0x42,0x17,0x58,0xdc,0x42,0x9c,0x0a,0xbf,0x42,0x6e,0xa4,0xde,0x42,0x90,0x25,0xb8, 14947 0x42,0xdf,0x53,0xd9,0x42,0x08,0x59,0x21,0xb5,0x42,0xf2,0xdf,0xd4,0x42,0x51,0x43,0xb3,0x42,0x91,0xb5, 14948 0xd0,0x42,0xc5,0x29,0xbb,0x42,0x0e,0x1a,0xca,0x42,0x08,0x65,0x36,0xc4,0x42,0xd0,0x07,0xbd,0x42,0x3e, 14949 0xe7,0xbf,0x42,0x37,0x09,0xbe,0x42,0x0c,0xea,0xc1,0x42,0xcd,0xd0,0xaf,0x42,0x08,0x2b,0x5b,0xc4,0x42, 14950 0x18,0x08,0xa3,0x42,0x67,0xa6,0xab,0x42,0x99,0x3c,0x94,0x42,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42, 14951 0x09,]; 14952 14953 private struct ThePath { 14954 public: 14955 enum Command { 14956 Bounds, // always first, has 4 args (x0, y0, x1, y1) 14957 StrokeMode, 14958 FillMode, 14959 StrokeFillMode, 14960 NormalStroke, 14961 ThinStroke, 14962 MoveTo, 14963 LineTo, 14964 CubicTo, // cubic bezier 14965 EndPath, 14966 } 14967 14968 public: 14969 const(ubyte)[] path; 14970 uint ppos; 14971 14972 public: 14973 this (const(void)[] apath) pure nothrow @trusted @nogc { 14974 path = cast(const(ubyte)[])apath; 14975 } 14976 14977 @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (ppos >= path.length); } 14978 14979 Command getCommand () nothrow @trusted @nogc { 14980 pragma(inline, true); 14981 if (ppos >= cast(uint)path.length) assert(0, "invalid path"); 14982 return cast(Command)(path.ptr[ppos++]); 14983 } 14984 14985 // number of (x,y) pairs for this command 14986 static int argCount (in Command cmd) nothrow @safe @nogc { 14987 version(aliced) pragma(inline, true); 14988 if (cmd == Command.Bounds) return 2; 14989 else if (cmd == Command.MoveTo || cmd == Command.LineTo) return 1; 14990 else if (cmd == Command.CubicTo) return 3; 14991 else return 0; 14992 } 14993 14994 void skipArgs (int argc) nothrow @trusted @nogc { 14995 pragma(inline, true); 14996 ppos += cast(uint)(float.sizeof*2*argc); 14997 } 14998 14999 float getFloat () nothrow @trusted @nogc { 15000 pragma(inline, true); 15001 if (ppos >= cast(uint)path.length || cast(uint)path.length-ppos < float.sizeof) assert(0, "invalid path"); 15002 version(LittleEndian) { 15003 float res = *cast(const(float)*)(&path.ptr[ppos]); 15004 ppos += cast(uint)float.sizeof; 15005 return res; 15006 } else { 15007 static assert(float.sizeof == 4); 15008 uint xp = path.ptr[ppos]|(path.ptr[ppos+1]<<8)|(path.ptr[ppos+2]<<16)|(path.ptr[ppos+3]<<24); 15009 ppos += cast(uint)float.sizeof; 15010 return *cast(const(float)*)(&xp); 15011 } 15012 } 15013 } 15014 15015 // this will add baphomet's background path to the current NanoVega path, so you can fill it. 15016 public void addBaphometBack (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc { 15017 if (nvg is null) return; 15018 15019 auto path = ThePath(baphometPath); 15020 15021 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15022 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15023 15024 bool inPath = false; 15025 while (!path.empty) { 15026 auto cmd = path.getCommand(); 15027 switch (cmd) { 15028 case ThePath.Command.MoveTo: 15029 inPath = true; 15030 immutable float ex = getScaledX(); 15031 immutable float ey = getScaledY(); 15032 nvg.moveTo(ex, ey); 15033 break; 15034 case ThePath.Command.LineTo: 15035 inPath = true; 15036 immutable float ex = getScaledX(); 15037 immutable float ey = getScaledY(); 15038 nvg.lineTo(ex, ey); 15039 break; 15040 case ThePath.Command.CubicTo: // cubic bezier 15041 inPath = true; 15042 immutable float x1 = getScaledX(); 15043 immutable float y1 = getScaledY(); 15044 immutable float x2 = getScaledX(); 15045 immutable float y2 = getScaledY(); 15046 immutable float ex = getScaledX(); 15047 immutable float ey = getScaledY(); 15048 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15049 break; 15050 case ThePath.Command.EndPath: 15051 if (inPath) return; 15052 break; 15053 default: 15054 path.skipArgs(path.argCount(cmd)); 15055 break; 15056 } 15057 } 15058 } 15059 15060 // this will add baphomet's pupil paths to the current NanoVega path, so you can fill it. 15061 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 { 15062 // pupils starts with "fill-and-stroke" mode 15063 if (nvg is null) return; 15064 15065 auto path = ThePath(baphometPath); 15066 15067 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15068 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15069 15070 bool inPath = false; 15071 bool pupLeft = true; 15072 while (!path.empty) { 15073 auto cmd = path.getCommand(); 15074 switch (cmd) { 15075 case ThePath.Command.StrokeFillMode: inPath = true; break; 15076 case ThePath.Command.MoveTo: 15077 if (!inPath) goto default; 15078 static if (!left) { if (pupLeft) goto default; } 15079 static if (!right) { if (!pupLeft) goto default; } 15080 immutable float ex = getScaledX(); 15081 immutable float ey = getScaledY(); 15082 nvg.moveTo(ex, ey); 15083 break; 15084 case ThePath.Command.LineTo: 15085 if (!inPath) goto default; 15086 static if (!left) { if (pupLeft) goto default; } 15087 static if (!right) { if (!pupLeft) goto default; } 15088 immutable float ex = getScaledX(); 15089 immutable float ey = getScaledY(); 15090 nvg.lineTo(ex, ey); 15091 break; 15092 case ThePath.Command.CubicTo: // cubic bezier 15093 if (!inPath) goto default; 15094 static if (!left) { if (pupLeft) goto default; } 15095 static if (!right) { if (!pupLeft) goto default; } 15096 immutable float x1 = getScaledX(); 15097 immutable float y1 = getScaledY(); 15098 immutable float x2 = getScaledX(); 15099 immutable float y2 = getScaledY(); 15100 immutable float ex = getScaledX(); 15101 immutable float ey = getScaledY(); 15102 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15103 break; 15104 case ThePath.Command.EndPath: 15105 if (inPath) { 15106 if (pupLeft) pupLeft = false; else return; 15107 } 15108 break; 15109 default: 15110 path.skipArgs(path.argCount(cmd)); 15111 break; 15112 } 15113 } 15114 } 15115 15116 // mode: 'f' to allow fills; 's' to allow strokes; 'w' to allow stroke widths; 'c' to replace fills with strokes 15117 public void renderBaphomet(string mode="fs") (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc { 15118 template hasChar(char ch, string s) { 15119 static if (s.length == 0) enum hasChar = false; 15120 else static if (s[0] == ch) enum hasChar = true; 15121 else enum hasChar = hasChar!(ch, s[1..$]); 15122 } 15123 enum AllowStroke = hasChar!('s', mode); 15124 enum AllowFill = hasChar!('f', mode); 15125 enum AllowWidth = hasChar!('w', mode); 15126 enum Contour = hasChar!('c', mode); 15127 //static assert(AllowWidth || AllowFill); 15128 15129 if (nvg is null) return; 15130 15131 auto path = ThePath(baphometPath); 15132 15133 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15134 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15135 15136 int mode = 0; 15137 int sw = ThePath.Command.NormalStroke; 15138 nvg.beginPath(); 15139 while (!path.empty) { 15140 auto cmd = path.getCommand(); 15141 switch (cmd) { 15142 case ThePath.Command.StrokeMode: mode = ThePath.Command.StrokeMode; break; 15143 case ThePath.Command.FillMode: mode = ThePath.Command.FillMode; break; 15144 case ThePath.Command.StrokeFillMode: mode = ThePath.Command.StrokeFillMode; break; 15145 case ThePath.Command.NormalStroke: sw = ThePath.Command.NormalStroke; break; 15146 case ThePath.Command.ThinStroke: sw = ThePath.Command.ThinStroke; break; 15147 case ThePath.Command.MoveTo: 15148 immutable float ex = getScaledX(); 15149 immutable float ey = getScaledY(); 15150 nvg.moveTo(ex, ey); 15151 break; 15152 case ThePath.Command.LineTo: 15153 immutable float ex = getScaledX(); 15154 immutable float ey = getScaledY(); 15155 nvg.lineTo(ex, ey); 15156 break; 15157 case ThePath.Command.CubicTo: // cubic bezier 15158 immutable float x1 = getScaledX(); 15159 immutable float y1 = getScaledY(); 15160 immutable float x2 = getScaledX(); 15161 immutable float y2 = getScaledY(); 15162 immutable float ex = getScaledX(); 15163 immutable float ey = getScaledY(); 15164 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15165 break; 15166 case ThePath.Command.EndPath: 15167 if (mode == ThePath.Command.FillMode || mode == ThePath.Command.StrokeFillMode) { 15168 static if (AllowFill || Contour) { 15169 static if (Contour) { 15170 if (mode == ThePath.Command.FillMode) { nvg.strokeWidth = 1; nvg.stroke(); } 15171 } else { 15172 nvg.fill(); 15173 } 15174 } 15175 } 15176 if (mode == ThePath.Command.StrokeMode || mode == ThePath.Command.StrokeFillMode) { 15177 static if (AllowStroke || Contour) { 15178 static if (AllowWidth) { 15179 if (sw == ThePath.Command.NormalStroke) nvg.strokeWidth = 1; 15180 else if (sw == ThePath.Command.ThinStroke) nvg.strokeWidth = 0.5; 15181 else assert(0, "wtf?!"); 15182 } 15183 nvg.stroke(); 15184 } 15185 } 15186 nvg.newPath(); 15187 break; 15188 default: 15189 path.skipArgs(path.argCount(cmd)); 15190 break; 15191 } 15192 } 15193 nvg.newPath(); 15194 }